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_copy(opt_encoding, 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)
1805 const char *id = view->id;
1811 string_copy(view->cmd, opt_cmd);
1813 /* When running random commands, initially show the
1814 * command in the title. However, it maybe later be
1815 * overwritten if a commit line is selected. */
1816 string_copy(view->ref, view->cmd);
1818 } else if (view == VIEW(REQ_VIEW_TREE)) {
1819 const char *format = view->cmd_env ? view->cmd_env : view->cmd_fmt;
1820 char path[SIZEOF_STR];
1822 if (strcmp(view->vid, view->id))
1823 opt_path[0] = path[0] = 0;
1824 else if (sq_quote(path, 0, opt_path) >= sizeof(path))
1827 if (!string_format(view->cmd, format, id, path))
1831 const char *format = view->cmd_env ? view->cmd_env : view->cmd_fmt;
1833 if (!string_format(view->cmd, format, id, id, id, id, id))
1836 /* Put the current ref_* value to the view title ref
1837 * member. This is needed by the blob view. Most other
1838 * views sets it automatically after loading because the
1839 * first line is a commit line. */
1840 string_copy(view->ref, id);
1843 /* Special case for the pager view. */
1845 view->pipe = opt_pipe;
1848 view->pipe = popen(view->cmd, "r");
1854 set_nonblocking_input(TRUE);
1859 string_copy_rev(view->vid, id);
1864 for (i = 0; i < view->lines; i++)
1865 if (view->line[i].data)
1866 free(view->line[i].data);
1872 view->start_time = time(NULL);
1877 static struct line *
1878 realloc_lines(struct view *view, size_t line_size)
1880 struct line *tmp = realloc(view->line, sizeof(*view->line) * line_size);
1886 view->line_size = line_size;
1891 update_view(struct view *view)
1893 char in_buffer[BUFSIZ];
1894 char out_buffer[BUFSIZ * 2];
1896 /* The number of lines to read. If too low it will cause too much
1897 * redrawing (and possible flickering), if too high responsiveness
1899 unsigned long lines = view->height;
1900 int redraw_from = -1;
1905 /* Only redraw if lines are visible. */
1906 if (view->offset + view->height >= view->lines)
1907 redraw_from = view->lines - view->offset;
1909 /* FIXME: This is probably not perfect for backgrounded views. */
1910 if (!realloc_lines(view, view->lines + lines))
1913 while ((line = fgets(in_buffer, sizeof(in_buffer), view->pipe))) {
1914 size_t linelen = strlen(line);
1917 line[linelen - 1] = 0;
1919 if (opt_iconv != ICONV_NONE) {
1921 size_t inlen = linelen;
1923 char *outbuf = out_buffer;
1924 size_t outlen = sizeof(out_buffer);
1928 ret = iconv(opt_iconv, &inbuf, &inlen, &outbuf, &outlen);
1929 if (ret != (size_t) -1) {
1931 linelen = strlen(out_buffer);
1935 if (!view->ops->read(view, line))
1945 lines = view->lines;
1946 for (digits = 0; lines; digits++)
1949 /* Keep the displayed view in sync with line number scaling. */
1950 if (digits != view->digits) {
1951 view->digits = digits;
1956 if (!view_is_displayed(view))
1959 if (view == VIEW(REQ_VIEW_TREE)) {
1960 /* Clear the view and redraw everything since the tree sorting
1961 * might have rearranged things. */
1964 } else if (redraw_from >= 0) {
1965 /* If this is an incremental update, redraw the previous line
1966 * since for commits some members could have changed when
1967 * loading the main view. */
1968 if (redraw_from > 0)
1971 /* Since revision graph visualization requires knowledge
1972 * about the parent commit, it causes a further one-off
1973 * needed to be redrawn for incremental updates. */
1974 if (redraw_from > 0 && opt_rev_graph)
1977 /* Incrementally draw avoids flickering. */
1978 redraw_view_from(view, redraw_from);
1981 /* Update the title _after_ the redraw so that if the redraw picks up a
1982 * commit reference in view->ref it'll be available here. */
1983 update_view_title(view);
1986 if (ferror(view->pipe)) {
1987 report("Failed to read: %s", strerror(errno));
1990 } else if (feof(view->pipe)) {
1998 report("Allocation failure");
2001 view->ops->read(view, NULL);
2006 static struct line *
2007 add_line_data(struct view *view, void *data, enum line_type type)
2009 struct line *line = &view->line[view->lines++];
2011 memset(line, 0, sizeof(*line));
2018 static struct line *
2019 add_line_text(struct view *view, char *data, enum line_type type)
2022 data = strdup(data);
2024 return data ? add_line_data(view, data, type) : NULL;
2033 OPEN_DEFAULT = 0, /* Use default view switching. */
2034 OPEN_SPLIT = 1, /* Split current view. */
2035 OPEN_BACKGROUNDED = 2, /* Backgrounded. */
2036 OPEN_RELOAD = 4, /* Reload view even if it is the current. */
2040 open_view(struct view *prev, enum request request, enum open_flags flags)
2042 bool backgrounded = !!(flags & OPEN_BACKGROUNDED);
2043 bool split = !!(flags & OPEN_SPLIT);
2044 bool reload = !!(flags & OPEN_RELOAD);
2045 struct view *view = VIEW(request);
2046 int nviews = displayed_views();
2047 struct view *base_view = display[0];
2049 if (view == prev && nviews == 1 && !reload) {
2050 report("Already in %s view", view->name);
2054 if (view->ops->open) {
2055 if (!view->ops->open(view)) {
2056 report("Failed to load %s view", view->name);
2060 } else if ((reload || strcmp(view->vid, view->id)) &&
2061 !begin_update(view)) {
2062 report("Failed to load %s view", view->name);
2071 /* Maximize the current view. */
2072 memset(display, 0, sizeof(display));
2074 display[current_view] = view;
2077 /* Resize the view when switching between split- and full-screen,
2078 * or when switching between two different full-screen views. */
2079 if (nviews != displayed_views() ||
2080 (nviews == 1 && base_view != display[0]))
2083 if (split && prev->lineno - prev->offset >= prev->height) {
2084 /* Take the title line into account. */
2085 int lines = prev->lineno - prev->offset - prev->height + 1;
2087 /* Scroll the view that was split if the current line is
2088 * outside the new limited view. */
2089 do_scroll_view(prev, lines);
2092 if (prev && view != prev) {
2093 if (split && !backgrounded) {
2094 /* "Blur" the previous view. */
2095 update_view_title(prev);
2098 view->parent = prev;
2101 if (view->pipe && view->lines == 0) {
2102 /* Clear the old view and let the incremental updating refill
2111 /* If the view is backgrounded the above calls to report()
2112 * won't redraw the view title. */
2114 update_view_title(view);
2119 * User request switch noodle
2123 view_driver(struct view *view, enum request request)
2130 case REQ_MOVE_PAGE_UP:
2131 case REQ_MOVE_PAGE_DOWN:
2132 case REQ_MOVE_FIRST_LINE:
2133 case REQ_MOVE_LAST_LINE:
2134 move_view(view, request);
2137 case REQ_SCROLL_LINE_DOWN:
2138 case REQ_SCROLL_LINE_UP:
2139 case REQ_SCROLL_PAGE_DOWN:
2140 case REQ_SCROLL_PAGE_UP:
2141 scroll_view(view, request);
2146 report("No file chosen, press %s to open tree view",
2147 get_key(REQ_VIEW_TREE));
2150 open_view(view, request, OPEN_DEFAULT);
2153 case REQ_VIEW_PAGER:
2154 if (!VIEW(REQ_VIEW_PAGER)->lines) {
2155 report("No pager content, press %s to run command from prompt",
2156 get_key(REQ_PROMPT));
2159 open_view(view, request, OPEN_DEFAULT);
2167 case REQ_VIEW_STATUS:
2168 open_view(view, request, OPEN_DEFAULT);
2173 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
2175 if ((view == VIEW(REQ_VIEW_DIFF) &&
2176 view->parent == VIEW(REQ_VIEW_MAIN)) ||
2177 (view == VIEW(REQ_VIEW_BLOB) &&
2178 view->parent == VIEW(REQ_VIEW_TREE))) {
2179 view = view->parent;
2180 move_view(view, request);
2181 if (view_is_displayed(view))
2182 update_view_title(view);
2184 move_view(view, request);
2191 report("Nothing to enter");
2194 return view->ops->enter(view, &view->line[view->lineno]);
2198 int nviews = displayed_views();
2199 int next_view = (current_view + 1) % nviews;
2201 if (next_view == current_view) {
2202 report("Only one view is displayed");
2206 current_view = next_view;
2207 /* Blur out the title of the previous view. */
2208 update_view_title(view);
2212 case REQ_TOGGLE_LINENO:
2213 opt_line_number = !opt_line_number;
2217 case REQ_TOGGLE_REV_GRAPH:
2218 opt_rev_graph = !opt_rev_graph;
2223 /* Always reload^Wrerun commands from the prompt. */
2224 open_view(view, opt_request, OPEN_RELOAD);
2228 case REQ_SEARCH_BACK:
2229 search_view(view, request);
2234 find_next(view, request);
2237 case REQ_STOP_LOADING:
2238 for (i = 0; i < ARRAY_SIZE(views); i++) {
2241 report("Stopped loading the %s view", view->name),
2246 case REQ_SHOW_VERSION:
2247 report("tig-%s (built %s)", VERSION, __DATE__);
2250 case REQ_SCREEN_RESIZE:
2253 case REQ_SCREEN_REDRAW:
2261 case REQ_VIEW_CLOSE:
2262 /* XXX: Mark closed views by letting view->parent point to the
2263 * view itself. Parents to closed view should never be
2266 view->parent->parent != view->parent) {
2267 memset(display, 0, sizeof(display));
2269 display[current_view] = view->parent;
2270 view->parent = view;
2280 /* An unknown key will show most commonly used commands. */
2281 report("Unknown key, press 'h' for help");
2294 pager_draw(struct view *view, struct line *line, unsigned int lineno, bool selected)
2296 char *text = line->data;
2297 enum line_type type = line->type;
2298 int textlen = strlen(text);
2301 wmove(view->win, lineno, 0);
2305 wchgat(view->win, -1, 0, type, NULL);
2308 attr = get_line_attr(type);
2309 wattrset(view->win, attr);
2311 if (opt_line_number || opt_tab_size < TABSIZE) {
2312 static char spaces[] = " ";
2313 int col_offset = 0, col = 0;
2315 if (opt_line_number) {
2316 unsigned long real_lineno = view->offset + lineno + 1;
2318 if (real_lineno == 1 ||
2319 (real_lineno % opt_num_interval) == 0) {
2320 wprintw(view->win, "%.*d", view->digits, real_lineno);
2323 waddnstr(view->win, spaces,
2324 MIN(view->digits, STRING_SIZE(spaces)));
2326 waddstr(view->win, ": ");
2327 col_offset = view->digits + 2;
2330 while (text && col_offset + col < view->width) {
2331 int cols_max = view->width - col_offset - col;
2335 if (*text == '\t') {
2337 assert(sizeof(spaces) > TABSIZE);
2339 cols = opt_tab_size - (col % opt_tab_size);
2342 text = strchr(text, '\t');
2343 cols = line ? text - pos : strlen(pos);
2346 waddnstr(view->win, pos, MIN(cols, cols_max));
2351 int col = 0, pos = 0;
2353 for (; pos < textlen && col < view->width; pos++, col++)
2354 if (text[pos] == '\t')
2355 col += TABSIZE - (col % TABSIZE) - 1;
2357 waddnstr(view->win, text, pos);
2364 add_describe_ref(char *buf, size_t *bufpos, char *commit_id, const char *sep)
2366 char refbuf[SIZEOF_STR];
2370 if (!string_format(refbuf, "git describe %s 2>/dev/null", commit_id))
2373 pipe = popen(refbuf, "r");
2377 if ((ref = fgets(refbuf, sizeof(refbuf), pipe)))
2378 ref = chomp_string(ref);
2384 /* This is the only fatal call, since it can "corrupt" the buffer. */
2385 if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
2392 add_pager_refs(struct view *view, struct line *line)
2394 char buf[SIZEOF_STR];
2395 char *commit_id = line->data + STRING_SIZE("commit ");
2397 size_t bufpos = 0, refpos = 0;
2398 const char *sep = "Refs: ";
2399 bool is_tag = FALSE;
2401 assert(line->type == LINE_COMMIT);
2403 refs = get_refs(commit_id);
2405 if (view == VIEW(REQ_VIEW_DIFF))
2406 goto try_add_describe_ref;
2411 struct ref *ref = refs[refpos];
2412 char *fmt = ref->tag ? "%s[%s]" :
2413 ref->remote ? "%s<%s>" : "%s%s";
2415 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
2420 } while (refs[refpos++]->next);
2422 if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
2423 try_add_describe_ref:
2424 /* Add <tag>-g<commit_id> "fake" reference. */
2425 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
2432 if (!realloc_lines(view, view->line_size + 1))
2435 add_line_text(view, buf, LINE_PP_REFS);
2439 pager_read(struct view *view, char *data)
2446 line = add_line_text(view, data, get_line_type(data));
2450 if (line->type == LINE_COMMIT &&
2451 (view == VIEW(REQ_VIEW_DIFF) ||
2452 view == VIEW(REQ_VIEW_LOG)))
2453 add_pager_refs(view, line);
2459 pager_enter(struct view *view, struct line *line)
2463 if (line->type == LINE_COMMIT &&
2464 (view == VIEW(REQ_VIEW_LOG) ||
2465 view == VIEW(REQ_VIEW_PAGER))) {
2466 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
2470 /* Always scroll the view even if it was split. That way
2471 * you can use Enter to scroll through the log view and
2472 * split open each commit diff. */
2473 scroll_view(view, REQ_SCROLL_LINE_DOWN);
2475 /* FIXME: A minor workaround. Scrolling the view will call report("")
2476 * but if we are scrolling a non-current view this won't properly
2477 * update the view title. */
2479 update_view_title(view);
2485 pager_grep(struct view *view, struct line *line)
2488 char *text = line->data;
2493 if (regexec(view->regex, text, 1, &pmatch, 0) == REG_NOMATCH)
2500 pager_select(struct view *view, struct line *line)
2502 if (line->type == LINE_COMMIT) {
2503 char *text = line->data + STRING_SIZE("commit ");
2505 if (view != VIEW(REQ_VIEW_PAGER))
2506 string_copy_rev(view->ref, text);
2507 string_copy_rev(ref_commit, text);
2511 static struct view_ops pager_ops = {
2527 help_open(struct view *view)
2530 int lines = ARRAY_SIZE(req_info) + 2;
2533 if (view->lines > 0)
2536 for (i = 0; i < ARRAY_SIZE(req_info); i++)
2537 if (!req_info[i].request)
2540 view->line = calloc(lines, sizeof(*view->line));
2544 add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
2546 for (i = 0; i < ARRAY_SIZE(req_info); i++) {
2549 if (!req_info[i].request) {
2550 add_line_text(view, "", LINE_DEFAULT);
2551 add_line_text(view, req_info[i].help, LINE_DEFAULT);
2555 key = get_key(req_info[i].request);
2556 if (!string_format(buf, " %-25s %s", key, req_info[i].help))
2559 add_line_text(view, buf, LINE_DEFAULT);
2565 static struct view_ops help_ops = {
2580 /* Parse output from git-ls-tree(1):
2582 * 100644 blob fb0e31ea6cc679b7379631188190e975f5789c26 Makefile
2583 * 100644 blob 5304ca4260aaddaee6498f9630e7d471b8591ea6 README
2584 * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
2585 * 100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38 web.conf
2588 #define SIZEOF_TREE_ATTR \
2589 STRING_SIZE("100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38\t")
2591 #define TREE_UP_FORMAT "040000 tree %s\t.."
2594 tree_compare_entry(enum line_type type1, char *name1,
2595 enum line_type type2, char *name2)
2597 if (type1 != type2) {
2598 if (type1 == LINE_TREE_DIR)
2603 return strcmp(name1, name2);
2607 tree_read(struct view *view, char *text)
2609 size_t textlen = text ? strlen(text) : 0;
2610 char buf[SIZEOF_STR];
2612 enum line_type type;
2613 bool first_read = view->lines == 0;
2615 if (textlen <= SIZEOF_TREE_ATTR)
2618 type = text[STRING_SIZE("100644 ")] == 't'
2619 ? LINE_TREE_DIR : LINE_TREE_FILE;
2622 /* Add path info line */
2623 if (!string_format(buf, "Directory path /%s", opt_path) ||
2624 !realloc_lines(view, view->line_size + 1) ||
2625 !add_line_text(view, buf, LINE_DEFAULT))
2628 /* Insert "link" to parent directory. */
2630 if (!string_format(buf, TREE_UP_FORMAT, view->ref) ||
2631 !realloc_lines(view, view->line_size + 1) ||
2632 !add_line_text(view, buf, LINE_TREE_DIR))
2637 /* Strip the path part ... */
2639 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
2640 size_t striplen = strlen(opt_path);
2641 char *path = text + SIZEOF_TREE_ATTR;
2643 if (pathlen > striplen)
2644 memmove(path, path + striplen,
2645 pathlen - striplen + 1);
2648 /* Skip "Directory ..." and ".." line. */
2649 for (pos = 1 + !!*opt_path; pos < view->lines; pos++) {
2650 struct line *line = &view->line[pos];
2651 char *path1 = ((char *) line->data) + SIZEOF_TREE_ATTR;
2652 char *path2 = text + SIZEOF_TREE_ATTR;
2653 int cmp = tree_compare_entry(line->type, path1, type, path2);
2658 text = strdup(text);
2662 if (view->lines > pos)
2663 memmove(&view->line[pos + 1], &view->line[pos],
2664 (view->lines - pos) * sizeof(*line));
2666 line = &view->line[pos];
2673 if (!add_line_text(view, text, type))
2676 /* Move the current line to the first tree entry. */
2684 tree_enter(struct view *view, struct line *line)
2686 enum open_flags flags;
2687 enum request request;
2689 switch (line->type) {
2691 /* Depending on whether it is a subdir or parent (updir?) link
2692 * mangle the path buffer. */
2693 if (line == &view->line[1] && *opt_path) {
2694 size_t path_len = strlen(opt_path);
2695 char *dirsep = opt_path + path_len - 1;
2697 while (dirsep > opt_path && dirsep[-1] != '/')
2703 size_t pathlen = strlen(opt_path);
2704 size_t origlen = pathlen;
2705 char *data = line->data;
2706 char *basename = data + SIZEOF_TREE_ATTR;
2708 if (!string_format_from(opt_path, &pathlen, "%s/", basename)) {
2709 opt_path[origlen] = 0;
2714 /* Trees and subtrees share the same ID, so they are not not
2715 * unique like blobs. */
2716 flags = OPEN_RELOAD;
2717 request = REQ_VIEW_TREE;
2720 case LINE_TREE_FILE:
2721 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
2722 request = REQ_VIEW_BLOB;
2729 open_view(view, request, flags);
2735 tree_select(struct view *view, struct line *line)
2737 char *text = line->data + STRING_SIZE("100644 blob ");
2739 if (line->type == LINE_TREE_FILE) {
2740 string_copy_rev(ref_blob, text);
2742 } else if (line->type != LINE_TREE_DIR) {
2746 string_copy_rev(view->ref, text);
2749 static struct view_ops tree_ops = {
2760 blob_read(struct view *view, char *line)
2762 return add_line_text(view, line, LINE_DEFAULT);
2765 static struct view_ops blob_ops = {
2784 char rev[SIZEOF_REV];
2788 char rev[SIZEOF_REV];
2790 char name[SIZEOF_STR];
2793 /* Get fields from the diff line:
2794 * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
2797 status_get_diff(struct status *file, char *buf, size_t bufsize)
2799 char *old_mode = buf + 1;
2800 char *new_mode = buf + 8;
2801 char *old_rev = buf + 15;
2802 char *new_rev = buf + 56;
2803 char *status = buf + 97;
2805 if (bufsize != 99 ||
2806 old_mode[-1] != ':' ||
2807 new_mode[-1] != ' ' ||
2808 old_rev[-1] != ' ' ||
2809 new_rev[-1] != ' ' ||
2813 file->status = *status;
2815 string_copy_rev(file->old.rev, old_rev);
2816 string_copy_rev(file->new.rev, new_rev);
2818 file->old.mode = strtoul(old_mode, NULL, 8);
2819 file->new.mode = strtoul(new_mode, NULL, 8);
2827 status_run(struct view *view, const char cmd[], bool diff, enum line_type type)
2829 struct status *file = NULL;
2830 char buf[SIZEOF_STR * 4];
2834 pipe = popen(cmd, "r");
2838 add_line_data(view, NULL, type);
2840 while (!feof(pipe) && !ferror(pipe)) {
2844 readsize = fread(buf + bufsize, 1, sizeof(buf) - bufsize, pipe);
2847 bufsize += readsize;
2849 /* Process while we have NUL chars. */
2850 while ((sep = memchr(buf, 0, bufsize))) {
2851 size_t sepsize = sep - buf + 1;
2854 if (!realloc_lines(view, view->line_size + 1))
2857 file = calloc(1, sizeof(*file));
2861 add_line_data(view, file, type);
2864 /* Parse diff info part. */
2868 } else if (!file->status) {
2869 if (!status_get_diff(file, buf, sepsize))
2873 memmove(buf, sep + 1, bufsize);
2875 sep = memchr(buf, 0, bufsize);
2878 sepsize = sep - buf + 1;
2881 /* git-ls-files just delivers a NUL separated
2882 * list of file names similar to the second half
2883 * of the git-diff-* output. */
2884 string_ncopy(file->name, buf, sepsize);
2886 memmove(buf, sep + 1, bufsize);
2897 if (!view->line[view->lines - 1].data)
2898 add_line_data(view, NULL, LINE_STAT_NONE);
2904 #define STATUS_DIFF_INDEX_CMD "git diff-index -z --cached HEAD"
2905 #define STATUS_DIFF_FILES_CMD "git diff-files -z"
2906 #define STATUS_LIST_OTHER_CMD \
2907 "_git_exclude=$(git rev-parse --git-dir)/info/exclude;" \
2908 "test -f \"$_git_exclude\" && exclude=\"--exclude-from=$_git_exclude\";" \
2909 "git ls-files -z --others --exclude-per-directory=.gitignore \"$exclude\"" \
2911 /* First parse staged info using git-diff-index(1), then parse unstaged
2912 * info using git-diff-files(1), and finally untracked files using
2913 * git-ls-files(1). */
2915 status_open(struct view *view)
2919 for (i = 0; i < view->lines; i++)
2920 free(view->line[i].data);
2922 view->lines = view->line_size = 0;
2925 if (!realloc_lines(view, view->line_size + 6))
2928 if (!status_run(view, STATUS_DIFF_INDEX_CMD, TRUE, LINE_STAT_STAGED) ||
2929 !status_run(view, STATUS_DIFF_FILES_CMD, TRUE, LINE_STAT_UNSTAGED) ||
2930 !status_run(view, STATUS_LIST_OTHER_CMD, FALSE, LINE_STAT_UNTRACKED))
2937 status_draw(struct view *view, struct line *line, unsigned int lineno, bool selected)
2939 struct status *status = line->data;
2941 wmove(view->win, lineno, 0);
2944 wattrset(view->win, get_line_attr(LINE_CURSOR));
2945 wchgat(view->win, -1, 0, LINE_CURSOR, NULL);
2947 } else if (!status && line->type != LINE_STAT_NONE) {
2948 wattrset(view->win, get_line_attr(LINE_STAT_SECTION));
2949 wchgat(view->win, -1, 0, LINE_STAT_SECTION, NULL);
2952 wattrset(view->win, get_line_attr(line->type));
2958 switch (line->type) {
2959 case LINE_STAT_STAGED:
2960 text = "Changes to be committed:";
2963 case LINE_STAT_UNSTAGED:
2964 text = "Changed but not updated:";
2967 case LINE_STAT_UNTRACKED:
2968 text = "Untracked files:";
2971 case LINE_STAT_NONE:
2972 text = " (no files)";
2979 waddstr(view->win, text);
2983 waddch(view->win, status->status);
2985 wattrset(view->win, A_NORMAL);
2986 wmove(view->win, lineno, 4);
2987 waddstr(view->win, status->name);
2993 status_enter(struct view *view, struct line *line)
2995 struct status *status = line->data;
2996 char cmd[SIZEOF_STR];
2997 char buf[SIZEOF_STR];
3007 line->type != LINE_STAT_UNTRACKED &&
3008 !string_format_from(cmd, &cmdsize, "cd %s;", opt_cdup))
3011 switch (line->type) {
3012 case LINE_STAT_STAGED:
3013 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
3019 string_add(cmd, cmdsize, "git update-index -z --index-info");
3022 case LINE_STAT_UNSTAGED:
3023 case LINE_STAT_UNTRACKED:
3024 if (!string_format_from(buf, &bufsize, "%s%c", status->name, 0))
3027 string_add(cmd, cmdsize, "git update-index -z --add --remove --stdin");
3034 pipe = popen(cmd, "w");
3038 while (!ferror(pipe) && written < bufsize) {
3039 written += fwrite(buf + written, 1, bufsize - written, pipe);
3044 if (written != bufsize)
3047 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
3052 status_select(struct view *view, struct line *line)
3056 switch (line->type) {
3057 case LINE_STAT_STAGED:
3058 text = "Press Enter to unstage file for commit";
3061 case LINE_STAT_UNSTAGED:
3062 text = "Press Enter to stage file for commit ";
3065 case LINE_STAT_UNTRACKED:
3066 text = "Press Enter to stage file for addition";
3069 case LINE_STAT_NONE:
3076 string_ncopy(view->ref, text, strlen(text));
3080 status_grep(struct view *view, struct line *line)
3082 struct status *status = line->data;
3083 enum { S_STATUS, S_NAME, S_END } state;
3090 for (state = S_STATUS; state < S_END; state++) {
3094 case S_NAME: text = status->name; break;
3096 buf[0] = status->status;
3104 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
3111 static struct view_ops status_ops = {
3127 char id[SIZEOF_REV]; /* SHA1 ID. */
3128 char title[128]; /* First line of the commit message. */
3129 char author[75]; /* Author of the commit. */
3130 struct tm time; /* Date from the author ident. */
3131 struct ref **refs; /* Repository references. */
3132 chtype graph[SIZEOF_REVGRAPH]; /* Ancestry chain graphics. */
3133 size_t graph_size; /* The width of the graph array. */
3136 /* Size of rev graph with no "padding" columns */
3137 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
3140 struct rev_graph *prev, *next, *parents;
3141 char rev[SIZEOF_REVITEMS][SIZEOF_REV];
3143 struct commit *commit;
3147 /* Parents of the commit being visualized. */
3148 static struct rev_graph graph_parents[4];
3150 /* The current stack of revisions on the graph. */
3151 static struct rev_graph graph_stacks[4] = {
3152 { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
3153 { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
3154 { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
3155 { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
3159 graph_parent_is_merge(struct rev_graph *graph)
3161 return graph->parents->size > 1;
3165 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
3167 struct commit *commit = graph->commit;
3169 if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
3170 commit->graph[commit->graph_size++] = symbol;
3174 done_rev_graph(struct rev_graph *graph)
3176 if (graph_parent_is_merge(graph) &&
3177 graph->pos < graph->size - 1 &&
3178 graph->next->size == graph->size + graph->parents->size - 1) {
3179 size_t i = graph->pos + graph->parents->size - 1;
3181 graph->commit->graph_size = i * 2;
3182 while (i < graph->next->size - 1) {
3183 append_to_rev_graph(graph, ' ');
3184 append_to_rev_graph(graph, '\\');
3189 graph->size = graph->pos = 0;
3190 graph->commit = NULL;
3191 memset(graph->parents, 0, sizeof(*graph->parents));
3195 push_rev_graph(struct rev_graph *graph, char *parent)
3199 /* "Collapse" duplicate parents lines.
3201 * FIXME: This needs to also update update the drawn graph but
3202 * for now it just serves as a method for pruning graph lines. */
3203 for (i = 0; i < graph->size; i++)
3204 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
3207 if (graph->size < SIZEOF_REVITEMS) {
3208 string_ncopy(graph->rev[graph->size++], parent, SIZEOF_REV);
3213 get_rev_graph_symbol(struct rev_graph *graph)
3217 if (graph->parents->size == 0)
3218 symbol = REVGRAPH_INIT;
3219 else if (graph_parent_is_merge(graph))
3220 symbol = REVGRAPH_MERGE;
3221 else if (graph->pos >= graph->size)
3222 symbol = REVGRAPH_BRANCH;
3224 symbol = REVGRAPH_COMMIT;
3230 draw_rev_graph(struct rev_graph *graph)
3233 chtype separator, line;
3235 enum { DEFAULT, RSHARP, RDIAG, LDIAG };
3236 static struct rev_filler fillers[] = {
3237 { ' ', REVGRAPH_LINE },
3242 chtype symbol = get_rev_graph_symbol(graph);
3243 struct rev_filler *filler;
3246 filler = &fillers[DEFAULT];
3248 for (i = 0; i < graph->pos; i++) {
3249 append_to_rev_graph(graph, filler->line);
3250 if (graph_parent_is_merge(graph->prev) &&
3251 graph->prev->pos == i)
3252 filler = &fillers[RSHARP];
3254 append_to_rev_graph(graph, filler->separator);
3257 /* Place the symbol for this revision. */
3258 append_to_rev_graph(graph, symbol);
3260 if (graph->prev->size > graph->size)
3261 filler = &fillers[RDIAG];
3263 filler = &fillers[DEFAULT];
3267 for (; i < graph->size; i++) {
3268 append_to_rev_graph(graph, filler->separator);
3269 append_to_rev_graph(graph, filler->line);
3270 if (graph_parent_is_merge(graph->prev) &&
3271 i < graph->prev->pos + graph->parents->size)
3272 filler = &fillers[RSHARP];
3273 if (graph->prev->size > graph->size)
3274 filler = &fillers[LDIAG];
3277 if (graph->prev->size > graph->size) {
3278 append_to_rev_graph(graph, filler->separator);
3279 if (filler->line != ' ')
3280 append_to_rev_graph(graph, filler->line);
3284 /* Prepare the next rev graph */
3286 prepare_rev_graph(struct rev_graph *graph)
3290 /* First, traverse all lines of revisions up to the active one. */
3291 for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
3292 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
3295 push_rev_graph(graph->next, graph->rev[graph->pos]);
3298 /* Interleave the new revision parent(s). */
3299 for (i = 0; i < graph->parents->size; i++)
3300 push_rev_graph(graph->next, graph->parents->rev[i]);
3302 /* Lastly, put any remaining revisions. */
3303 for (i = graph->pos + 1; i < graph->size; i++)
3304 push_rev_graph(graph->next, graph->rev[i]);
3308 update_rev_graph(struct rev_graph *graph)
3310 /* If this is the finalizing update ... */
3312 prepare_rev_graph(graph);
3314 /* Graph visualization needs a one rev look-ahead,
3315 * so the first update doesn't visualize anything. */
3316 if (!graph->prev->commit)
3319 draw_rev_graph(graph->prev);
3320 done_rev_graph(graph->prev->prev);
3329 main_draw(struct view *view, struct line *line, unsigned int lineno, bool selected)
3331 char buf[DATE_COLS + 1];
3332 struct commit *commit = line->data;
3333 enum line_type type;
3339 if (!*commit->author)
3342 wmove(view->win, lineno, col);
3346 wattrset(view->win, get_line_attr(type));
3347 wchgat(view->win, -1, 0, type, NULL);
3350 type = LINE_MAIN_COMMIT;
3351 wattrset(view->win, get_line_attr(LINE_MAIN_DATE));
3354 timelen = strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time);
3355 waddnstr(view->win, buf, timelen);
3356 waddstr(view->win, " ");
3359 wmove(view->win, lineno, col);
3360 if (type != LINE_CURSOR)
3361 wattrset(view->win, get_line_attr(LINE_MAIN_AUTHOR));
3364 authorlen = utf8_length(commit->author, AUTHOR_COLS - 2, &col, &trimmed);
3366 authorlen = strlen(commit->author);
3367 if (authorlen > AUTHOR_COLS - 2) {
3368 authorlen = AUTHOR_COLS - 2;
3374 waddnstr(view->win, commit->author, authorlen);
3375 if (type != LINE_CURSOR)
3376 wattrset(view->win, get_line_attr(LINE_MAIN_DELIM));
3377 waddch(view->win, '~');
3379 waddstr(view->win, commit->author);
3383 if (type != LINE_CURSOR)
3384 wattrset(view->win, A_NORMAL);
3386 if (opt_rev_graph && commit->graph_size) {
3389 wmove(view->win, lineno, col);
3390 /* Using waddch() instead of waddnstr() ensures that
3391 * they'll be rendered correctly for the cursor line. */
3392 for (i = 0; i < commit->graph_size; i++)
3393 waddch(view->win, commit->graph[i]);
3395 waddch(view->win, ' ');
3396 col += commit->graph_size + 1;
3399 wmove(view->win, lineno, col);
3405 if (type == LINE_CURSOR)
3407 else if (commit->refs[i]->tag)
3408 wattrset(view->win, get_line_attr(LINE_MAIN_TAG));
3409 else if (commit->refs[i]->remote)
3410 wattrset(view->win, get_line_attr(LINE_MAIN_REMOTE));
3412 wattrset(view->win, get_line_attr(LINE_MAIN_REF));
3413 waddstr(view->win, "[");
3414 waddstr(view->win, commit->refs[i]->name);
3415 waddstr(view->win, "]");
3416 if (type != LINE_CURSOR)
3417 wattrset(view->win, A_NORMAL);
3418 waddstr(view->win, " ");
3419 col += strlen(commit->refs[i]->name) + STRING_SIZE("[] ");
3420 } while (commit->refs[i++]->next);
3423 if (type != LINE_CURSOR)
3424 wattrset(view->win, get_line_attr(type));
3427 int titlelen = strlen(commit->title);
3429 if (col + titlelen > view->width)
3430 titlelen = view->width - col;
3432 waddnstr(view->win, commit->title, titlelen);
3438 /* Reads git log --pretty=raw output and parses it into the commit struct. */
3440 main_read(struct view *view, char *line)
3442 static struct rev_graph *graph = graph_stacks;
3443 enum line_type type;
3444 struct commit *commit;
3447 update_rev_graph(graph);
3451 type = get_line_type(line);
3452 if (type == LINE_COMMIT) {
3453 commit = calloc(1, sizeof(struct commit));
3457 string_copy_rev(commit->id, line + STRING_SIZE("commit "));
3458 commit->refs = get_refs(commit->id);
3459 graph->commit = commit;
3460 add_line_data(view, commit, LINE_MAIN_COMMIT);
3466 commit = view->line[view->lines - 1].data;
3470 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
3475 /* Parse author lines where the name may be empty:
3476 * author <email@address.tld> 1138474660 +0100
3478 char *ident = line + STRING_SIZE("author ");
3479 char *nameend = strchr(ident, '<');
3480 char *emailend = strchr(ident, '>');
3482 if (!nameend || !emailend)
3485 update_rev_graph(graph);
3486 graph = graph->next;
3488 *nameend = *emailend = 0;
3489 ident = chomp_string(ident);
3491 ident = chomp_string(nameend + 1);
3496 string_copy(commit->author, ident);
3498 /* Parse epoch and timezone */
3499 if (emailend[1] == ' ') {
3500 char *secs = emailend + 2;
3501 char *zone = strchr(secs, ' ');
3502 time_t time = (time_t) atol(secs);
3504 if (zone && strlen(zone) == STRING_SIZE(" +0700")) {
3508 tz = ('0' - zone[1]) * 60 * 60 * 10;
3509 tz += ('0' - zone[2]) * 60 * 60;
3510 tz += ('0' - zone[3]) * 60;
3511 tz += ('0' - zone[4]) * 60;
3519 gmtime_r(&time, &commit->time);
3524 /* Fill in the commit title if it has not already been set. */
3525 if (commit->title[0])
3528 /* Require titles to start with a non-space character at the
3529 * offset used by git log. */
3530 if (strncmp(line, " ", 4))
3533 /* Well, if the title starts with a whitespace character,
3534 * try to be forgiving. Otherwise we end up with no title. */
3535 while (isspace(*line))
3539 /* FIXME: More graceful handling of titles; append "..." to
3540 * shortened titles, etc. */
3542 string_copy(commit->title, line);
3549 main_enter(struct view *view, struct line *line)
3551 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
3553 open_view(view, REQ_VIEW_DIFF, flags);
3558 main_grep(struct view *view, struct line *line)
3560 struct commit *commit = line->data;
3561 enum { S_TITLE, S_AUTHOR, S_DATE, S_END } state;
3562 char buf[DATE_COLS + 1];
3565 for (state = S_TITLE; state < S_END; state++) {
3569 case S_TITLE: text = commit->title; break;
3570 case S_AUTHOR: text = commit->author; break;
3572 if (!strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time))
3581 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
3589 main_select(struct view *view, struct line *line)
3591 struct commit *commit = line->data;
3593 string_copy_rev(view->ref, commit->id);
3594 string_copy_rev(ref_commit, view->ref);
3597 static struct view_ops main_ops = {
3609 * Unicode / UTF-8 handling
3611 * NOTE: Much of the following code for dealing with unicode is derived from
3612 * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
3613 * src/intl/charset.c from the utf8 branch commit elinks-0.11.0-g31f2c28.
3616 /* I've (over)annotated a lot of code snippets because I am not entirely
3617 * confident that the approach taken by this small UTF-8 interface is correct.
3621 unicode_width(unsigned long c)
3624 (c <= 0x115f /* Hangul Jamo */
3627 || (c >= 0x2e80 && c <= 0xa4cf && c != 0x303f)
3629 || (c >= 0xac00 && c <= 0xd7a3) /* Hangul Syllables */
3630 || (c >= 0xf900 && c <= 0xfaff) /* CJK Compatibility Ideographs */
3631 || (c >= 0xfe30 && c <= 0xfe6f) /* CJK Compatibility Forms */
3632 || (c >= 0xff00 && c <= 0xff60) /* Fullwidth Forms */
3633 || (c >= 0xffe0 && c <= 0xffe6)
3634 || (c >= 0x20000 && c <= 0x2fffd)
3635 || (c >= 0x30000 && c <= 0x3fffd)))
3641 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
3642 * Illegal bytes are set one. */
3643 static const unsigned char utf8_bytes[256] = {
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 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,
3650 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,
3651 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,
3654 /* Decode UTF-8 multi-byte representation into a unicode character. */
3655 static inline unsigned long
3656 utf8_to_unicode(const char *string, size_t length)
3658 unsigned long unicode;
3662 unicode = string[0];
3665 unicode = (string[0] & 0x1f) << 6;
3666 unicode += (string[1] & 0x3f);
3669 unicode = (string[0] & 0x0f) << 12;
3670 unicode += ((string[1] & 0x3f) << 6);
3671 unicode += (string[2] & 0x3f);
3674 unicode = (string[0] & 0x0f) << 18;
3675 unicode += ((string[1] & 0x3f) << 12);
3676 unicode += ((string[2] & 0x3f) << 6);
3677 unicode += (string[3] & 0x3f);
3680 unicode = (string[0] & 0x0f) << 24;
3681 unicode += ((string[1] & 0x3f) << 18);
3682 unicode += ((string[2] & 0x3f) << 12);
3683 unicode += ((string[3] & 0x3f) << 6);
3684 unicode += (string[4] & 0x3f);
3687 unicode = (string[0] & 0x01) << 30;
3688 unicode += ((string[1] & 0x3f) << 24);
3689 unicode += ((string[2] & 0x3f) << 18);
3690 unicode += ((string[3] & 0x3f) << 12);
3691 unicode += ((string[4] & 0x3f) << 6);
3692 unicode += (string[5] & 0x3f);
3695 die("Invalid unicode length");
3698 /* Invalid characters could return the special 0xfffd value but NUL
3699 * should be just as good. */
3700 return unicode > 0xffff ? 0 : unicode;
3703 /* Calculates how much of string can be shown within the given maximum width
3704 * and sets trimmed parameter to non-zero value if all of string could not be
3707 * Additionally, adds to coloffset how many many columns to move to align with
3708 * the expected position. Takes into account how multi-byte and double-width
3709 * characters will effect the cursor position.
3711 * Returns the number of bytes to output from string to satisfy max_width. */
3713 utf8_length(const char *string, size_t max_width, int *coloffset, int *trimmed)
3715 const char *start = string;
3716 const char *end = strchr(string, '\0');
3722 while (string < end) {
3723 int c = *(unsigned char *) string;
3724 unsigned char bytes = utf8_bytes[c];
3726 unsigned long unicode;
3728 if (string + bytes > end)
3731 /* Change representation to figure out whether
3732 * it is a single- or double-width character. */
3734 unicode = utf8_to_unicode(string, bytes);
3735 /* FIXME: Graceful handling of invalid unicode character. */
3739 ucwidth = unicode_width(unicode);
3741 if (width > max_width) {
3746 /* The column offset collects the differences between the
3747 * number of bytes encoding a character and the number of
3748 * columns will be used for rendering said character.
3750 * So if some character A is encoded in 2 bytes, but will be
3751 * represented on the screen using only 1 byte this will and up
3752 * adding 1 to the multi-byte column offset.
3754 * Assumes that no double-width character can be encoding in
3755 * less than two bytes. */
3756 if (bytes > ucwidth)
3757 mbwidth += bytes - ucwidth;
3762 *coloffset += mbwidth;
3764 return string - start;
3772 /* Whether or not the curses interface has been initialized. */
3773 static bool cursed = FALSE;
3775 /* The status window is used for polling keystrokes. */
3776 static WINDOW *status_win;
3778 static bool status_empty = TRUE;
3780 /* Update status and title window. */
3782 report(const char *msg, ...)
3784 struct view *view = display[current_view];
3789 if (!status_empty || *msg) {
3792 va_start(args, msg);
3794 wmove(status_win, 0, 0);
3796 vwprintw(status_win, msg, args);
3797 status_empty = FALSE;
3799 status_empty = TRUE;
3801 wclrtoeol(status_win);
3802 wrefresh(status_win);
3807 update_view_title(view);
3808 update_display_cursor(view);
3811 /* Controls when nodelay should be in effect when polling user input. */
3813 set_nonblocking_input(bool loading)
3815 static unsigned int loading_views;
3817 if ((loading == FALSE && loading_views-- == 1) ||
3818 (loading == TRUE && loading_views++ == 0))
3819 nodelay(status_win, loading);
3827 /* Initialize the curses library */
3828 if (isatty(STDIN_FILENO)) {
3829 cursed = !!initscr();
3831 /* Leave stdin and stdout alone when acting as a pager. */
3832 FILE *io = fopen("/dev/tty", "r+");
3835 die("Failed to open /dev/tty");
3836 cursed = !!newterm(NULL, io, io);
3840 die("Failed to initialize curses");
3842 nonl(); /* Tell curses not to do NL->CR/NL on output */
3843 cbreak(); /* Take input chars one at a time, no wait for \n */
3844 noecho(); /* Don't echo input */
3845 leaveok(stdscr, TRUE);
3850 getmaxyx(stdscr, y, x);
3851 status_win = newwin(1, 0, y - 1, 0);
3853 die("Failed to create status window");
3855 /* Enable keyboard mapping */
3856 keypad(status_win, TRUE);
3857 wbkgdset(status_win, get_line_attr(LINE_STATUS));
3861 read_prompt(const char *prompt)
3863 enum { READING, STOP, CANCEL } status = READING;
3864 static char buf[sizeof(opt_cmd) - STRING_SIZE("git \0")];
3867 while (status == READING) {
3873 foreach_view (view, i)
3878 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
3879 wclrtoeol(status_win);
3881 /* Refresh, accept single keystroke of input */
3882 key = wgetch(status_win);
3887 status = pos ? STOP : CANCEL;
3905 if (pos >= sizeof(buf)) {
3906 report("Input string too long");
3911 buf[pos++] = (char) key;
3915 /* Clear the status window */
3916 status_empty = FALSE;
3919 if (status == CANCEL)
3928 * Repository references
3931 static struct ref *refs;
3932 static size_t refs_size;
3934 /* Id <-> ref store */
3935 static struct ref ***id_refs;
3936 static size_t id_refs_size;
3938 static struct ref **
3941 struct ref ***tmp_id_refs;
3942 struct ref **ref_list = NULL;
3943 size_t ref_list_size = 0;
3946 for (i = 0; i < id_refs_size; i++)
3947 if (!strcmp(id, id_refs[i][0]->id))
3950 tmp_id_refs = realloc(id_refs, (id_refs_size + 1) * sizeof(*id_refs));
3954 id_refs = tmp_id_refs;
3956 for (i = 0; i < refs_size; i++) {
3959 if (strcmp(id, refs[i].id))
3962 tmp = realloc(ref_list, (ref_list_size + 1) * sizeof(*ref_list));
3970 if (ref_list_size > 0)
3971 ref_list[ref_list_size - 1]->next = 1;
3972 ref_list[ref_list_size] = &refs[i];
3974 /* XXX: The properties of the commit chains ensures that we can
3975 * safely modify the shared ref. The repo references will
3976 * always be similar for the same id. */
3977 ref_list[ref_list_size]->next = 0;
3982 id_refs[id_refs_size++] = ref_list;
3988 read_ref(char *id, int idlen, char *name, int namelen)
3992 bool remote = FALSE;
3994 if (!strncmp(name, "refs/tags/", STRING_SIZE("refs/tags/"))) {
3995 /* Commits referenced by tags has "^{}" appended. */
3996 if (name[namelen - 1] != '}')
3999 while (namelen > 0 && name[namelen] != '^')
4003 namelen -= STRING_SIZE("refs/tags/");
4004 name += STRING_SIZE("refs/tags/");
4006 } else if (!strncmp(name, "refs/remotes/", STRING_SIZE("refs/remotes/"))) {
4008 namelen -= STRING_SIZE("refs/remotes/");
4009 name += STRING_SIZE("refs/remotes/");
4011 } else if (!strncmp(name, "refs/heads/", STRING_SIZE("refs/heads/"))) {
4012 namelen -= STRING_SIZE("refs/heads/");
4013 name += STRING_SIZE("refs/heads/");
4015 } else if (!strcmp(name, "HEAD")) {
4019 refs = realloc(refs, sizeof(*refs) * (refs_size + 1));
4023 ref = &refs[refs_size++];
4024 ref->name = malloc(namelen + 1);
4028 strncpy(ref->name, name, namelen);
4029 ref->name[namelen] = 0;
4031 ref->remote = remote;
4032 string_copy_rev(ref->id, id);
4040 const char *cmd_env = getenv("TIG_LS_REMOTE");
4041 const char *cmd = cmd_env && *cmd_env ? cmd_env : TIG_LS_REMOTE;
4043 return read_properties(popen(cmd, "r"), "\t", read_ref);
4047 read_repo_config_option(char *name, int namelen, char *value, int valuelen)
4049 if (!strcmp(name, "i18n.commitencoding"))
4050 string_copy(opt_encoding, value);
4056 load_repo_config(void)
4058 return read_properties(popen("git repo-config --list", "r"),
4059 "=", read_repo_config_option);
4063 read_repo_info(char *name, int namelen, char *value, int valuelen)
4066 string_copy(opt_cdup, name);
4071 load_repo_info(void)
4073 return read_properties(popen("git rev-parse --show-cdup", "r"),
4074 "=", read_repo_info);
4078 read_properties(FILE *pipe, const char *separators,
4079 int (*read_property)(char *, int, char *, int))
4081 char buffer[BUFSIZ];
4088 while (state == OK && (name = fgets(buffer, sizeof(buffer), pipe))) {
4093 name = chomp_string(name);
4094 namelen = strcspn(name, separators);
4096 if (name[namelen]) {
4098 value = chomp_string(name + namelen + 1);
4099 valuelen = strlen(value);
4106 state = read_property(name, namelen, value, valuelen);
4109 if (state != ERR && ferror(pipe))
4122 static void __NORETURN
4125 /* XXX: Restore tty modes and let the OS cleanup the rest! */
4131 static void __NORETURN
4132 die(const char *err, ...)
4138 va_start(args, err);
4139 fputs("tig: ", stderr);
4140 vfprintf(stderr, err, args);
4141 fputs("\n", stderr);
4148 main(int argc, char *argv[])
4151 enum request request;
4154 signal(SIGINT, quit);
4156 if (setlocale(LC_ALL, "")) {
4157 string_copy(opt_codeset, nl_langinfo(CODESET));
4160 if (load_options() == ERR)
4161 die("Failed to load user config.");
4163 /* Load the repo config file so options can be overwritten from
4164 * the command line. */
4165 if (load_repo_config() == ERR)
4166 die("Failed to load repo config.");
4168 if (load_repo_info() == ERR)
4169 die("Failed to load repo info.");
4171 if (!parse_options(argc, argv))
4174 if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
4175 opt_iconv = iconv_open(opt_codeset, opt_encoding);
4176 if (opt_iconv == ICONV_NONE)
4177 die("Failed to initialize character set conversion");
4180 if (load_refs() == ERR)
4181 die("Failed to load refs.");
4183 /* Require a git repository unless when running in pager mode. */
4184 if (refs_size == 0 && opt_request != REQ_VIEW_PAGER)
4185 die("Not a git repository");
4187 for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
4188 view->cmd_env = getenv(view->cmd_env);
4190 request = opt_request;
4194 while (view_driver(display[current_view], request)) {
4198 foreach_view (view, i)
4201 /* Refresh, accept single keystroke of input */
4202 key = wgetch(status_win);
4204 /* wgetch() with nodelay() enabled returns ERR when there's no
4211 request = get_keybinding(display[current_view]->keymap, key);
4213 /* Some low-level request handling. This keeps access to
4214 * status_win restricted. */
4218 char *cmd = read_prompt(":");
4220 if (cmd && string_format(opt_cmd, "git %s", cmd)) {
4221 if (strncmp(cmd, "show", 4) && isspace(cmd[4])) {
4222 opt_request = REQ_VIEW_DIFF;
4224 opt_request = REQ_VIEW_PAGER;
4233 case REQ_SEARCH_BACK:
4235 const char *prompt = request == REQ_SEARCH
4237 char *search = read_prompt(prompt);
4240 string_copy(opt_search, search);
4245 case REQ_SCREEN_RESIZE:
4249 getmaxyx(stdscr, height, width);
4251 /* Resize the status view and let the view driver take
4252 * care of resizing the displayed views. */
4253 wresize(status_win, 1, width);
4254 mvwin(status_win, height - 1, 0);
4255 wrefresh(status_win);