1 /* Copyright (c) 2006-2007 Jonas Fonseca <fonseca@diku.dk>
3 * This program is free software; you can redistribute it and/or
4 * modify it under the terms of the GNU General Public License as
5 * published by the Free Software Foundation; either version 2 of
6 * the License, or (at your option) any later version.
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
19 #define TIG_VERSION "unknown-version"
34 #include <sys/types.h>
45 /* ncurses(3): Must be defined to have extended wide-character functions. */
46 #define _XOPEN_SOURCE_EXTENDED
51 #define __NORETURN __attribute__((__noreturn__))
56 static void __NORETURN die(const char *err, ...);
57 static void warn(const char *msg, ...);
58 static void report(const char *msg, ...);
59 static int read_properties(FILE *pipe, const char *separators, int (*read)(char *, size_t, char *, size_t));
60 static void set_nonblocking_input(bool loading);
61 static size_t utf8_length(const char *string, size_t max_width, int *trimmed, bool reserve);
63 #define ABS(x) ((x) >= 0 ? (x) : -(x))
64 #define MIN(x, y) ((x) < (y) ? (x) : (y))
66 #define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0]))
67 #define STRING_SIZE(x) (sizeof(x) - 1)
69 #define SIZEOF_STR 1024 /* Default string size. */
70 #define SIZEOF_REF 256 /* Size of symbolic or SHA1 ID. */
71 #define SIZEOF_REV 41 /* Holds a SHA-1 and an ending NUL */
75 #define REVGRAPH_INIT 'I'
76 #define REVGRAPH_MERGE 'M'
77 #define REVGRAPH_BRANCH '+'
78 #define REVGRAPH_COMMIT '*'
79 #define REVGRAPH_BOUND '^'
80 #define REVGRAPH_LINE '|'
82 #define SIZEOF_REVGRAPH 19 /* Size of revision ancestry graphics. */
84 /* This color name can be used to refer to the default term colors. */
85 #define COLOR_DEFAULT (-1)
87 #define ICONV_NONE ((iconv_t) -1)
89 #define ICONV_CONST /* nothing */
92 /* The format and size of the date column in the main view. */
93 #define DATE_FORMAT "%Y-%m-%d %H:%M"
94 #define DATE_COLS STRING_SIZE("2006-04-29 14:21 ")
96 #define AUTHOR_COLS 20
98 /* The default interval between line numbers. */
99 #define NUMBER_INTERVAL 1
103 #define SCALE_SPLIT_VIEW(height) ((height) * 2 / 3)
106 #define GIT_CONFIG "git config"
109 #define TIG_LS_REMOTE \
110 "git ls-remote $(git rev-parse --git-dir) 2>/dev/null"
112 #define TIG_DIFF_CMD \
113 "git show --pretty=fuller --no-color --root --patch-with-stat --find-copies-harder -C %s 2>/dev/null"
115 #define TIG_LOG_CMD \
116 "git log --no-color --cc --stat -n100 %s 2>/dev/null"
118 #define TIG_MAIN_CMD \
119 "git log --no-color --topo-order --parents --boundary --pretty=raw %s 2>/dev/null"
121 #define TIG_TREE_CMD \
124 #define TIG_BLOB_CMD \
125 "git cat-file blob %s"
127 /* XXX: Needs to be defined to the empty string. */
128 #define TIG_HELP_CMD ""
129 #define TIG_PAGER_CMD ""
130 #define TIG_STATUS_CMD ""
131 #define TIG_STAGE_CMD ""
133 /* Some ascii-shorthands fitted into the ncurses namespace. */
135 #define KEY_RETURN '\r'
140 char *name; /* Ref name; tag or head names are shortened. */
141 char id[SIZEOF_REV]; /* Commit SHA1 ID */
142 unsigned int tag:1; /* Is it a tag? */
143 unsigned int ltag:1; /* If so, is the tag local? */
144 unsigned int remote:1; /* Is it a remote ref? */
145 unsigned int next:1; /* For ref lists: are there more refs? */
148 static struct ref **get_refs(char *id);
157 set_from_int_map(struct int_map *map, size_t map_size,
158 int *value, const char *name, int namelen)
163 for (i = 0; i < map_size; i++)
164 if (namelen == map[i].namelen &&
165 !strncasecmp(name, map[i].name, namelen)) {
166 *value = map[i].value;
179 string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t srclen)
181 if (srclen > dstlen - 1)
184 strncpy(dst, src, srclen);
188 /* Shorthands for safely copying into a fixed buffer. */
190 #define string_copy(dst, src) \
191 string_ncopy_do(dst, sizeof(dst), src, sizeof(src))
193 #define string_ncopy(dst, src, srclen) \
194 string_ncopy_do(dst, sizeof(dst), src, srclen)
196 #define string_copy_rev(dst, src) \
197 string_ncopy_do(dst, SIZEOF_REV, src, SIZEOF_REV - 1)
199 #define string_add(dst, from, src) \
200 string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src))
203 chomp_string(char *name)
207 while (isspace(*name))
210 namelen = strlen(name) - 1;
211 while (namelen > 0 && isspace(name[namelen]))
218 string_nformat(char *buf, size_t bufsize, size_t *bufpos, const char *fmt, ...)
221 size_t pos = bufpos ? *bufpos : 0;
224 pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
230 return pos >= bufsize ? FALSE : TRUE;
233 #define string_format(buf, fmt, args...) \
234 string_nformat(buf, sizeof(buf), NULL, fmt, args)
236 #define string_format_from(buf, from, fmt, args...) \
237 string_nformat(buf, sizeof(buf), from, fmt, args)
240 string_enum_compare(const char *str1, const char *str2, int len)
244 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
246 /* Diff-Header == DIFF_HEADER */
247 for (i = 0; i < len; i++) {
248 if (toupper(str1[i]) == toupper(str2[i]))
251 if (string_enum_sep(str1[i]) &&
252 string_enum_sep(str2[i]))
255 return str1[i] - str2[i];
263 * NOTE: The following is a slightly modified copy of the git project's shell
264 * quoting routines found in the quote.c file.
266 * Help to copy the thing properly quoted for the shell safety. any single
267 * quote is replaced with '\'', any exclamation point is replaced with '\!',
268 * and the whole thing is enclosed in a
271 * original sq_quote result
272 * name ==> name ==> 'name'
273 * a b ==> a b ==> 'a b'
274 * a'b ==> a'\''b ==> 'a'\''b'
275 * a!b ==> a'\!'b ==> 'a'\!'b'
279 sq_quote(char buf[SIZEOF_STR], size_t bufsize, const char *src)
283 #define BUFPUT(x) do { if (bufsize < SIZEOF_STR) buf[bufsize++] = (x); } while (0)
286 while ((c = *src++)) {
287 if (c == '\'' || c == '!') {
298 if (bufsize < SIZEOF_STR)
310 /* XXX: Keep the view request first and in sync with views[]. */ \
311 REQ_GROUP("View switching") \
312 REQ_(VIEW_MAIN, "Show main view"), \
313 REQ_(VIEW_DIFF, "Show diff view"), \
314 REQ_(VIEW_LOG, "Show log view"), \
315 REQ_(VIEW_TREE, "Show tree view"), \
316 REQ_(VIEW_BLOB, "Show blob view"), \
317 REQ_(VIEW_HELP, "Show help page"), \
318 REQ_(VIEW_PAGER, "Show pager view"), \
319 REQ_(VIEW_STATUS, "Show status view"), \
320 REQ_(VIEW_STAGE, "Show stage view"), \
322 REQ_GROUP("View manipulation") \
323 REQ_(ENTER, "Enter current line and scroll"), \
324 REQ_(NEXT, "Move to next"), \
325 REQ_(PREVIOUS, "Move to previous"), \
326 REQ_(VIEW_NEXT, "Move focus to next view"), \
327 REQ_(REFRESH, "Reload and refresh"), \
328 REQ_(VIEW_CLOSE, "Close the current view"), \
329 REQ_(QUIT, "Close all views and quit"), \
331 REQ_GROUP("Cursor navigation") \
332 REQ_(MOVE_UP, "Move cursor one line up"), \
333 REQ_(MOVE_DOWN, "Move cursor one line down"), \
334 REQ_(MOVE_PAGE_DOWN, "Move cursor one page down"), \
335 REQ_(MOVE_PAGE_UP, "Move cursor one page up"), \
336 REQ_(MOVE_FIRST_LINE, "Move cursor to first line"), \
337 REQ_(MOVE_LAST_LINE, "Move cursor to last line"), \
339 REQ_GROUP("Scrolling") \
340 REQ_(SCROLL_LINE_UP, "Scroll one line up"), \
341 REQ_(SCROLL_LINE_DOWN, "Scroll one line down"), \
342 REQ_(SCROLL_PAGE_UP, "Scroll one page up"), \
343 REQ_(SCROLL_PAGE_DOWN, "Scroll one page down"), \
345 REQ_GROUP("Searching") \
346 REQ_(SEARCH, "Search the view"), \
347 REQ_(SEARCH_BACK, "Search backwards in the view"), \
348 REQ_(FIND_NEXT, "Find next search match"), \
349 REQ_(FIND_PREV, "Find previous search match"), \
352 REQ_(PROMPT, "Bring up the prompt"), \
353 REQ_(SCREEN_REDRAW, "Redraw the screen"), \
354 REQ_(SCREEN_RESIZE, "Resize the screen"), \
355 REQ_(SHOW_VERSION, "Show version information"), \
356 REQ_(STOP_LOADING, "Stop all loading views"), \
357 REQ_(TOGGLE_LINENO, "Toggle line numbers"), \
358 REQ_(TOGGLE_DATE, "Toggle date display"), \
359 REQ_(TOGGLE_AUTHOR, "Toggle author display"), \
360 REQ_(TOGGLE_REV_GRAPH, "Toggle revision graph visualization"), \
361 REQ_(TOGGLE_REFS, "Toggle reference display (tags/branches)"), \
362 REQ_(STATUS_UPDATE, "Update file status"), \
363 REQ_(STATUS_MERGE, "Merge file using external tool"), \
364 REQ_(TREE_PARENT, "Switch to parent directory in tree view"), \
365 REQ_(EDIT, "Open in editor"), \
366 REQ_(NONE, "Do nothing")
369 /* User action requests. */
371 #define REQ_GROUP(help)
372 #define REQ_(req, help) REQ_##req
374 /* Offset all requests to avoid conflicts with ncurses getch values. */
375 REQ_OFFSET = KEY_MAX + 1,
382 struct request_info {
383 enum request request;
389 static struct request_info req_info[] = {
390 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
391 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
398 get_request(const char *name)
400 int namelen = strlen(name);
403 for (i = 0; i < ARRAY_SIZE(req_info); i++)
404 if (req_info[i].namelen == namelen &&
405 !string_enum_compare(req_info[i].name, name, namelen))
406 return req_info[i].request;
416 static const char usage[] =
417 "tig " TIG_VERSION " (" __DATE__ ")\n"
419 "Usage: tig [options] [revs] [--] [paths]\n"
420 " or: tig show [options] [revs] [--] [paths]\n"
422 " or: tig < [git command output]\n"
425 " -v, --version Show version and exit\n"
426 " -h, --help Show help message and exit\n";
428 /* Option and state variables. */
429 static bool opt_date = TRUE;
430 static bool opt_author = TRUE;
431 static bool opt_line_number = FALSE;
432 static bool opt_rev_graph = FALSE;
433 static bool opt_show_refs = TRUE;
434 static int opt_num_interval = NUMBER_INTERVAL;
435 static int opt_tab_size = TABSIZE;
436 static enum request opt_request = REQ_VIEW_MAIN;
437 static char opt_cmd[SIZEOF_STR] = "";
438 static char opt_path[SIZEOF_STR] = "";
439 static FILE *opt_pipe = NULL;
440 static char opt_encoding[20] = "UTF-8";
441 static bool opt_utf8 = TRUE;
442 static char opt_codeset[20] = "UTF-8";
443 static iconv_t opt_iconv = ICONV_NONE;
444 static char opt_search[SIZEOF_STR] = "";
445 static char opt_cdup[SIZEOF_STR] = "";
446 static char opt_git_dir[SIZEOF_STR] = "";
447 static signed char opt_is_inside_work_tree = -1; /* set to TRUE or FALSE */
448 static char opt_editor[SIZEOF_STR] = "";
456 check_option(char *opt, char short_name, char *name, enum option_type type, ...)
466 int namelen = strlen(name);
470 if (strncmp(opt, name, namelen))
473 if (opt[namelen] == '=')
474 value = opt + namelen + 1;
477 if (!short_name || opt[1] != short_name)
482 va_start(args, type);
483 if (type == OPT_INT) {
484 number = va_arg(args, int *);
486 *number = atoi(value);
493 /* Returns the index of log or diff command or -1 to exit. */
495 parse_options(int argc, char *argv[])
505 subcommand = argv[1];
506 if (!strcmp(subcommand, "status")) {
507 opt_request = REQ_VIEW_STATUS;
509 warn("ignoring arguments after `%s'", subcommand);
512 } else if (!strcmp(subcommand, "show")) {
513 opt_request = REQ_VIEW_DIFF;
515 } else if (!strcmp(subcommand, "log") || !strcmp(subcommand, "diff")) {
516 opt_request = subcommand[0] == 'l'
517 ? REQ_VIEW_LOG : REQ_VIEW_DIFF;
518 warn("`tig %s' has been deprecated", subcommand);
524 for (i = 1 + !!subcommand; i < argc; i++) {
527 if (opt[0] && opt[0] != '-')
530 if (!strcmp(opt, "--")) {
535 if (check_option(opt, 'v', "version", OPT_NONE)) {
536 printf("tig version %s\n", TIG_VERSION);
540 if (check_option(opt, 'h', "help", OPT_NONE)) {
545 if (!strcmp(opt, "-S")) {
546 warn("`%s' has been deprecated; use `tig status' instead", opt);
547 opt_request = REQ_VIEW_STATUS;
551 if (!strcmp(opt, "-l")) {
552 opt_request = REQ_VIEW_LOG;
553 } else if (!strcmp(opt, "-d")) {
554 opt_request = REQ_VIEW_DIFF;
555 } else if (check_option(opt, 'n', "line-number", OPT_INT, &opt_num_interval)) {
556 opt_line_number = TRUE;
557 } else if (check_option(opt, 'b', "tab-size", OPT_INT, &opt_tab_size)) {
558 opt_tab_size = MIN(opt_tab_size, TABSIZE);
560 if (altargc >= ARRAY_SIZE(altargv))
561 die("maximum number of arguments exceeded");
562 altargv[altargc++] = opt;
566 warn("`%s' has been deprecated", opt);
569 if (!isatty(STDIN_FILENO)) {
570 opt_request = REQ_VIEW_PAGER;
573 } else if (i < argc || altargc > 0) {
577 if (opt_request == REQ_VIEW_MAIN)
578 /* XXX: This is vulnerable to the user overriding
579 * options required for the main view parser. */
580 string_copy(opt_cmd, "git log --no-color --pretty=raw --boundary --parents");
582 string_format(opt_cmd, "git %s", subcommand);
583 buf_size = strlen(opt_cmd);
585 while (buf_size < sizeof(opt_cmd) && alti < altargc) {
586 opt_cmd[buf_size++] = ' ';
587 buf_size = sq_quote(opt_cmd, buf_size, altargv[alti++]);
590 while (buf_size < sizeof(opt_cmd) && i < argc) {
591 opt_cmd[buf_size++] = ' ';
592 buf_size = sq_quote(opt_cmd, buf_size, argv[i++]);
595 if (buf_size >= sizeof(opt_cmd))
596 die("command too long");
598 opt_cmd[buf_size] = 0;
606 * Line-oriented content detection.
610 LINE(DIFF_HEADER, "diff --git ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
611 LINE(DIFF_CHUNK, "@@", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
612 LINE(DIFF_ADD, "+", COLOR_GREEN, COLOR_DEFAULT, 0), \
613 LINE(DIFF_DEL, "-", COLOR_RED, COLOR_DEFAULT, 0), \
614 LINE(DIFF_INDEX, "index ", COLOR_BLUE, COLOR_DEFAULT, 0), \
615 LINE(DIFF_OLDMODE, "old file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
616 LINE(DIFF_NEWMODE, "new file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
617 LINE(DIFF_COPY_FROM, "copy from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
618 LINE(DIFF_COPY_TO, "copy to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
619 LINE(DIFF_RENAME_FROM, "rename from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
620 LINE(DIFF_RENAME_TO, "rename to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
621 LINE(DIFF_SIMILARITY, "similarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
622 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
623 LINE(DIFF_TREE, "diff-tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
624 LINE(PP_AUTHOR, "Author: ", COLOR_CYAN, COLOR_DEFAULT, 0), \
625 LINE(PP_COMMIT, "Commit: ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
626 LINE(PP_MERGE, "Merge: ", COLOR_BLUE, COLOR_DEFAULT, 0), \
627 LINE(PP_DATE, "Date: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
628 LINE(PP_ADATE, "AuthorDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
629 LINE(PP_CDATE, "CommitDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
630 LINE(PP_REFS, "Refs: ", COLOR_RED, COLOR_DEFAULT, 0), \
631 LINE(COMMIT, "commit ", COLOR_GREEN, COLOR_DEFAULT, 0), \
632 LINE(PARENT, "parent ", COLOR_BLUE, COLOR_DEFAULT, 0), \
633 LINE(TREE, "tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
634 LINE(AUTHOR, "author ", COLOR_CYAN, COLOR_DEFAULT, 0), \
635 LINE(COMMITTER, "committer ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
636 LINE(SIGNOFF, " Signed-off-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
637 LINE(ACKED, " Acked-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
638 LINE(DEFAULT, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
639 LINE(CURSOR, "", COLOR_WHITE, COLOR_GREEN, A_BOLD), \
640 LINE(STATUS, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
641 LINE(TITLE_BLUR, "", COLOR_WHITE, COLOR_BLUE, 0), \
642 LINE(TITLE_FOCUS, "", COLOR_WHITE, COLOR_BLUE, A_BOLD), \
643 LINE(MAIN_DATE, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
644 LINE(MAIN_AUTHOR, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
645 LINE(MAIN_COMMIT, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
646 LINE(MAIN_DELIM, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
647 LINE(MAIN_TAG, "", COLOR_MAGENTA, COLOR_DEFAULT, A_BOLD), \
648 LINE(MAIN_LOCAL_TAG,"", COLOR_MAGENTA, COLOR_DEFAULT, A_BOLD), \
649 LINE(MAIN_REMOTE, "", COLOR_YELLOW, COLOR_DEFAULT, A_BOLD), \
650 LINE(MAIN_REF, "", COLOR_CYAN, COLOR_DEFAULT, A_BOLD), \
651 LINE(MAIN_REVGRAPH,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
652 LINE(TREE_DIR, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
653 LINE(TREE_FILE, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
654 LINE(STAT_SECTION, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
655 LINE(STAT_NONE, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
656 LINE(STAT_STAGED, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
657 LINE(STAT_UNSTAGED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
658 LINE(STAT_UNTRACKED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0)
661 #define LINE(type, line, fg, bg, attr) \
668 const char *name; /* Option name. */
669 int namelen; /* Size of option name. */
670 const char *line; /* The start of line to match. */
671 int linelen; /* Size of string to match. */
672 int fg, bg, attr; /* Color and text attributes for the lines. */
675 static struct line_info line_info[] = {
676 #define LINE(type, line, fg, bg, attr) \
677 { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
682 static enum line_type
683 get_line_type(char *line)
685 int linelen = strlen(line);
688 for (type = 0; type < ARRAY_SIZE(line_info); type++)
689 /* Case insensitive search matches Signed-off-by lines better. */
690 if (linelen >= line_info[type].linelen &&
691 !strncasecmp(line_info[type].line, line, line_info[type].linelen))
698 get_line_attr(enum line_type type)
700 assert(type < ARRAY_SIZE(line_info));
701 return COLOR_PAIR(type) | line_info[type].attr;
704 static struct line_info *
705 get_line_info(char *name, int namelen)
709 for (type = 0; type < ARRAY_SIZE(line_info); type++)
710 if (namelen == line_info[type].namelen &&
711 !string_enum_compare(line_info[type].name, name, namelen))
712 return &line_info[type];
720 int default_bg = line_info[LINE_DEFAULT].bg;
721 int default_fg = line_info[LINE_DEFAULT].fg;
726 if (assume_default_colors(default_fg, default_bg) == ERR) {
727 default_bg = COLOR_BLACK;
728 default_fg = COLOR_WHITE;
731 for (type = 0; type < ARRAY_SIZE(line_info); type++) {
732 struct line_info *info = &line_info[type];
733 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
734 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
736 init_pair(type, fg, bg);
744 unsigned int selected:1;
746 void *data; /* User data */
756 enum request request;
757 struct keybinding *next;
760 static struct keybinding default_keybindings[] = {
762 { 'm', REQ_VIEW_MAIN },
763 { 'd', REQ_VIEW_DIFF },
764 { 'l', REQ_VIEW_LOG },
765 { 't', REQ_VIEW_TREE },
766 { 'f', REQ_VIEW_BLOB },
767 { 'p', REQ_VIEW_PAGER },
768 { 'h', REQ_VIEW_HELP },
769 { 'S', REQ_VIEW_STATUS },
770 { 'c', REQ_VIEW_STAGE },
772 /* View manipulation */
773 { 'q', REQ_VIEW_CLOSE },
774 { KEY_TAB, REQ_VIEW_NEXT },
775 { KEY_RETURN, REQ_ENTER },
776 { KEY_UP, REQ_PREVIOUS },
777 { KEY_DOWN, REQ_NEXT },
778 { 'R', REQ_REFRESH },
780 /* Cursor navigation */
781 { 'k', REQ_MOVE_UP },
782 { 'j', REQ_MOVE_DOWN },
783 { KEY_HOME, REQ_MOVE_FIRST_LINE },
784 { KEY_END, REQ_MOVE_LAST_LINE },
785 { KEY_NPAGE, REQ_MOVE_PAGE_DOWN },
786 { ' ', REQ_MOVE_PAGE_DOWN },
787 { KEY_PPAGE, REQ_MOVE_PAGE_UP },
788 { 'b', REQ_MOVE_PAGE_UP },
789 { '-', REQ_MOVE_PAGE_UP },
792 { KEY_IC, REQ_SCROLL_LINE_UP },
793 { KEY_DC, REQ_SCROLL_LINE_DOWN },
794 { 'w', REQ_SCROLL_PAGE_UP },
795 { 's', REQ_SCROLL_PAGE_DOWN },
799 { '?', REQ_SEARCH_BACK },
800 { 'n', REQ_FIND_NEXT },
801 { 'N', REQ_FIND_PREV },
805 { 'z', REQ_STOP_LOADING },
806 { 'v', REQ_SHOW_VERSION },
807 { 'r', REQ_SCREEN_REDRAW },
808 { '.', REQ_TOGGLE_LINENO },
809 { 'D', REQ_TOGGLE_DATE },
810 { 'A', REQ_TOGGLE_AUTHOR },
811 { 'g', REQ_TOGGLE_REV_GRAPH },
812 { 'F', REQ_TOGGLE_REFS },
814 { 'u', REQ_STATUS_UPDATE },
815 { 'M', REQ_STATUS_MERGE },
816 { ',', REQ_TREE_PARENT },
819 /* Using the ncurses SIGWINCH handler. */
820 { KEY_RESIZE, REQ_SCREEN_RESIZE },
823 #define KEYMAP_INFO \
836 #define KEYMAP_(name) KEYMAP_##name
841 static struct int_map keymap_table[] = {
842 #define KEYMAP_(name) { #name, STRING_SIZE(#name), KEYMAP_##name }
847 #define set_keymap(map, name) \
848 set_from_int_map(keymap_table, ARRAY_SIZE(keymap_table), map, name, strlen(name))
850 static struct keybinding *keybindings[ARRAY_SIZE(keymap_table)];
853 add_keybinding(enum keymap keymap, enum request request, int key)
855 struct keybinding *keybinding;
857 keybinding = calloc(1, sizeof(*keybinding));
859 die("Failed to allocate keybinding");
861 keybinding->alias = key;
862 keybinding->request = request;
863 keybinding->next = keybindings[keymap];
864 keybindings[keymap] = keybinding;
867 /* Looks for a key binding first in the given map, then in the generic map, and
868 * lastly in the default keybindings. */
870 get_keybinding(enum keymap keymap, int key)
872 struct keybinding *kbd;
875 for (kbd = keybindings[keymap]; kbd; kbd = kbd->next)
876 if (kbd->alias == key)
879 for (kbd = keybindings[KEYMAP_GENERIC]; kbd; kbd = kbd->next)
880 if (kbd->alias == key)
883 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
884 if (default_keybindings[i].alias == key)
885 return default_keybindings[i].request;
887 return (enum request) key;
896 static struct key key_table[] = {
897 { "Enter", KEY_RETURN },
899 { "Backspace", KEY_BACKSPACE },
901 { "Escape", KEY_ESC },
902 { "Left", KEY_LEFT },
903 { "Right", KEY_RIGHT },
905 { "Down", KEY_DOWN },
906 { "Insert", KEY_IC },
907 { "Delete", KEY_DC },
909 { "Home", KEY_HOME },
911 { "PageUp", KEY_PPAGE },
912 { "PageDown", KEY_NPAGE },
922 { "F10", KEY_F(10) },
923 { "F11", KEY_F(11) },
924 { "F12", KEY_F(12) },
928 get_key_value(const char *name)
932 for (i = 0; i < ARRAY_SIZE(key_table); i++)
933 if (!strcasecmp(key_table[i].name, name))
934 return key_table[i].value;
936 if (strlen(name) == 1 && isprint(*name))
943 get_key_name(int key_value)
945 static char key_char[] = "'X'";
949 for (key = 0; key < ARRAY_SIZE(key_table); key++)
950 if (key_table[key].value == key_value)
951 seq = key_table[key].name;
955 isprint(key_value)) {
956 key_char[1] = (char) key_value;
960 return seq ? seq : "'?'";
964 get_key(enum request request)
966 static char buf[BUFSIZ];
973 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
974 struct keybinding *keybinding = &default_keybindings[i];
976 if (keybinding->request != request)
979 if (!string_format_from(buf, &pos, "%s%s", sep,
980 get_key_name(keybinding->alias)))
981 return "Too many keybindings!";
991 char cmd[SIZEOF_STR];
994 static struct run_request *run_request;
995 static size_t run_requests;
998 add_run_request(enum keymap keymap, int key, int argc, char **argv)
1000 struct run_request *tmp;
1001 struct run_request req = { keymap, key };
1004 for (bufpos = 0; argc > 0; argc--, argv++)
1005 if (!string_format_from(req.cmd, &bufpos, "%s ", *argv))
1008 req.cmd[bufpos - 1] = 0;
1010 tmp = realloc(run_request, (run_requests + 1) * sizeof(*run_request));
1015 run_request[run_requests++] = req;
1017 return REQ_NONE + run_requests;
1020 static struct run_request *
1021 get_run_request(enum request request)
1023 if (request <= REQ_NONE)
1025 return &run_request[request - REQ_NONE - 1];
1029 add_builtin_run_requests(void)
1036 { KEYMAP_MAIN, 'C', { "git cherry-pick %(commit)" } },
1037 { KEYMAP_GENERIC, 'G', { "git gc" } },
1041 for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1044 req = add_run_request(reqs[i].keymap, reqs[i].key, 1, reqs[i].argv);
1045 if (req != REQ_NONE)
1046 add_keybinding(reqs[i].keymap, req, reqs[i].key);
1051 * User config file handling.
1054 static struct int_map color_map[] = {
1055 #define COLOR_MAP(name) { #name, STRING_SIZE(#name), COLOR_##name }
1067 #define set_color(color, name) \
1068 set_from_int_map(color_map, ARRAY_SIZE(color_map), color, name, strlen(name))
1070 static struct int_map attr_map[] = {
1071 #define ATTR_MAP(name) { #name, STRING_SIZE(#name), A_##name }
1078 ATTR_MAP(UNDERLINE),
1081 #define set_attribute(attr, name) \
1082 set_from_int_map(attr_map, ARRAY_SIZE(attr_map), attr, name, strlen(name))
1084 static int config_lineno;
1085 static bool config_errors;
1086 static char *config_msg;
1088 /* Wants: object fgcolor bgcolor [attr] */
1090 option_color_command(int argc, char *argv[])
1092 struct line_info *info;
1094 if (argc != 3 && argc != 4) {
1095 config_msg = "Wrong number of arguments given to color command";
1099 info = get_line_info(argv[0], strlen(argv[0]));
1101 config_msg = "Unknown color name";
1105 if (set_color(&info->fg, argv[1]) == ERR ||
1106 set_color(&info->bg, argv[2]) == ERR) {
1107 config_msg = "Unknown color";
1111 if (argc == 4 && set_attribute(&info->attr, argv[3]) == ERR) {
1112 config_msg = "Unknown attribute";
1119 static bool parse_bool(const char *s)
1121 return (!strcmp(s, "1") || !strcmp(s, "true") ||
1122 !strcmp(s, "yes")) ? TRUE : FALSE;
1125 /* Wants: name = value */
1127 option_set_command(int argc, char *argv[])
1130 config_msg = "Wrong number of arguments given to set command";
1134 if (strcmp(argv[1], "=")) {
1135 config_msg = "No value assigned";
1139 if (!strcmp(argv[0], "show-author")) {
1140 opt_author = parse_bool(argv[2]);
1144 if (!strcmp(argv[0], "show-date")) {
1145 opt_date = parse_bool(argv[2]);
1149 if (!strcmp(argv[0], "show-rev-graph")) {
1150 opt_rev_graph = parse_bool(argv[2]);
1154 if (!strcmp(argv[0], "show-refs")) {
1155 opt_show_refs = parse_bool(argv[2]);
1159 if (!strcmp(argv[0], "show-line-numbers")) {
1160 opt_line_number = parse_bool(argv[2]);
1164 if (!strcmp(argv[0], "line-number-interval")) {
1165 opt_num_interval = atoi(argv[2]);
1169 if (!strcmp(argv[0], "tab-size")) {
1170 opt_tab_size = atoi(argv[2]);
1174 if (!strcmp(argv[0], "commit-encoding")) {
1175 char *arg = argv[2];
1176 int delimiter = *arg;
1179 switch (delimiter) {
1182 for (arg++, i = 0; arg[i]; i++)
1183 if (arg[i] == delimiter) {
1188 string_ncopy(opt_encoding, arg, strlen(arg));
1193 config_msg = "Unknown variable name";
1197 /* Wants: mode request key */
1199 option_bind_command(int argc, char *argv[])
1201 enum request request;
1206 config_msg = "Wrong number of arguments given to bind command";
1210 if (set_keymap(&keymap, argv[0]) == ERR) {
1211 config_msg = "Unknown key map";
1215 key = get_key_value(argv[1]);
1217 config_msg = "Unknown key";
1221 request = get_request(argv[2]);
1222 if (request == REQ_NONE) {
1223 const char *obsolete[] = { "cherry-pick" };
1224 size_t namelen = strlen(argv[2]);
1227 for (i = 0; i < ARRAY_SIZE(obsolete); i++) {
1228 if (namelen == strlen(obsolete[i]) &&
1229 !string_enum_compare(obsolete[i], argv[2], namelen)) {
1230 config_msg = "Obsolete request name";
1235 if (request == REQ_NONE && *argv[2]++ == '!')
1236 request = add_run_request(keymap, key, argc - 2, argv + 2);
1237 if (request == REQ_NONE) {
1238 config_msg = "Unknown request name";
1242 add_keybinding(keymap, request, key);
1248 set_option(char *opt, char *value)
1255 while (argc < ARRAY_SIZE(argv) && (valuelen = strcspn(value, " \t"))) {
1256 argv[argc++] = value;
1259 /* Nothing more to tokenize or last available token. */
1260 if (!*value || argc >= ARRAY_SIZE(argv))
1264 while (isspace(*value))
1268 if (!strcmp(opt, "color"))
1269 return option_color_command(argc, argv);
1271 if (!strcmp(opt, "set"))
1272 return option_set_command(argc, argv);
1274 if (!strcmp(opt, "bind"))
1275 return option_bind_command(argc, argv);
1277 config_msg = "Unknown option command";
1282 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
1287 config_msg = "Internal error";
1289 /* Check for comment markers, since read_properties() will
1290 * only ensure opt and value are split at first " \t". */
1291 optlen = strcspn(opt, "#");
1295 if (opt[optlen] != 0) {
1296 config_msg = "No option value";
1300 /* Look for comment endings in the value. */
1301 size_t len = strcspn(value, "#");
1303 if (len < valuelen) {
1305 value[valuelen] = 0;
1308 status = set_option(opt, value);
1311 if (status == ERR) {
1312 fprintf(stderr, "Error on line %d, near '%.*s': %s\n",
1313 config_lineno, (int) optlen, opt, config_msg);
1314 config_errors = TRUE;
1317 /* Always keep going if errors are encountered. */
1322 load_option_file(const char *path)
1326 /* It's ok that the file doesn't exist. */
1327 file = fopen(path, "r");
1332 config_errors = FALSE;
1334 if (read_properties(file, " \t", read_option) == ERR ||
1335 config_errors == TRUE)
1336 fprintf(stderr, "Errors while loading %s.\n", path);
1342 char *home = getenv("HOME");
1343 char *tigrc_user = getenv("TIGRC_USER");
1344 char *tigrc_system = getenv("TIGRC_SYSTEM");
1345 char buf[SIZEOF_STR];
1347 add_builtin_run_requests();
1349 if (!tigrc_system) {
1350 if (!string_format(buf, "%s/tigrc", SYSCONFDIR))
1354 load_option_file(tigrc_system);
1357 if (!home || !string_format(buf, "%s/.tigrc", home))
1361 load_option_file(tigrc_user);
1374 /* The display array of active views and the index of the current view. */
1375 static struct view *display[2];
1376 static unsigned int current_view;
1378 /* Reading from the prompt? */
1379 static bool input_mode = FALSE;
1381 #define foreach_displayed_view(view, i) \
1382 for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1384 #define displayed_views() (display[1] != NULL ? 2 : 1)
1386 /* Current head and commit ID */
1387 static char ref_blob[SIZEOF_REF] = "";
1388 static char ref_commit[SIZEOF_REF] = "HEAD";
1389 static char ref_head[SIZEOF_REF] = "HEAD";
1392 const char *name; /* View name */
1393 const char *cmd_fmt; /* Default command line format */
1394 const char *cmd_env; /* Command line set via environment */
1395 const char *id; /* Points to either of ref_{head,commit,blob} */
1397 struct view_ops *ops; /* View operations */
1399 enum keymap keymap; /* What keymap does this view have */
1401 char cmd[SIZEOF_STR]; /* Command buffer */
1402 char ref[SIZEOF_REF]; /* Hovered commit reference */
1403 char vid[SIZEOF_REF]; /* View ID. Set to id member when updating. */
1405 int height, width; /* The width and height of the main window */
1406 WINDOW *win; /* The main window */
1407 WINDOW *title; /* The title window living below the main window */
1410 unsigned long offset; /* Offset of the window top */
1411 unsigned long lineno; /* Current line number */
1414 char grep[SIZEOF_STR]; /* Search string */
1415 regex_t *regex; /* Pre-compiled regex */
1417 /* If non-NULL, points to the view that opened this view. If this view
1418 * is closed tig will switch back to the parent view. */
1419 struct view *parent;
1422 size_t lines; /* Total number of lines */
1423 struct line *line; /* Line index */
1424 size_t line_alloc; /* Total number of allocated lines */
1425 size_t line_size; /* Total number of used lines */
1426 unsigned int digits; /* Number of digits in the lines member. */
1434 /* What type of content being displayed. Used in the title bar. */
1436 /* Open and reads in all view content. */
1437 bool (*open)(struct view *view);
1438 /* Read one line; updates view->line. */
1439 bool (*read)(struct view *view, char *data);
1440 /* Draw one line; @lineno must be < view->height. */
1441 bool (*draw)(struct view *view, struct line *line, unsigned int lineno, bool selected);
1442 /* Depending on view handle a special requests. */
1443 enum request (*request)(struct view *view, enum request request, struct line *line);
1444 /* Search for regex in a line. */
1445 bool (*grep)(struct view *view, struct line *line);
1447 void (*select)(struct view *view, struct line *line);
1450 static struct view_ops pager_ops;
1451 static struct view_ops main_ops;
1452 static struct view_ops tree_ops;
1453 static struct view_ops blob_ops;
1454 static struct view_ops help_ops;
1455 static struct view_ops status_ops;
1456 static struct view_ops stage_ops;
1458 #define VIEW_STR(name, cmd, env, ref, ops, map) \
1459 { name, cmd, #env, ref, ops, map}
1461 #define VIEW_(id, name, ops, ref) \
1462 VIEW_STR(name, TIG_##id##_CMD, TIG_##id##_CMD, ref, ops, KEYMAP_##id)
1465 static struct view views[] = {
1466 VIEW_(MAIN, "main", &main_ops, ref_head),
1467 VIEW_(DIFF, "diff", &pager_ops, ref_commit),
1468 VIEW_(LOG, "log", &pager_ops, ref_head),
1469 VIEW_(TREE, "tree", &tree_ops, ref_commit),
1470 VIEW_(BLOB, "blob", &blob_ops, ref_blob),
1471 VIEW_(HELP, "help", &help_ops, ""),
1472 VIEW_(PAGER, "pager", &pager_ops, "stdin"),
1473 VIEW_(STATUS, "status", &status_ops, ""),
1474 VIEW_(STAGE, "stage", &stage_ops, ""),
1477 #define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
1479 #define foreach_view(view, i) \
1480 for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
1482 #define view_is_displayed(view) \
1483 (view == display[0] || view == display[1])
1486 draw_text(struct view *view, const char *string, int max_len,
1487 bool use_tilde, int tilde_attr)
1490 int trimmed = FALSE;
1496 len = utf8_length(string, max_len, &trimmed, use_tilde);
1498 len = strlen(string);
1499 if (len > max_len) {
1508 waddnstr(view->win, string, len);
1509 if (trimmed && use_tilde) {
1510 if (tilde_attr != -1)
1511 wattrset(view->win, tilde_attr);
1512 waddch(view->win, '~');
1520 draw_view_line(struct view *view, unsigned int lineno)
1523 bool selected = (view->offset + lineno == view->lineno);
1526 assert(view_is_displayed(view));
1528 if (view->offset + lineno >= view->lines)
1531 line = &view->line[view->offset + lineno];
1534 line->selected = TRUE;
1535 view->ops->select(view, line);
1536 } else if (line->selected) {
1537 line->selected = FALSE;
1538 wmove(view->win, lineno, 0);
1539 wclrtoeol(view->win);
1542 scrollok(view->win, FALSE);
1543 draw_ok = view->ops->draw(view, line, lineno, selected);
1544 scrollok(view->win, TRUE);
1550 redraw_view_from(struct view *view, int lineno)
1552 assert(0 <= lineno && lineno < view->height);
1554 for (; lineno < view->height; lineno++) {
1555 if (!draw_view_line(view, lineno))
1559 redrawwin(view->win);
1561 wnoutrefresh(view->win);
1563 wrefresh(view->win);
1567 redraw_view(struct view *view)
1570 redraw_view_from(view, 0);
1575 update_view_title(struct view *view)
1577 char buf[SIZEOF_STR];
1578 char state[SIZEOF_STR];
1579 size_t bufpos = 0, statelen = 0;
1581 assert(view_is_displayed(view));
1583 if (view != VIEW(REQ_VIEW_STATUS) && (view->lines || view->pipe)) {
1584 unsigned int view_lines = view->offset + view->height;
1585 unsigned int lines = view->lines
1586 ? MIN(view_lines, view->lines) * 100 / view->lines
1589 string_format_from(state, &statelen, "- %s %d of %d (%d%%)",
1596 time_t secs = time(NULL) - view->start_time;
1598 /* Three git seconds are a long time ... */
1600 string_format_from(state, &statelen, " %lds", secs);
1604 string_format_from(buf, &bufpos, "[%s]", view->name);
1605 if (*view->ref && bufpos < view->width) {
1606 size_t refsize = strlen(view->ref);
1607 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
1609 if (minsize < view->width)
1610 refsize = view->width - minsize + 7;
1611 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
1614 if (statelen && bufpos < view->width) {
1615 string_format_from(buf, &bufpos, " %s", state);
1618 if (view == display[current_view])
1619 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
1621 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
1623 mvwaddnstr(view->title, 0, 0, buf, bufpos);
1624 wclrtoeol(view->title);
1625 wmove(view->title, 0, view->width - 1);
1628 wnoutrefresh(view->title);
1630 wrefresh(view->title);
1634 resize_display(void)
1637 struct view *base = display[0];
1638 struct view *view = display[1] ? display[1] : display[0];
1640 /* Setup window dimensions */
1642 getmaxyx(stdscr, base->height, base->width);
1644 /* Make room for the status window. */
1648 /* Horizontal split. */
1649 view->width = base->width;
1650 view->height = SCALE_SPLIT_VIEW(base->height);
1651 base->height -= view->height;
1653 /* Make room for the title bar. */
1657 /* Make room for the title bar. */
1662 foreach_displayed_view (view, i) {
1664 view->win = newwin(view->height, 0, offset, 0);
1666 die("Failed to create %s view", view->name);
1668 scrollok(view->win, TRUE);
1670 view->title = newwin(1, 0, offset + view->height, 0);
1672 die("Failed to create title window");
1675 wresize(view->win, view->height, view->width);
1676 mvwin(view->win, offset, 0);
1677 mvwin(view->title, offset + view->height, 0);
1680 offset += view->height + 1;
1685 redraw_display(void)
1690 foreach_displayed_view (view, i) {
1692 update_view_title(view);
1697 update_display_cursor(struct view *view)
1699 /* Move the cursor to the right-most column of the cursor line.
1701 * XXX: This could turn out to be a bit expensive, but it ensures that
1702 * the cursor does not jump around. */
1704 wmove(view->win, view->lineno - view->offset, view->width - 1);
1705 wrefresh(view->win);
1713 /* Scrolling backend */
1715 do_scroll_view(struct view *view, int lines)
1717 bool redraw_current_line = FALSE;
1719 /* The rendering expects the new offset. */
1720 view->offset += lines;
1722 assert(0 <= view->offset && view->offset < view->lines);
1725 /* Move current line into the view. */
1726 if (view->lineno < view->offset) {
1727 view->lineno = view->offset;
1728 redraw_current_line = TRUE;
1729 } else if (view->lineno >= view->offset + view->height) {
1730 view->lineno = view->offset + view->height - 1;
1731 redraw_current_line = TRUE;
1734 assert(view->offset <= view->lineno && view->lineno < view->lines);
1736 /* Redraw the whole screen if scrolling is pointless. */
1737 if (view->height < ABS(lines)) {
1741 int line = lines > 0 ? view->height - lines : 0;
1742 int end = line + ABS(lines);
1744 wscrl(view->win, lines);
1746 for (; line < end; line++) {
1747 if (!draw_view_line(view, line))
1751 if (redraw_current_line)
1752 draw_view_line(view, view->lineno - view->offset);
1755 redrawwin(view->win);
1756 wrefresh(view->win);
1760 /* Scroll frontend */
1762 scroll_view(struct view *view, enum request request)
1766 assert(view_is_displayed(view));
1769 case REQ_SCROLL_PAGE_DOWN:
1770 lines = view->height;
1771 case REQ_SCROLL_LINE_DOWN:
1772 if (view->offset + lines > view->lines)
1773 lines = view->lines - view->offset;
1775 if (lines == 0 || view->offset + view->height >= view->lines) {
1776 report("Cannot scroll beyond the last line");
1781 case REQ_SCROLL_PAGE_UP:
1782 lines = view->height;
1783 case REQ_SCROLL_LINE_UP:
1784 if (lines > view->offset)
1785 lines = view->offset;
1788 report("Cannot scroll beyond the first line");
1796 die("request %d not handled in switch", request);
1799 do_scroll_view(view, lines);
1804 move_view(struct view *view, enum request request)
1806 int scroll_steps = 0;
1810 case REQ_MOVE_FIRST_LINE:
1811 steps = -view->lineno;
1814 case REQ_MOVE_LAST_LINE:
1815 steps = view->lines - view->lineno - 1;
1818 case REQ_MOVE_PAGE_UP:
1819 steps = view->height > view->lineno
1820 ? -view->lineno : -view->height;
1823 case REQ_MOVE_PAGE_DOWN:
1824 steps = view->lineno + view->height >= view->lines
1825 ? view->lines - view->lineno - 1 : view->height;
1837 die("request %d not handled in switch", request);
1840 if (steps <= 0 && view->lineno == 0) {
1841 report("Cannot move beyond the first line");
1844 } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
1845 report("Cannot move beyond the last line");
1849 /* Move the current line */
1850 view->lineno += steps;
1851 assert(0 <= view->lineno && view->lineno < view->lines);
1853 /* Check whether the view needs to be scrolled */
1854 if (view->lineno < view->offset ||
1855 view->lineno >= view->offset + view->height) {
1856 scroll_steps = steps;
1857 if (steps < 0 && -steps > view->offset) {
1858 scroll_steps = -view->offset;
1860 } else if (steps > 0) {
1861 if (view->lineno == view->lines - 1 &&
1862 view->lines > view->height) {
1863 scroll_steps = view->lines - view->offset - 1;
1864 if (scroll_steps >= view->height)
1865 scroll_steps -= view->height - 1;
1870 if (!view_is_displayed(view)) {
1871 view->offset += scroll_steps;
1872 assert(0 <= view->offset && view->offset < view->lines);
1873 view->ops->select(view, &view->line[view->lineno]);
1877 /* Repaint the old "current" line if we be scrolling */
1878 if (ABS(steps) < view->height)
1879 draw_view_line(view, view->lineno - steps - view->offset);
1882 do_scroll_view(view, scroll_steps);
1886 /* Draw the current line */
1887 draw_view_line(view, view->lineno - view->offset);
1889 redrawwin(view->win);
1890 wrefresh(view->win);
1899 static void search_view(struct view *view, enum request request);
1902 find_next_line(struct view *view, unsigned long lineno, struct line *line)
1904 assert(view_is_displayed(view));
1906 if (!view->ops->grep(view, line))
1909 if (lineno - view->offset >= view->height) {
1910 view->offset = lineno;
1911 view->lineno = lineno;
1915 unsigned long old_lineno = view->lineno - view->offset;
1917 view->lineno = lineno;
1918 draw_view_line(view, old_lineno);
1920 draw_view_line(view, view->lineno - view->offset);
1921 redrawwin(view->win);
1922 wrefresh(view->win);
1925 report("Line %ld matches '%s'", lineno + 1, view->grep);
1930 find_next(struct view *view, enum request request)
1932 unsigned long lineno = view->lineno;
1937 report("No previous search");
1939 search_view(view, request);
1949 case REQ_SEARCH_BACK:
1958 if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
1959 lineno += direction;
1961 /* Note, lineno is unsigned long so will wrap around in which case it
1962 * will become bigger than view->lines. */
1963 for (; lineno < view->lines; lineno += direction) {
1964 struct line *line = &view->line[lineno];
1966 if (find_next_line(view, lineno, line))
1970 report("No match found for '%s'", view->grep);
1974 search_view(struct view *view, enum request request)
1979 regfree(view->regex);
1982 view->regex = calloc(1, sizeof(*view->regex));
1987 regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
1988 if (regex_err != 0) {
1989 char buf[SIZEOF_STR] = "unknown error";
1991 regerror(regex_err, view->regex, buf, sizeof(buf));
1992 report("Search failed: %s", buf);
1996 string_copy(view->grep, opt_search);
1998 find_next(view, request);
2002 * Incremental updating
2006 end_update(struct view *view)
2010 set_nonblocking_input(FALSE);
2011 if (view->pipe == stdin)
2019 begin_update(struct view *view)
2025 string_copy(view->cmd, opt_cmd);
2027 /* When running random commands, initially show the
2028 * command in the title. However, it maybe later be
2029 * overwritten if a commit line is selected. */
2030 if (view == VIEW(REQ_VIEW_PAGER))
2031 string_copy(view->ref, view->cmd);
2035 } else if (view == VIEW(REQ_VIEW_TREE)) {
2036 const char *format = view->cmd_env ? view->cmd_env : view->cmd_fmt;
2037 char path[SIZEOF_STR];
2039 if (strcmp(view->vid, view->id))
2040 opt_path[0] = path[0] = 0;
2041 else if (sq_quote(path, 0, opt_path) >= sizeof(path))
2044 if (!string_format(view->cmd, format, view->id, path))
2048 const char *format = view->cmd_env ? view->cmd_env : view->cmd_fmt;
2049 const char *id = view->id;
2051 if (!string_format(view->cmd, format, id, id, id, id, id))
2054 /* Put the current ref_* value to the view title ref
2055 * member. This is needed by the blob view. Most other
2056 * views sets it automatically after loading because the
2057 * first line is a commit line. */
2058 string_copy_rev(view->ref, view->id);
2061 /* Special case for the pager view. */
2063 view->pipe = opt_pipe;
2066 view->pipe = popen(view->cmd, "r");
2072 set_nonblocking_input(TRUE);
2077 string_copy_rev(view->vid, view->id);
2082 for (i = 0; i < view->lines; i++)
2083 if (view->line[i].data)
2084 free(view->line[i].data);
2090 view->start_time = time(NULL);
2095 #define ITEM_CHUNK_SIZE 256
2097 realloc_items(void *mem, size_t *size, size_t new_size, size_t item_size)
2099 size_t num_chunks = *size / ITEM_CHUNK_SIZE;
2100 size_t num_chunks_new = (new_size + ITEM_CHUNK_SIZE - 1) / ITEM_CHUNK_SIZE;
2102 if (mem == NULL || num_chunks != num_chunks_new) {
2103 *size = num_chunks_new * ITEM_CHUNK_SIZE;
2104 mem = realloc(mem, *size * item_size);
2110 static struct line *
2111 realloc_lines(struct view *view, size_t line_size)
2113 size_t alloc = view->line_alloc;
2114 struct line *tmp = realloc_items(view->line, &alloc, line_size,
2115 sizeof(*view->line));
2121 view->line_alloc = alloc;
2122 view->line_size = line_size;
2127 update_view(struct view *view)
2129 char in_buffer[BUFSIZ];
2130 char out_buffer[BUFSIZ * 2];
2132 /* The number of lines to read. If too low it will cause too much
2133 * redrawing (and possible flickering), if too high responsiveness
2135 unsigned long lines = view->height;
2136 int redraw_from = -1;
2141 /* Only redraw if lines are visible. */
2142 if (view->offset + view->height >= view->lines)
2143 redraw_from = view->lines - view->offset;
2145 /* FIXME: This is probably not perfect for backgrounded views. */
2146 if (!realloc_lines(view, view->lines + lines))
2149 while ((line = fgets(in_buffer, sizeof(in_buffer), view->pipe))) {
2150 size_t linelen = strlen(line);
2153 line[linelen - 1] = 0;
2155 if (opt_iconv != ICONV_NONE) {
2156 ICONV_CONST char *inbuf = line;
2157 size_t inlen = linelen;
2159 char *outbuf = out_buffer;
2160 size_t outlen = sizeof(out_buffer);
2164 ret = iconv(opt_iconv, &inbuf, &inlen, &outbuf, &outlen);
2165 if (ret != (size_t) -1) {
2167 linelen = strlen(out_buffer);
2171 if (!view->ops->read(view, line))
2181 lines = view->lines;
2182 for (digits = 0; lines; digits++)
2185 /* Keep the displayed view in sync with line number scaling. */
2186 if (digits != view->digits) {
2187 view->digits = digits;
2192 if (!view_is_displayed(view))
2195 if (view == VIEW(REQ_VIEW_TREE)) {
2196 /* Clear the view and redraw everything since the tree sorting
2197 * might have rearranged things. */
2200 } else if (redraw_from >= 0) {
2201 /* If this is an incremental update, redraw the previous line
2202 * since for commits some members could have changed when
2203 * loading the main view. */
2204 if (redraw_from > 0)
2207 /* Since revision graph visualization requires knowledge
2208 * about the parent commit, it causes a further one-off
2209 * needed to be redrawn for incremental updates. */
2210 if (redraw_from > 0 && opt_rev_graph)
2213 /* Incrementally draw avoids flickering. */
2214 redraw_view_from(view, redraw_from);
2217 /* Update the title _after_ the redraw so that if the redraw picks up a
2218 * commit reference in view->ref it'll be available here. */
2219 update_view_title(view);
2222 if (ferror(view->pipe)) {
2223 report("Failed to read: %s", strerror(errno));
2226 } else if (feof(view->pipe)) {
2234 report("Allocation failure");
2237 view->ops->read(view, NULL);
2242 static struct line *
2243 add_line_data(struct view *view, void *data, enum line_type type)
2245 struct line *line = &view->line[view->lines++];
2247 memset(line, 0, sizeof(*line));
2254 static struct line *
2255 add_line_text(struct view *view, char *data, enum line_type type)
2258 data = strdup(data);
2260 return data ? add_line_data(view, data, type) : NULL;
2269 OPEN_DEFAULT = 0, /* Use default view switching. */
2270 OPEN_SPLIT = 1, /* Split current view. */
2271 OPEN_BACKGROUNDED = 2, /* Backgrounded. */
2272 OPEN_RELOAD = 4, /* Reload view even if it is the current. */
2276 open_view(struct view *prev, enum request request, enum open_flags flags)
2278 bool backgrounded = !!(flags & OPEN_BACKGROUNDED);
2279 bool split = !!(flags & OPEN_SPLIT);
2280 bool reload = !!(flags & OPEN_RELOAD);
2281 struct view *view = VIEW(request);
2282 int nviews = displayed_views();
2283 struct view *base_view = display[0];
2285 if (view == prev && nviews == 1 && !reload) {
2286 report("Already in %s view", view->name);
2290 if (view->ops->open) {
2291 if (!view->ops->open(view)) {
2292 report("Failed to load %s view", view->name);
2296 } else if ((reload || strcmp(view->vid, view->id)) &&
2297 !begin_update(view)) {
2298 report("Failed to load %s view", view->name);
2307 /* Maximize the current view. */
2308 memset(display, 0, sizeof(display));
2310 display[current_view] = view;
2313 /* Resize the view when switching between split- and full-screen,
2314 * or when switching between two different full-screen views. */
2315 if (nviews != displayed_views() ||
2316 (nviews == 1 && base_view != display[0]))
2319 if (split && prev->lineno - prev->offset >= prev->height) {
2320 /* Take the title line into account. */
2321 int lines = prev->lineno - prev->offset - prev->height + 1;
2323 /* Scroll the view that was split if the current line is
2324 * outside the new limited view. */
2325 do_scroll_view(prev, lines);
2328 if (prev && view != prev) {
2329 if (split && !backgrounded) {
2330 /* "Blur" the previous view. */
2331 update_view_title(prev);
2334 view->parent = prev;
2337 if (view->pipe && view->lines == 0) {
2338 /* Clear the old view and let the incremental updating refill
2347 /* If the view is backgrounded the above calls to report()
2348 * won't redraw the view title. */
2350 update_view_title(view);
2354 open_external_viewer(const char *cmd)
2356 def_prog_mode(); /* save current tty modes */
2357 endwin(); /* restore original tty modes */
2359 fprintf(stderr, "Press Enter to continue");
2366 open_mergetool(const char *file)
2368 char cmd[SIZEOF_STR];
2369 char file_sq[SIZEOF_STR];
2371 if (sq_quote(file_sq, 0, file) < sizeof(file_sq) &&
2372 string_format(cmd, "git mergetool %s", file_sq)) {
2373 open_external_viewer(cmd);
2378 open_editor(bool from_root, const char *file)
2380 char cmd[SIZEOF_STR];
2381 char file_sq[SIZEOF_STR];
2383 char *prefix = from_root ? opt_cdup : "";
2385 editor = getenv("GIT_EDITOR");
2386 if (!editor && *opt_editor)
2387 editor = opt_editor;
2389 editor = getenv("VISUAL");
2391 editor = getenv("EDITOR");
2395 if (sq_quote(file_sq, 0, file) < sizeof(file_sq) &&
2396 string_format(cmd, "%s %s%s", editor, prefix, file_sq)) {
2397 open_external_viewer(cmd);
2402 open_run_request(enum request request)
2404 struct run_request *req = get_run_request(request);
2405 char buf[SIZEOF_STR * 2];
2410 report("Unknown run request");
2418 char *next = strstr(cmd, "%(");
2419 int len = next - cmd;
2426 } else if (!strncmp(next, "%(head)", 7)) {
2429 } else if (!strncmp(next, "%(commit)", 9)) {
2432 } else if (!strncmp(next, "%(blob)", 7)) {
2436 report("Unknown replacement in run request: `%s`", req->cmd);
2440 if (!string_format_from(buf, &bufpos, "%.*s%s", len, cmd, value))
2444 next = strchr(next, ')') + 1;
2448 open_external_viewer(buf);
2452 * User request switch noodle
2456 view_driver(struct view *view, enum request request)
2460 if (request == REQ_NONE) {
2465 if (request > REQ_NONE) {
2466 open_run_request(request);
2470 if (view && view->lines) {
2471 request = view->ops->request(view, request, &view->line[view->lineno]);
2472 if (request == REQ_NONE)
2479 case REQ_MOVE_PAGE_UP:
2480 case REQ_MOVE_PAGE_DOWN:
2481 case REQ_MOVE_FIRST_LINE:
2482 case REQ_MOVE_LAST_LINE:
2483 move_view(view, request);
2486 case REQ_SCROLL_LINE_DOWN:
2487 case REQ_SCROLL_LINE_UP:
2488 case REQ_SCROLL_PAGE_DOWN:
2489 case REQ_SCROLL_PAGE_UP:
2490 scroll_view(view, request);
2495 report("No file chosen, press %s to open tree view",
2496 get_key(REQ_VIEW_TREE));
2499 open_view(view, request, OPEN_DEFAULT);
2502 case REQ_VIEW_PAGER:
2503 if (!opt_pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
2504 report("No pager content, press %s to run command from prompt",
2505 get_key(REQ_PROMPT));
2508 open_view(view, request, OPEN_DEFAULT);
2511 case REQ_VIEW_STAGE:
2512 if (!VIEW(REQ_VIEW_STAGE)->lines) {
2513 report("No stage content, press %s to open the status view and choose file",
2514 get_key(REQ_VIEW_STATUS));
2517 open_view(view, request, OPEN_DEFAULT);
2520 case REQ_VIEW_STATUS:
2521 if (opt_is_inside_work_tree == FALSE) {
2522 report("The status view requires a working tree");
2525 open_view(view, request, OPEN_DEFAULT);
2533 open_view(view, request, OPEN_DEFAULT);
2538 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
2540 if ((view == VIEW(REQ_VIEW_DIFF) &&
2541 view->parent == VIEW(REQ_VIEW_MAIN)) ||
2542 (view == VIEW(REQ_VIEW_STAGE) &&
2543 view->parent == VIEW(REQ_VIEW_STATUS)) ||
2544 (view == VIEW(REQ_VIEW_BLOB) &&
2545 view->parent == VIEW(REQ_VIEW_TREE))) {
2548 view = view->parent;
2549 line = view->lineno;
2550 move_view(view, request);
2551 if (view_is_displayed(view))
2552 update_view_title(view);
2553 if (line != view->lineno)
2554 view->ops->request(view, REQ_ENTER,
2555 &view->line[view->lineno]);
2558 move_view(view, request);
2564 int nviews = displayed_views();
2565 int next_view = (current_view + 1) % nviews;
2567 if (next_view == current_view) {
2568 report("Only one view is displayed");
2572 current_view = next_view;
2573 /* Blur out the title of the previous view. */
2574 update_view_title(view);
2579 report("Refreshing is not yet supported for the %s view", view->name);
2582 case REQ_TOGGLE_LINENO:
2583 opt_line_number = !opt_line_number;
2587 case REQ_TOGGLE_DATE:
2588 opt_date = !opt_date;
2592 case REQ_TOGGLE_AUTHOR:
2593 opt_author = !opt_author;
2597 case REQ_TOGGLE_REV_GRAPH:
2598 opt_rev_graph = !opt_rev_graph;
2602 case REQ_TOGGLE_REFS:
2603 opt_show_refs = !opt_show_refs;
2608 /* Always reload^Wrerun commands from the prompt. */
2609 open_view(view, opt_request, OPEN_RELOAD);
2613 case REQ_SEARCH_BACK:
2614 search_view(view, request);
2619 find_next(view, request);
2622 case REQ_STOP_LOADING:
2623 for (i = 0; i < ARRAY_SIZE(views); i++) {
2626 report("Stopped loading the %s view", view->name),
2631 case REQ_SHOW_VERSION:
2632 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
2635 case REQ_SCREEN_RESIZE:
2638 case REQ_SCREEN_REDRAW:
2643 report("Nothing to edit");
2648 report("Nothing to enter");
2652 case REQ_VIEW_CLOSE:
2653 /* XXX: Mark closed views by letting view->parent point to the
2654 * view itself. Parents to closed view should never be
2657 view->parent->parent != view->parent) {
2658 memset(display, 0, sizeof(display));
2660 display[current_view] = view->parent;
2661 view->parent = view;
2671 /* An unknown key will show most commonly used commands. */
2672 report("Unknown key, press 'h' for help");
2685 pager_draw(struct view *view, struct line *line, unsigned int lineno, bool selected)
2687 char *text = line->data;
2688 enum line_type type = line->type;
2691 wmove(view->win, lineno, 0);
2695 wchgat(view->win, -1, 0, type, NULL);
2698 attr = get_line_attr(type);
2699 wattrset(view->win, attr);
2701 if (opt_line_number || opt_tab_size < TABSIZE) {
2702 static char spaces[] = " ";
2703 int col_offset = 0, col = 0;
2705 if (opt_line_number) {
2706 unsigned long real_lineno = view->offset + lineno + 1;
2708 if (real_lineno == 1 ||
2709 (real_lineno % opt_num_interval) == 0) {
2710 wprintw(view->win, "%.*d", view->digits, real_lineno);
2713 waddnstr(view->win, spaces,
2714 MIN(view->digits, STRING_SIZE(spaces)));
2716 waddstr(view->win, ": ");
2717 col_offset = view->digits + 2;
2720 while (text && col_offset + col < view->width) {
2721 int cols_max = view->width - col_offset - col;
2725 if (*text == '\t') {
2727 assert(sizeof(spaces) > TABSIZE);
2729 cols = opt_tab_size - (col % opt_tab_size);
2732 text = strchr(text, '\t');
2733 cols = line ? text - pos : strlen(pos);
2736 waddnstr(view->win, pos, MIN(cols, cols_max));
2741 int tilde_attr = get_line_attr(LINE_MAIN_DELIM);
2743 draw_text(view, text, view->width, TRUE, tilde_attr);
2750 add_describe_ref(char *buf, size_t *bufpos, char *commit_id, const char *sep)
2752 char refbuf[SIZEOF_STR];
2756 if (!string_format(refbuf, "git describe %s 2>/dev/null", commit_id))
2759 pipe = popen(refbuf, "r");
2763 if ((ref = fgets(refbuf, sizeof(refbuf), pipe)))
2764 ref = chomp_string(ref);
2770 /* This is the only fatal call, since it can "corrupt" the buffer. */
2771 if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
2778 add_pager_refs(struct view *view, struct line *line)
2780 char buf[SIZEOF_STR];
2781 char *commit_id = (char *)line->data + STRING_SIZE("commit ");
2783 size_t bufpos = 0, refpos = 0;
2784 const char *sep = "Refs: ";
2785 bool is_tag = FALSE;
2787 assert(line->type == LINE_COMMIT);
2789 refs = get_refs(commit_id);
2791 if (view == VIEW(REQ_VIEW_DIFF))
2792 goto try_add_describe_ref;
2797 struct ref *ref = refs[refpos];
2798 char *fmt = ref->tag ? "%s[%s]" :
2799 ref->remote ? "%s<%s>" : "%s%s";
2801 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
2806 } while (refs[refpos++]->next);
2808 if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
2809 try_add_describe_ref:
2810 /* Add <tag>-g<commit_id> "fake" reference. */
2811 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
2818 if (!realloc_lines(view, view->line_size + 1))
2821 add_line_text(view, buf, LINE_PP_REFS);
2825 pager_read(struct view *view, char *data)
2832 line = add_line_text(view, data, get_line_type(data));
2836 if (line->type == LINE_COMMIT &&
2837 (view == VIEW(REQ_VIEW_DIFF) ||
2838 view == VIEW(REQ_VIEW_LOG)))
2839 add_pager_refs(view, line);
2845 pager_request(struct view *view, enum request request, struct line *line)
2849 if (request != REQ_ENTER)
2852 if (line->type == LINE_COMMIT &&
2853 (view == VIEW(REQ_VIEW_LOG) ||
2854 view == VIEW(REQ_VIEW_PAGER))) {
2855 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
2859 /* Always scroll the view even if it was split. That way
2860 * you can use Enter to scroll through the log view and
2861 * split open each commit diff. */
2862 scroll_view(view, REQ_SCROLL_LINE_DOWN);
2864 /* FIXME: A minor workaround. Scrolling the view will call report("")
2865 * but if we are scrolling a non-current view this won't properly
2866 * update the view title. */
2868 update_view_title(view);
2874 pager_grep(struct view *view, struct line *line)
2877 char *text = line->data;
2882 if (regexec(view->regex, text, 1, &pmatch, 0) == REG_NOMATCH)
2889 pager_select(struct view *view, struct line *line)
2891 if (line->type == LINE_COMMIT) {
2892 char *text = (char *)line->data + STRING_SIZE("commit ");
2894 if (view != VIEW(REQ_VIEW_PAGER))
2895 string_copy_rev(view->ref, text);
2896 string_copy_rev(ref_commit, text);
2900 static struct view_ops pager_ops = {
2916 help_open(struct view *view)
2919 int lines = ARRAY_SIZE(req_info) + 2;
2922 if (view->lines > 0)
2925 for (i = 0; i < ARRAY_SIZE(req_info); i++)
2926 if (!req_info[i].request)
2929 lines += run_requests + 1;
2931 view->line = calloc(lines, sizeof(*view->line));
2935 add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
2937 for (i = 0; i < ARRAY_SIZE(req_info); i++) {
2940 if (req_info[i].request == REQ_NONE)
2943 if (!req_info[i].request) {
2944 add_line_text(view, "", LINE_DEFAULT);
2945 add_line_text(view, req_info[i].help, LINE_DEFAULT);
2949 key = get_key(req_info[i].request);
2951 key = "(no key defined)";
2953 if (!string_format(buf, " %-25s %s", key, req_info[i].help))
2956 add_line_text(view, buf, LINE_DEFAULT);
2960 add_line_text(view, "", LINE_DEFAULT);
2961 add_line_text(view, "External commands:", LINE_DEFAULT);
2964 for (i = 0; i < run_requests; i++) {
2965 struct run_request *req = get_run_request(REQ_NONE + i + 1);
2971 key = get_key_name(req->key);
2973 key = "(no key defined)";
2975 if (!string_format(buf, " %-10s %-14s `%s`",
2976 keymap_table[req->keymap].name,
2980 add_line_text(view, buf, LINE_DEFAULT);
2986 static struct view_ops help_ops = {
3001 struct tree_stack_entry {
3002 struct tree_stack_entry *prev; /* Entry below this in the stack */
3003 unsigned long lineno; /* Line number to restore */
3004 char *name; /* Position of name in opt_path */
3007 /* The top of the path stack. */
3008 static struct tree_stack_entry *tree_stack = NULL;
3009 unsigned long tree_lineno = 0;
3012 pop_tree_stack_entry(void)
3014 struct tree_stack_entry *entry = tree_stack;
3016 tree_lineno = entry->lineno;
3018 tree_stack = entry->prev;
3023 push_tree_stack_entry(char *name, unsigned long lineno)
3025 struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
3026 size_t pathlen = strlen(opt_path);
3031 entry->prev = tree_stack;
3032 entry->name = opt_path + pathlen;
3035 if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
3036 pop_tree_stack_entry();
3040 /* Move the current line to the first tree entry. */
3042 entry->lineno = lineno;
3045 /* Parse output from git-ls-tree(1):
3047 * 100644 blob fb0e31ea6cc679b7379631188190e975f5789c26 Makefile
3048 * 100644 blob 5304ca4260aaddaee6498f9630e7d471b8591ea6 README
3049 * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
3050 * 100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38 web.conf
3053 #define SIZEOF_TREE_ATTR \
3054 STRING_SIZE("100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38\t")
3056 #define TREE_UP_FORMAT "040000 tree %s\t.."
3059 tree_compare_entry(enum line_type type1, char *name1,
3060 enum line_type type2, char *name2)
3062 if (type1 != type2) {
3063 if (type1 == LINE_TREE_DIR)
3068 return strcmp(name1, name2);
3072 tree_read(struct view *view, char *text)
3074 size_t textlen = text ? strlen(text) : 0;
3075 char buf[SIZEOF_STR];
3077 enum line_type type;
3078 bool first_read = view->lines == 0;
3080 if (textlen <= SIZEOF_TREE_ATTR)
3083 type = text[STRING_SIZE("100644 ")] == 't'
3084 ? LINE_TREE_DIR : LINE_TREE_FILE;
3087 /* Add path info line */
3088 if (!string_format(buf, "Directory path /%s", opt_path) ||
3089 !realloc_lines(view, view->line_size + 1) ||
3090 !add_line_text(view, buf, LINE_DEFAULT))
3093 /* Insert "link" to parent directory. */
3095 if (!string_format(buf, TREE_UP_FORMAT, view->ref) ||
3096 !realloc_lines(view, view->line_size + 1) ||
3097 !add_line_text(view, buf, LINE_TREE_DIR))
3102 /* Strip the path part ... */
3104 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
3105 size_t striplen = strlen(opt_path);
3106 char *path = text + SIZEOF_TREE_ATTR;
3108 if (pathlen > striplen)
3109 memmove(path, path + striplen,
3110 pathlen - striplen + 1);
3113 /* Skip "Directory ..." and ".." line. */
3114 for (pos = 1 + !!*opt_path; pos < view->lines; pos++) {
3115 struct line *line = &view->line[pos];
3116 char *path1 = ((char *) line->data) + SIZEOF_TREE_ATTR;
3117 char *path2 = text + SIZEOF_TREE_ATTR;
3118 int cmp = tree_compare_entry(line->type, path1, type, path2);
3123 text = strdup(text);
3127 if (view->lines > pos)
3128 memmove(&view->line[pos + 1], &view->line[pos],
3129 (view->lines - pos) * sizeof(*line));
3131 line = &view->line[pos];
3138 if (!add_line_text(view, text, type))
3141 if (tree_lineno > view->lineno) {
3142 view->lineno = tree_lineno;
3150 tree_request(struct view *view, enum request request, struct line *line)
3152 enum open_flags flags;
3154 if (request == REQ_TREE_PARENT) {
3157 request = REQ_ENTER;
3158 line = &view->line[1];
3160 /* quit view if at top of tree */
3161 return REQ_VIEW_CLOSE;
3164 if (request != REQ_ENTER)
3167 /* Cleanup the stack if the tree view is at a different tree. */
3168 while (!*opt_path && tree_stack)
3169 pop_tree_stack_entry();
3171 switch (line->type) {
3173 /* Depending on whether it is a subdir or parent (updir?) link
3174 * mangle the path buffer. */
3175 if (line == &view->line[1] && *opt_path) {
3176 pop_tree_stack_entry();
3179 char *data = line->data;
3180 char *basename = data + SIZEOF_TREE_ATTR;
3182 push_tree_stack_entry(basename, view->lineno);
3185 /* Trees and subtrees share the same ID, so they are not not
3186 * unique like blobs. */
3187 flags = OPEN_RELOAD;
3188 request = REQ_VIEW_TREE;
3191 case LINE_TREE_FILE:
3192 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
3193 request = REQ_VIEW_BLOB;
3200 open_view(view, request, flags);
3201 if (request == REQ_VIEW_TREE) {
3202 view->lineno = tree_lineno;
3209 tree_select(struct view *view, struct line *line)
3211 char *text = (char *)line->data + STRING_SIZE("100644 blob ");
3213 if (line->type == LINE_TREE_FILE) {
3214 string_copy_rev(ref_blob, text);
3216 } else if (line->type != LINE_TREE_DIR) {
3220 string_copy_rev(view->ref, text);
3223 static struct view_ops tree_ops = {
3234 blob_read(struct view *view, char *line)
3236 return add_line_text(view, line, LINE_DEFAULT) != NULL;
3239 static struct view_ops blob_ops = {
3258 char rev[SIZEOF_REV];
3259 char name[SIZEOF_STR];
3263 char rev[SIZEOF_REV];
3264 char name[SIZEOF_STR];
3268 static struct status stage_status;
3269 static enum line_type stage_line_type;
3271 /* Get fields from the diff line:
3272 * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
3275 status_get_diff(struct status *file, char *buf, size_t bufsize)
3277 char *old_mode = buf + 1;
3278 char *new_mode = buf + 8;
3279 char *old_rev = buf + 15;
3280 char *new_rev = buf + 56;
3281 char *status = buf + 97;
3284 old_mode[-1] != ':' ||
3285 new_mode[-1] != ' ' ||
3286 old_rev[-1] != ' ' ||
3287 new_rev[-1] != ' ' ||
3291 file->status = *status;
3293 string_copy_rev(file->old.rev, old_rev);
3294 string_copy_rev(file->new.rev, new_rev);
3296 file->old.mode = strtoul(old_mode, NULL, 8);
3297 file->new.mode = strtoul(new_mode, NULL, 8);
3299 file->old.name[0] = file->new.name[0] = 0;
3305 status_run(struct view *view, const char cmd[], bool diff, enum line_type type)
3307 struct status *file = NULL;
3308 struct status *unmerged = NULL;
3309 char buf[SIZEOF_STR * 4];
3313 pipe = popen(cmd, "r");
3317 add_line_data(view, NULL, type);
3319 while (!feof(pipe) && !ferror(pipe)) {
3323 readsize = fread(buf + bufsize, 1, sizeof(buf) - bufsize, pipe);
3326 bufsize += readsize;
3328 /* Process while we have NUL chars. */
3329 while ((sep = memchr(buf, 0, bufsize))) {
3330 size_t sepsize = sep - buf + 1;
3333 if (!realloc_lines(view, view->line_size + 1))
3336 file = calloc(1, sizeof(*file));
3340 add_line_data(view, file, type);
3343 /* Parse diff info part. */
3347 } else if (!file->status) {
3348 if (!status_get_diff(file, buf, sepsize))
3352 memmove(buf, sep + 1, bufsize);
3354 sep = memchr(buf, 0, bufsize);
3357 sepsize = sep - buf + 1;
3359 /* Collapse all 'M'odified entries that
3360 * follow a associated 'U'nmerged entry.
3362 if (file->status == 'U') {
3365 } else if (unmerged) {
3366 int collapse = !strcmp(buf, unmerged->new.name);
3377 /* Grab the old name for rename/copy. */
3378 if (!*file->old.name &&
3379 (file->status == 'R' || file->status == 'C')) {
3380 sepsize = sep - buf + 1;
3381 string_ncopy(file->old.name, buf, sepsize);
3383 memmove(buf, sep + 1, bufsize);
3385 sep = memchr(buf, 0, bufsize);
3388 sepsize = sep - buf + 1;
3391 /* git-ls-files just delivers a NUL separated
3392 * list of file names similar to the second half
3393 * of the git-diff-* output. */
3394 string_ncopy(file->new.name, buf, sepsize);
3395 if (!*file->old.name)
3396 string_copy(file->old.name, file->new.name);
3398 memmove(buf, sep + 1, bufsize);
3409 if (!view->line[view->lines - 1].data)
3410 add_line_data(view, NULL, LINE_STAT_NONE);
3416 /* Don't show unmerged entries in the staged section. */
3417 #define STATUS_DIFF_INDEX_CMD "git diff-index -z --diff-filter=ACDMRTXB --cached -M HEAD"
3418 #define STATUS_DIFF_FILES_CMD "git diff-files -z"
3419 #define STATUS_LIST_OTHER_CMD \
3420 "git ls-files -z --others --exclude-per-directory=.gitignore"
3422 #define STATUS_DIFF_INDEX_SHOW_CMD \
3423 "git diff-index --root --patch-with-stat -C -M --cached HEAD -- %s %s 2>/dev/null"
3425 #define STATUS_DIFF_FILES_SHOW_CMD \
3426 "git diff-files --root --patch-with-stat -C -M -- %s %s 2>/dev/null"
3428 /* First parse staged info using git-diff-index(1), then parse unstaged
3429 * info using git-diff-files(1), and finally untracked files using
3430 * git-ls-files(1). */
3432 status_open(struct view *view)
3434 struct stat statbuf;
3435 char exclude[SIZEOF_STR];
3436 char cmd[SIZEOF_STR];
3437 unsigned long prev_lineno = view->lineno;
3440 for (i = 0; i < view->lines; i++)
3441 free(view->line[i].data);
3443 view->lines = view->line_alloc = view->line_size = view->lineno = 0;
3446 if (!realloc_lines(view, view->line_size + 6))
3449 if (!string_format(exclude, "%s/info/exclude", opt_git_dir))
3452 string_copy(cmd, STATUS_LIST_OTHER_CMD);
3454 if (stat(exclude, &statbuf) >= 0) {
3455 size_t cmdsize = strlen(cmd);
3457 if (!string_format_from(cmd, &cmdsize, " %s", "--exclude-from=") ||
3458 sq_quote(cmd, cmdsize, exclude) >= sizeof(cmd))
3462 system("git update-index -q --refresh");
3464 if (!status_run(view, STATUS_DIFF_INDEX_CMD, TRUE, LINE_STAT_STAGED) ||
3465 !status_run(view, STATUS_DIFF_FILES_CMD, TRUE, LINE_STAT_UNSTAGED) ||
3466 !status_run(view, cmd, FALSE, LINE_STAT_UNTRACKED))
3469 /* If all went well restore the previous line number to stay in
3471 if (prev_lineno < view->lines)
3472 view->lineno = prev_lineno;
3474 view->lineno = view->lines - 1;
3480 status_draw(struct view *view, struct line *line, unsigned int lineno, bool selected)
3482 struct status *status = line->data;
3483 int tilde_attr = get_line_attr(LINE_MAIN_DELIM);
3485 wmove(view->win, lineno, 0);
3488 wattrset(view->win, get_line_attr(LINE_CURSOR));
3489 wchgat(view->win, -1, 0, LINE_CURSOR, NULL);
3492 } else if (!status && line->type != LINE_STAT_NONE) {
3493 wattrset(view->win, get_line_attr(LINE_STAT_SECTION));
3494 wchgat(view->win, -1, 0, LINE_STAT_SECTION, NULL);
3497 wattrset(view->win, get_line_attr(line->type));
3503 switch (line->type) {
3504 case LINE_STAT_STAGED:
3505 text = "Changes to be committed:";
3508 case LINE_STAT_UNSTAGED:
3509 text = "Changed but not updated:";
3512 case LINE_STAT_UNTRACKED:
3513 text = "Untracked files:";
3516 case LINE_STAT_NONE:
3517 text = " (no files)";
3524 draw_text(view, text, view->width, TRUE, tilde_attr);
3528 waddch(view->win, status->status);
3530 wattrset(view->win, A_NORMAL);
3531 wmove(view->win, lineno, 4);
3532 if (view->width < 5)
3535 draw_text(view, status->new.name, view->width - 5, TRUE, tilde_attr);
3540 status_enter(struct view *view, struct line *line)
3542 struct status *status = line->data;
3543 char oldpath[SIZEOF_STR] = "";
3544 char newpath[SIZEOF_STR] = "";
3548 if (line->type == LINE_STAT_NONE ||
3549 (!status && line[1].type == LINE_STAT_NONE)) {
3550 report("No file to diff");
3555 if (sq_quote(oldpath, 0, status->old.name) >= sizeof(oldpath))
3557 /* Diffs for unmerged entries are empty when pasing the
3558 * new path, so leave it empty. */
3559 if (status->status != 'U' &&
3560 sq_quote(newpath, 0, status->new.name) >= sizeof(newpath))
3565 line->type != LINE_STAT_UNTRACKED &&
3566 !string_format_from(opt_cmd, &cmdsize, "cd %s;", opt_cdup))
3569 switch (line->type) {
3570 case LINE_STAT_STAGED:
3571 if (!string_format_from(opt_cmd, &cmdsize,
3572 STATUS_DIFF_INDEX_SHOW_CMD, oldpath, newpath))
3575 info = "Staged changes to %s";
3577 info = "Staged changes";
3580 case LINE_STAT_UNSTAGED:
3581 if (!string_format_from(opt_cmd, &cmdsize,
3582 STATUS_DIFF_FILES_SHOW_CMD, oldpath, newpath))
3585 info = "Unstaged changes to %s";
3587 info = "Unstaged changes";
3590 case LINE_STAT_UNTRACKED:
3596 report("No file to show");
3600 opt_pipe = fopen(status->new.name, "r");
3601 info = "Untracked file %s";
3605 die("line type %d not handled in switch", line->type);
3608 open_view(view, REQ_VIEW_STAGE, OPEN_RELOAD | OPEN_SPLIT);
3609 if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
3611 stage_status = *status;
3613 memset(&stage_status, 0, sizeof(stage_status));
3616 stage_line_type = line->type;
3617 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
3625 status_update_file(struct view *view, struct status *status, enum line_type type)
3627 char cmd[SIZEOF_STR];
3628 char buf[SIZEOF_STR];
3635 type != LINE_STAT_UNTRACKED &&
3636 !string_format_from(cmd, &cmdsize, "cd %s;", opt_cdup))
3640 case LINE_STAT_STAGED:
3641 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
3644 status->old.name, 0))
3647 string_add(cmd, cmdsize, "git update-index -z --index-info");
3650 case LINE_STAT_UNSTAGED:
3651 case LINE_STAT_UNTRACKED:
3652 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
3655 string_add(cmd, cmdsize, "git update-index -z --add --remove --stdin");
3659 die("line type %d not handled in switch", type);
3662 pipe = popen(cmd, "w");
3666 while (!ferror(pipe) && written < bufsize) {
3667 written += fwrite(buf + written, 1, bufsize - written, pipe);
3672 if (written != bufsize)
3679 status_update(struct view *view)
3681 struct line *line = &view->line[view->lineno];
3683 assert(view->lines);
3686 while (++line < view->line + view->lines && line->data) {
3687 if (!status_update_file(view, line->data, line->type))
3688 report("Failed to update file status");
3691 if (!line[-1].data) {
3692 report("Nothing to update");
3696 } else if (!status_update_file(view, line->data, line->type)) {
3697 report("Failed to update file status");
3702 status_request(struct view *view, enum request request, struct line *line)
3704 struct status *status = line->data;
3707 case REQ_STATUS_UPDATE:
3708 status_update(view);
3711 case REQ_STATUS_MERGE:
3712 if (!status || status->status != 'U') {
3713 report("Merging only possible for files with unmerged status ('U').");
3716 open_mergetool(status->new.name);
3723 open_editor(status->status != '?', status->new.name);
3727 /* After returning the status view has been split to
3728 * show the stage view. No further reloading is
3730 status_enter(view, line);
3734 /* Simply reload the view. */
3741 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
3747 status_select(struct view *view, struct line *line)
3749 struct status *status = line->data;
3750 char file[SIZEOF_STR] = "all files";
3754 if (status && !string_format(file, "'%s'", status->new.name))
3757 if (!status && line[1].type == LINE_STAT_NONE)
3760 switch (line->type) {
3761 case LINE_STAT_STAGED:
3762 text = "Press %s to unstage %s for commit";
3765 case LINE_STAT_UNSTAGED:
3766 text = "Press %s to stage %s for commit";
3769 case LINE_STAT_UNTRACKED:
3770 text = "Press %s to stage %s for addition";
3773 case LINE_STAT_NONE:
3774 text = "Nothing to update";
3778 die("line type %d not handled in switch", line->type);
3781 if (status && status->status == 'U') {
3782 text = "Press %s to resolve conflict in %s";
3783 key = get_key(REQ_STATUS_MERGE);
3786 key = get_key(REQ_STATUS_UPDATE);
3789 string_format(view->ref, text, key, file);
3793 status_grep(struct view *view, struct line *line)
3795 struct status *status = line->data;
3796 enum { S_STATUS, S_NAME, S_END } state;
3803 for (state = S_STATUS; state < S_END; state++) {
3807 case S_NAME: text = status->new.name; break;
3809 buf[0] = status->status;
3817 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
3824 static struct view_ops status_ops = {
3836 stage_diff_line(FILE *pipe, struct line *line)
3838 char *buf = line->data;
3839 size_t bufsize = strlen(buf);
3842 while (!ferror(pipe) && written < bufsize) {
3843 written += fwrite(buf + written, 1, bufsize - written, pipe);
3848 return written == bufsize;
3851 static struct line *
3852 stage_diff_hdr(struct view *view, struct line *line)
3854 int diff_hdr_dir = line->type == LINE_DIFF_CHUNK ? -1 : 1;
3855 struct line *diff_hdr;
3857 if (line->type == LINE_DIFF_CHUNK)
3858 diff_hdr = line - 1;
3860 diff_hdr = view->line + 1;
3862 while (diff_hdr > view->line && diff_hdr < view->line + view->lines) {
3863 if (diff_hdr->type == LINE_DIFF_HEADER)
3866 diff_hdr += diff_hdr_dir;
3873 stage_update_chunk(struct view *view, struct line *line)
3875 char cmd[SIZEOF_STR];
3877 struct line *diff_hdr, *diff_chunk, *diff_end;
3880 diff_hdr = stage_diff_hdr(view, line);
3885 !string_format_from(cmd, &cmdsize, "cd %s;", opt_cdup))
3888 if (!string_format_from(cmd, &cmdsize,
3889 "git apply --cached %s - && "
3890 "git update-index -q --unmerged --refresh 2>/dev/null",
3891 stage_line_type == LINE_STAT_STAGED ? "-R" : ""))
3894 pipe = popen(cmd, "w");
3898 diff_end = view->line + view->lines;
3899 if (line->type != LINE_DIFF_CHUNK) {
3900 diff_chunk = diff_hdr;
3903 for (diff_chunk = line + 1; diff_chunk < diff_end; diff_chunk++)
3904 if (diff_chunk->type == LINE_DIFF_CHUNK ||
3905 diff_chunk->type == LINE_DIFF_HEADER)
3906 diff_end = diff_chunk;
3910 while (diff_hdr->type != LINE_DIFF_CHUNK) {
3911 switch (diff_hdr->type) {
3912 case LINE_DIFF_HEADER:
3913 case LINE_DIFF_INDEX:
3923 if (!stage_diff_line(pipe, diff_hdr++)) {
3930 while (diff_chunk < diff_end && stage_diff_line(pipe, diff_chunk))
3935 if (diff_chunk != diff_end)
3942 stage_update(struct view *view, struct line *line)
3944 if (stage_line_type != LINE_STAT_UNTRACKED &&
3945 (line->type == LINE_DIFF_CHUNK || !stage_status.status)) {
3946 if (!stage_update_chunk(view, line)) {
3947 report("Failed to apply chunk");
3951 } else if (!status_update_file(view, &stage_status, stage_line_type)) {
3952 report("Failed to update file");
3956 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
3958 view = VIEW(REQ_VIEW_STATUS);
3959 if (view_is_displayed(view))
3960 status_enter(view, &view->line[view->lineno]);
3964 stage_request(struct view *view, enum request request, struct line *line)
3967 case REQ_STATUS_UPDATE:
3968 stage_update(view, line);
3972 if (!stage_status.new.name[0])
3975 open_editor(stage_status.status != '?', stage_status.new.name);
3979 pager_request(view, request, line);
3989 static struct view_ops stage_ops = {
4005 char id[SIZEOF_REV]; /* SHA1 ID. */
4006 char title[128]; /* First line of the commit message. */
4007 char author[75]; /* Author of the commit. */
4008 struct tm time; /* Date from the author ident. */
4009 struct ref **refs; /* Repository references. */
4010 chtype graph[SIZEOF_REVGRAPH]; /* Ancestry chain graphics. */
4011 size_t graph_size; /* The width of the graph array. */
4012 bool has_parents; /* Rewritten --parents seen. */
4015 /* Size of rev graph with no "padding" columns */
4016 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
4019 struct rev_graph *prev, *next, *parents;
4020 char rev[SIZEOF_REVITEMS][SIZEOF_REV];
4022 struct commit *commit;
4024 unsigned int boundary:1;
4027 /* Parents of the commit being visualized. */
4028 static struct rev_graph graph_parents[4];
4030 /* The current stack of revisions on the graph. */
4031 static struct rev_graph graph_stacks[4] = {
4032 { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
4033 { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
4034 { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
4035 { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
4039 graph_parent_is_merge(struct rev_graph *graph)
4041 return graph->parents->size > 1;
4045 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
4047 struct commit *commit = graph->commit;
4049 if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
4050 commit->graph[commit->graph_size++] = symbol;
4054 done_rev_graph(struct rev_graph *graph)
4056 if (graph_parent_is_merge(graph) &&
4057 graph->pos < graph->size - 1 &&
4058 graph->next->size == graph->size + graph->parents->size - 1) {
4059 size_t i = graph->pos + graph->parents->size - 1;
4061 graph->commit->graph_size = i * 2;
4062 while (i < graph->next->size - 1) {
4063 append_to_rev_graph(graph, ' ');
4064 append_to_rev_graph(graph, '\\');
4069 graph->size = graph->pos = 0;
4070 graph->commit = NULL;
4071 memset(graph->parents, 0, sizeof(*graph->parents));
4075 push_rev_graph(struct rev_graph *graph, char *parent)
4079 /* "Collapse" duplicate parents lines.
4081 * FIXME: This needs to also update update the drawn graph but
4082 * for now it just serves as a method for pruning graph lines. */
4083 for (i = 0; i < graph->size; i++)
4084 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
4087 if (graph->size < SIZEOF_REVITEMS) {
4088 string_copy_rev(graph->rev[graph->size++], parent);
4093 get_rev_graph_symbol(struct rev_graph *graph)
4097 if (graph->boundary)
4098 symbol = REVGRAPH_BOUND;
4099 else if (graph->parents->size == 0)
4100 symbol = REVGRAPH_INIT;
4101 else if (graph_parent_is_merge(graph))
4102 symbol = REVGRAPH_MERGE;
4103 else if (graph->pos >= graph->size)
4104 symbol = REVGRAPH_BRANCH;
4106 symbol = REVGRAPH_COMMIT;
4112 draw_rev_graph(struct rev_graph *graph)
4115 chtype separator, line;
4117 enum { DEFAULT, RSHARP, RDIAG, LDIAG };
4118 static struct rev_filler fillers[] = {
4119 { ' ', REVGRAPH_LINE },
4124 chtype symbol = get_rev_graph_symbol(graph);
4125 struct rev_filler *filler;
4128 filler = &fillers[DEFAULT];
4130 for (i = 0; i < graph->pos; i++) {
4131 append_to_rev_graph(graph, filler->line);
4132 if (graph_parent_is_merge(graph->prev) &&
4133 graph->prev->pos == i)
4134 filler = &fillers[RSHARP];
4136 append_to_rev_graph(graph, filler->separator);
4139 /* Place the symbol for this revision. */
4140 append_to_rev_graph(graph, symbol);
4142 if (graph->prev->size > graph->size)
4143 filler = &fillers[RDIAG];
4145 filler = &fillers[DEFAULT];
4149 for (; i < graph->size; i++) {
4150 append_to_rev_graph(graph, filler->separator);
4151 append_to_rev_graph(graph, filler->line);
4152 if (graph_parent_is_merge(graph->prev) &&
4153 i < graph->prev->pos + graph->parents->size)
4154 filler = &fillers[RSHARP];
4155 if (graph->prev->size > graph->size)
4156 filler = &fillers[LDIAG];
4159 if (graph->prev->size > graph->size) {
4160 append_to_rev_graph(graph, filler->separator);
4161 if (filler->line != ' ')
4162 append_to_rev_graph(graph, filler->line);
4166 /* Prepare the next rev graph */
4168 prepare_rev_graph(struct rev_graph *graph)
4172 /* First, traverse all lines of revisions up to the active one. */
4173 for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
4174 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
4177 push_rev_graph(graph->next, graph->rev[graph->pos]);
4180 /* Interleave the new revision parent(s). */
4181 for (i = 0; !graph->boundary && i < graph->parents->size; i++)
4182 push_rev_graph(graph->next, graph->parents->rev[i]);
4184 /* Lastly, put any remaining revisions. */
4185 for (i = graph->pos + 1; i < graph->size; i++)
4186 push_rev_graph(graph->next, graph->rev[i]);
4190 update_rev_graph(struct rev_graph *graph)
4192 /* If this is the finalizing update ... */
4194 prepare_rev_graph(graph);
4196 /* Graph visualization needs a one rev look-ahead,
4197 * so the first update doesn't visualize anything. */
4198 if (!graph->prev->commit)
4201 draw_rev_graph(graph->prev);
4202 done_rev_graph(graph->prev->prev);
4211 main_draw(struct view *view, struct line *line, unsigned int lineno, bool selected)
4213 char buf[DATE_COLS + 1];
4214 struct commit *commit = line->data;
4215 enum line_type type;
4221 if (!*commit->author)
4224 space = view->width;
4225 wmove(view->win, lineno, col);
4229 wattrset(view->win, get_line_attr(type));
4230 wchgat(view->win, -1, 0, type, NULL);
4233 type = LINE_MAIN_COMMIT;
4234 wattrset(view->win, get_line_attr(LINE_MAIN_DATE));
4235 tilde_attr = get_line_attr(LINE_MAIN_DELIM);
4241 timelen = strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time);
4242 n = draw_text(view, buf, view->width - col, FALSE, tilde_attr);
4243 draw_text(view, " ", view->width - col - n, FALSE, tilde_attr);
4246 wmove(view->win, lineno, col);
4247 if (col >= view->width)
4250 if (type != LINE_CURSOR)
4251 wattrset(view->win, get_line_attr(LINE_MAIN_AUTHOR));
4256 max_len = view->width - col;
4257 if (max_len > AUTHOR_COLS - 1)
4258 max_len = AUTHOR_COLS - 1;
4259 draw_text(view, commit->author, max_len, TRUE, tilde_attr);
4261 if (col >= view->width)
4265 if (opt_rev_graph && commit->graph_size) {
4266 size_t graph_size = view->width - col;
4269 if (type != LINE_CURSOR)
4270 wattrset(view->win, get_line_attr(LINE_MAIN_REVGRAPH));
4271 wmove(view->win, lineno, col);
4272 if (graph_size > commit->graph_size)
4273 graph_size = commit->graph_size;
4274 /* Using waddch() instead of waddnstr() ensures that
4275 * they'll be rendered correctly for the cursor line. */
4276 for (i = 0; i < graph_size; i++)
4277 waddch(view->win, commit->graph[i]);
4279 col += commit->graph_size + 1;
4280 if (col >= view->width)
4282 waddch(view->win, ' ');
4284 if (type != LINE_CURSOR)
4285 wattrset(view->win, A_NORMAL);
4287 wmove(view->win, lineno, col);
4289 if (opt_show_refs && commit->refs) {
4293 if (type == LINE_CURSOR)
4295 else if (commit->refs[i]->ltag)
4296 wattrset(view->win, get_line_attr(LINE_MAIN_LOCAL_TAG));
4297 else if (commit->refs[i]->tag)
4298 wattrset(view->win, get_line_attr(LINE_MAIN_TAG));
4299 else if (commit->refs[i]->remote)
4300 wattrset(view->win, get_line_attr(LINE_MAIN_REMOTE));
4302 wattrset(view->win, get_line_attr(LINE_MAIN_REF));
4304 col += draw_text(view, "[", view->width - col, TRUE, tilde_attr);
4305 col += draw_text(view, commit->refs[i]->name, view->width - col,
4307 col += draw_text(view, "]", view->width - col, TRUE, tilde_attr);
4308 if (type != LINE_CURSOR)
4309 wattrset(view->win, A_NORMAL);
4310 col += draw_text(view, " ", view->width - col, TRUE, tilde_attr);
4311 if (col >= view->width)
4313 } while (commit->refs[i++]->next);
4316 if (type != LINE_CURSOR)
4317 wattrset(view->win, get_line_attr(type));
4319 draw_text(view, commit->title, view->width - col, TRUE, tilde_attr);
4323 /* Reads git log --pretty=raw output and parses it into the commit struct. */
4325 main_read(struct view *view, char *line)
4327 static struct rev_graph *graph = graph_stacks;
4328 enum line_type type;
4329 struct commit *commit;
4332 update_rev_graph(graph);
4336 type = get_line_type(line);
4337 if (type == LINE_COMMIT) {
4338 commit = calloc(1, sizeof(struct commit));
4342 line += STRING_SIZE("commit ");
4344 graph->boundary = 1;
4348 string_copy_rev(commit->id, line);
4349 commit->refs = get_refs(commit->id);
4350 graph->commit = commit;
4351 add_line_data(view, commit, LINE_MAIN_COMMIT);
4353 while ((line = strchr(line, ' '))) {
4355 push_rev_graph(graph->parents, line);
4356 commit->has_parents = TRUE;
4363 commit = view->line[view->lines - 1].data;
4367 if (commit->has_parents)
4369 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
4374 /* Parse author lines where the name may be empty:
4375 * author <email@address.tld> 1138474660 +0100
4377 char *ident = line + STRING_SIZE("author ");
4378 char *nameend = strchr(ident, '<');
4379 char *emailend = strchr(ident, '>');
4381 if (!nameend || !emailend)
4384 update_rev_graph(graph);
4385 graph = graph->next;
4387 *nameend = *emailend = 0;
4388 ident = chomp_string(ident);
4390 ident = chomp_string(nameend + 1);
4395 string_ncopy(commit->author, ident, strlen(ident));
4397 /* Parse epoch and timezone */
4398 if (emailend[1] == ' ') {
4399 char *secs = emailend + 2;
4400 char *zone = strchr(secs, ' ');
4401 time_t time = (time_t) atol(secs);
4403 if (zone && strlen(zone) == STRING_SIZE(" +0700")) {
4407 tz = ('0' - zone[1]) * 60 * 60 * 10;
4408 tz += ('0' - zone[2]) * 60 * 60;
4409 tz += ('0' - zone[3]) * 60;
4410 tz += ('0' - zone[4]) * 60;
4418 gmtime_r(&time, &commit->time);
4423 /* Fill in the commit title if it has not already been set. */
4424 if (commit->title[0])
4427 /* Require titles to start with a non-space character at the
4428 * offset used by git log. */
4429 if (strncmp(line, " ", 4))
4432 /* Well, if the title starts with a whitespace character,
4433 * try to be forgiving. Otherwise we end up with no title. */
4434 while (isspace(*line))
4438 /* FIXME: More graceful handling of titles; append "..." to
4439 * shortened titles, etc. */
4441 string_ncopy(commit->title, line, strlen(line));
4448 main_request(struct view *view, enum request request, struct line *line)
4450 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4452 if (request == REQ_ENTER)
4453 open_view(view, REQ_VIEW_DIFF, flags);
4461 main_grep(struct view *view, struct line *line)
4463 struct commit *commit = line->data;
4464 enum { S_TITLE, S_AUTHOR, S_DATE, S_END } state;
4465 char buf[DATE_COLS + 1];
4468 for (state = S_TITLE; state < S_END; state++) {
4472 case S_TITLE: text = commit->title; break;
4473 case S_AUTHOR: text = commit->author; break;
4475 if (!strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time))
4484 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
4492 main_select(struct view *view, struct line *line)
4494 struct commit *commit = line->data;
4496 string_copy_rev(view->ref, commit->id);
4497 string_copy_rev(ref_commit, view->ref);
4500 static struct view_ops main_ops = {
4512 * Unicode / UTF-8 handling
4514 * NOTE: Much of the following code for dealing with unicode is derived from
4515 * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
4516 * src/intl/charset.c from the utf8 branch commit elinks-0.11.0-g31f2c28.
4519 /* I've (over)annotated a lot of code snippets because I am not entirely
4520 * confident that the approach taken by this small UTF-8 interface is correct.
4524 unicode_width(unsigned long c)
4527 (c <= 0x115f /* Hangul Jamo */
4530 || (c >= 0x2e80 && c <= 0xa4cf && c != 0x303f)
4532 || (c >= 0xac00 && c <= 0xd7a3) /* Hangul Syllables */
4533 || (c >= 0xf900 && c <= 0xfaff) /* CJK Compatibility Ideographs */
4534 || (c >= 0xfe30 && c <= 0xfe6f) /* CJK Compatibility Forms */
4535 || (c >= 0xff00 && c <= 0xff60) /* Fullwidth Forms */
4536 || (c >= 0xffe0 && c <= 0xffe6)
4537 || (c >= 0x20000 && c <= 0x2fffd)
4538 || (c >= 0x30000 && c <= 0x3fffd)))
4542 return opt_tab_size;
4547 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
4548 * Illegal bytes are set one. */
4549 static const unsigned char utf8_bytes[256] = {
4550 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,
4551 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,
4552 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,
4553 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,
4554 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,
4555 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,
4556 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,
4557 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,
4560 /* Decode UTF-8 multi-byte representation into a unicode character. */
4561 static inline unsigned long
4562 utf8_to_unicode(const char *string, size_t length)
4564 unsigned long unicode;
4568 unicode = string[0];
4571 unicode = (string[0] & 0x1f) << 6;
4572 unicode += (string[1] & 0x3f);
4575 unicode = (string[0] & 0x0f) << 12;
4576 unicode += ((string[1] & 0x3f) << 6);
4577 unicode += (string[2] & 0x3f);
4580 unicode = (string[0] & 0x0f) << 18;
4581 unicode += ((string[1] & 0x3f) << 12);
4582 unicode += ((string[2] & 0x3f) << 6);
4583 unicode += (string[3] & 0x3f);
4586 unicode = (string[0] & 0x0f) << 24;
4587 unicode += ((string[1] & 0x3f) << 18);
4588 unicode += ((string[2] & 0x3f) << 12);
4589 unicode += ((string[3] & 0x3f) << 6);
4590 unicode += (string[4] & 0x3f);
4593 unicode = (string[0] & 0x01) << 30;
4594 unicode += ((string[1] & 0x3f) << 24);
4595 unicode += ((string[2] & 0x3f) << 18);
4596 unicode += ((string[3] & 0x3f) << 12);
4597 unicode += ((string[4] & 0x3f) << 6);
4598 unicode += (string[5] & 0x3f);
4601 die("Invalid unicode length");
4604 /* Invalid characters could return the special 0xfffd value but NUL
4605 * should be just as good. */
4606 return unicode > 0xffff ? 0 : unicode;
4609 /* Calculates how much of string can be shown within the given maximum width
4610 * and sets trimmed parameter to non-zero value if all of string could not be
4611 * shown. If the reserve flag is TRUE, it will reserve at least one
4612 * trailing character, which can be useful when drawing a delimiter.
4614 * Returns the number of bytes to output from string to satisfy max_width. */
4616 utf8_length(const char *string, size_t max_width, int *trimmed, bool reserve)
4618 const char *start = string;
4619 const char *end = strchr(string, '\0');
4620 unsigned char last_bytes = 0;
4625 while (string < end) {
4626 int c = *(unsigned char *) string;
4627 unsigned char bytes = utf8_bytes[c];
4629 unsigned long unicode;
4631 if (string + bytes > end)
4634 /* Change representation to figure out whether
4635 * it is a single- or double-width character. */
4637 unicode = utf8_to_unicode(string, bytes);
4638 /* FIXME: Graceful handling of invalid unicode character. */
4642 ucwidth = unicode_width(unicode);
4644 if (width > max_width) {
4646 if (reserve && width - ucwidth == max_width) {
4647 string -= last_bytes;
4656 return string - start;
4664 /* Whether or not the curses interface has been initialized. */
4665 static bool cursed = FALSE;
4667 /* The status window is used for polling keystrokes. */
4668 static WINDOW *status_win;
4670 static bool status_empty = TRUE;
4672 /* Update status and title window. */
4674 report(const char *msg, ...)
4676 struct view *view = display[current_view];
4682 char buf[SIZEOF_STR];
4685 va_start(args, msg);
4686 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
4687 buf[sizeof(buf) - 1] = 0;
4688 buf[sizeof(buf) - 2] = '.';
4689 buf[sizeof(buf) - 3] = '.';
4690 buf[sizeof(buf) - 4] = '.';
4696 if (!status_empty || *msg) {
4699 va_start(args, msg);
4701 wmove(status_win, 0, 0);
4703 vwprintw(status_win, msg, args);
4704 status_empty = FALSE;
4706 status_empty = TRUE;
4708 wclrtoeol(status_win);
4709 wrefresh(status_win);
4714 update_view_title(view);
4715 update_display_cursor(view);
4718 /* Controls when nodelay should be in effect when polling user input. */
4720 set_nonblocking_input(bool loading)
4722 static unsigned int loading_views;
4724 if ((loading == FALSE && loading_views-- == 1) ||
4725 (loading == TRUE && loading_views++ == 0))
4726 nodelay(status_win, loading);
4734 /* Initialize the curses library */
4735 if (isatty(STDIN_FILENO)) {
4736 cursed = !!initscr();
4738 /* Leave stdin and stdout alone when acting as a pager. */
4739 FILE *io = fopen("/dev/tty", "r+");
4742 die("Failed to open /dev/tty");
4743 cursed = !!newterm(NULL, io, io);
4747 die("Failed to initialize curses");
4749 nonl(); /* Tell curses not to do NL->CR/NL on output */
4750 cbreak(); /* Take input chars one at a time, no wait for \n */
4751 noecho(); /* Don't echo input */
4752 leaveok(stdscr, TRUE);
4757 getmaxyx(stdscr, y, x);
4758 status_win = newwin(1, 0, y - 1, 0);
4760 die("Failed to create status window");
4762 /* Enable keyboard mapping */
4763 keypad(status_win, TRUE);
4764 wbkgdset(status_win, get_line_attr(LINE_STATUS));
4768 read_prompt(const char *prompt)
4770 enum { READING, STOP, CANCEL } status = READING;
4771 static char buf[sizeof(opt_cmd) - STRING_SIZE("git \0")];
4774 while (status == READING) {
4780 foreach_view (view, i)
4785 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
4786 wclrtoeol(status_win);
4788 /* Refresh, accept single keystroke of input */
4789 key = wgetch(status_win);
4794 status = pos ? STOP : CANCEL;
4812 if (pos >= sizeof(buf)) {
4813 report("Input string too long");
4818 buf[pos++] = (char) key;
4822 /* Clear the status window */
4823 status_empty = FALSE;
4826 if (status == CANCEL)
4835 * Repository references
4838 static struct ref *refs = NULL;
4839 static size_t refs_alloc = 0;
4840 static size_t refs_size = 0;
4842 /* Id <-> ref store */
4843 static struct ref ***id_refs = NULL;
4844 static size_t id_refs_alloc = 0;
4845 static size_t id_refs_size = 0;
4847 static struct ref **
4850 struct ref ***tmp_id_refs;
4851 struct ref **ref_list = NULL;
4852 size_t ref_list_alloc = 0;
4853 size_t ref_list_size = 0;
4856 for (i = 0; i < id_refs_size; i++)
4857 if (!strcmp(id, id_refs[i][0]->id))
4860 tmp_id_refs = realloc_items(id_refs, &id_refs_alloc, id_refs_size + 1,
4865 id_refs = tmp_id_refs;
4867 for (i = 0; i < refs_size; i++) {
4870 if (strcmp(id, refs[i].id))
4873 tmp = realloc_items(ref_list, &ref_list_alloc,
4874 ref_list_size + 1, sizeof(*ref_list));
4882 if (ref_list_size > 0)
4883 ref_list[ref_list_size - 1]->next = 1;
4884 ref_list[ref_list_size] = &refs[i];
4886 /* XXX: The properties of the commit chains ensures that we can
4887 * safely modify the shared ref. The repo references will
4888 * always be similar for the same id. */
4889 ref_list[ref_list_size]->next = 0;
4894 id_refs[id_refs_size++] = ref_list;
4900 read_ref(char *id, size_t idlen, char *name, size_t namelen)
4905 bool remote = FALSE;
4906 bool check_replace = FALSE;
4908 if (!strncmp(name, "refs/tags/", STRING_SIZE("refs/tags/"))) {
4909 if (!strcmp(name + namelen - 3, "^{}")) {
4912 if (refs_size > 0 && refs[refs_size - 1].ltag == TRUE)
4913 check_replace = TRUE;
4919 namelen -= STRING_SIZE("refs/tags/");
4920 name += STRING_SIZE("refs/tags/");
4922 } else if (!strncmp(name, "refs/remotes/", STRING_SIZE("refs/remotes/"))) {
4924 namelen -= STRING_SIZE("refs/remotes/");
4925 name += STRING_SIZE("refs/remotes/");
4927 } else if (!strncmp(name, "refs/heads/", STRING_SIZE("refs/heads/"))) {
4928 namelen -= STRING_SIZE("refs/heads/");
4929 name += STRING_SIZE("refs/heads/");
4931 } else if (!strcmp(name, "HEAD")) {
4935 if (check_replace && !strcmp(name, refs[refs_size - 1].name)) {
4936 /* it's an annotated tag, replace the previous sha1 with the
4937 * resolved commit id; relies on the fact git-ls-remote lists
4938 * the commit id of an annotated tag right beofre the commit id
4940 refs[refs_size - 1].ltag = ltag;
4941 string_copy_rev(refs[refs_size - 1].id, id);
4945 refs = realloc_items(refs, &refs_alloc, refs_size + 1, sizeof(*refs));
4949 ref = &refs[refs_size++];
4950 ref->name = malloc(namelen + 1);
4954 strncpy(ref->name, name, namelen);
4955 ref->name[namelen] = 0;
4958 ref->remote = remote;
4959 string_copy_rev(ref->id, id);
4967 const char *cmd_env = getenv("TIG_LS_REMOTE");
4968 const char *cmd = cmd_env && *cmd_env ? cmd_env : TIG_LS_REMOTE;
4970 return read_properties(popen(cmd, "r"), "\t", read_ref);
4974 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
4976 if (!strcmp(name, "i18n.commitencoding"))
4977 string_ncopy(opt_encoding, value, valuelen);
4979 if (!strcmp(name, "core.editor"))
4980 string_ncopy(opt_editor, value, valuelen);
4986 load_repo_config(void)
4988 return read_properties(popen(GIT_CONFIG " --list", "r"),
4989 "=", read_repo_config_option);
4993 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
4995 if (!opt_git_dir[0]) {
4996 string_ncopy(opt_git_dir, name, namelen);
4998 } else if (opt_is_inside_work_tree == -1) {
4999 /* This can be 3 different values depending on the
5000 * version of git being used. If git-rev-parse does not
5001 * understand --is-inside-work-tree it will simply echo
5002 * the option else either "true" or "false" is printed.
5003 * Default to true for the unknown case. */
5004 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
5007 string_ncopy(opt_cdup, name, namelen);
5013 /* XXX: The line outputted by "--show-cdup" can be empty so the option
5014 * must be the last one! */
5016 load_repo_info(void)
5018 return read_properties(popen("git rev-parse --git-dir --is-inside-work-tree --show-cdup 2>/dev/null", "r"),
5019 "=", read_repo_info);
5023 read_properties(FILE *pipe, const char *separators,
5024 int (*read_property)(char *, size_t, char *, size_t))
5026 char buffer[BUFSIZ];
5033 while (state == OK && (name = fgets(buffer, sizeof(buffer), pipe))) {
5038 name = chomp_string(name);
5039 namelen = strcspn(name, separators);
5041 if (name[namelen]) {
5043 value = chomp_string(name + namelen + 1);
5044 valuelen = strlen(value);
5051 state = read_property(name, namelen, value, valuelen);
5054 if (state != ERR && ferror(pipe))
5067 static void __NORETURN
5070 /* XXX: Restore tty modes and let the OS cleanup the rest! */
5076 static void __NORETURN
5077 die(const char *err, ...)
5083 va_start(args, err);
5084 fputs("tig: ", stderr);
5085 vfprintf(stderr, err, args);
5086 fputs("\n", stderr);
5093 warn(const char *msg, ...)
5097 va_start(args, msg);
5098 fputs("tig warning: ", stderr);
5099 vfprintf(stderr, msg, args);
5100 fputs("\n", stderr);
5105 main(int argc, char *argv[])
5108 enum request request;
5111 signal(SIGINT, quit);
5113 if (setlocale(LC_ALL, "")) {
5114 char *codeset = nl_langinfo(CODESET);
5116 string_ncopy(opt_codeset, codeset, strlen(codeset));
5119 if (load_repo_info() == ERR)
5120 die("Failed to load repo info.");
5122 if (load_options() == ERR)
5123 die("Failed to load user config.");
5125 /* Load the repo config file so options can be overwritten from
5126 * the command line. */
5127 if (load_repo_config() == ERR)
5128 die("Failed to load repo config.");
5130 if (!parse_options(argc, argv))
5133 /* Require a git repository unless when running in pager mode. */
5134 if (!opt_git_dir[0])
5135 die("Not a git repository");
5137 if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
5140 if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
5141 opt_iconv = iconv_open(opt_codeset, opt_encoding);
5142 if (opt_iconv == ICONV_NONE)
5143 die("Failed to initialize character set conversion");
5146 if (load_refs() == ERR)
5147 die("Failed to load refs.");
5149 for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
5150 view->cmd_env = getenv(view->cmd_env);
5152 request = opt_request;
5156 while (view_driver(display[current_view], request)) {
5160 foreach_view (view, i)
5163 /* Refresh, accept single keystroke of input */
5164 key = wgetch(status_win);
5166 /* wgetch() with nodelay() enabled returns ERR when there's no
5173 request = get_keybinding(display[current_view]->keymap, key);
5175 /* Some low-level request handling. This keeps access to
5176 * status_win restricted. */
5180 char *cmd = read_prompt(":");
5182 if (cmd && string_format(opt_cmd, "git %s", cmd)) {
5183 if (strncmp(cmd, "show", 4) && isspace(cmd[4])) {
5184 opt_request = REQ_VIEW_DIFF;
5186 opt_request = REQ_VIEW_PAGER;
5195 case REQ_SEARCH_BACK:
5197 const char *prompt = request == REQ_SEARCH
5199 char *search = read_prompt(prompt);
5202 string_ncopy(opt_search, search, strlen(search));
5207 case REQ_SCREEN_RESIZE:
5211 getmaxyx(stdscr, height, width);
5213 /* Resize the status view and let the view driver take
5214 * care of resizing the displayed views. */
5215 wresize(status_win, 1, width);
5216 mvwin(status_win, height - 1, 0);
5217 wrefresh(status_win);