Code

tig-0.10.1.git
[tig.git] / tig.c
1 /* Copyright (c) 2006-2007 Jonas Fonseca <fonseca@diku.dk>
2  *
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.
7  *
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.
12  */
14 #ifdef HAVE_CONFIG_H
15 #include "config.h"
16 #endif
18 #ifndef TIG_VERSION
19 #define TIG_VERSION "unknown-version"
20 #endif
22 #ifndef DEBUG
23 #define NDEBUG
24 #endif
26 #include <assert.h>
27 #include <errno.h>
28 #include <ctype.h>
29 #include <signal.h>
30 #include <stdarg.h>
31 #include <stdio.h>
32 #include <stdlib.h>
33 #include <string.h>
34 #include <sys/types.h>
35 #include <sys/stat.h>
36 #include <unistd.h>
37 #include <time.h>
39 #include <regex.h>
41 #include <locale.h>
42 #include <langinfo.h>
43 #include <iconv.h>
45 #include <curses.h>
47 #if __GNUC__ >= 3
48 #define __NORETURN __attribute__((__noreturn__))
49 #else
50 #define __NORETURN
51 #endif
53 static void __NORETURN die(const char *err, ...);
54 static void warn(const char *msg, ...);
55 static void report(const char *msg, ...);
56 static int read_properties(FILE *pipe, const char *separators, int (*read)(char *, size_t, char *, size_t));
57 static void set_nonblocking_input(bool loading);
58 static size_t utf8_length(const char *string, size_t max_width, int *coloffset, int *trimmed);
60 #define ABS(x)          ((x) >= 0  ? (x) : -(x))
61 #define MIN(x, y)       ((x) < (y) ? (x) :  (y))
63 #define ARRAY_SIZE(x)   (sizeof(x) / sizeof(x[0]))
64 #define STRING_SIZE(x)  (sizeof(x) - 1)
66 #define SIZEOF_STR      1024    /* Default string size. */
67 #define SIZEOF_REF      256     /* Size of symbolic or SHA1 ID. */
68 #define SIZEOF_REV      41      /* Holds a SHA-1 and an ending NUL */
70 /* Revision graph */
72 #define REVGRAPH_INIT   'I'
73 #define REVGRAPH_MERGE  'M'
74 #define REVGRAPH_BRANCH '+'
75 #define REVGRAPH_COMMIT '*'
76 #define REVGRAPH_BOUND  '^'
77 #define REVGRAPH_LINE   '|'
79 #define SIZEOF_REVGRAPH 19      /* Size of revision ancestry graphics. */
81 /* This color name can be used to refer to the default term colors. */
82 #define COLOR_DEFAULT   (-1)
84 #define ICONV_NONE      ((iconv_t) -1)
85 #ifndef ICONV_CONST
86 #define ICONV_CONST     /* nothing */
87 #endif
89 /* The format and size of the date column in the main view. */
90 #define DATE_FORMAT     "%Y-%m-%d %H:%M"
91 #define DATE_COLS       STRING_SIZE("2006-04-29 14:21 ")
93 #define AUTHOR_COLS     20
95 /* The default interval between line numbers. */
96 #define NUMBER_INTERVAL 1
98 #define TABSIZE         8
100 #define SCALE_SPLIT_VIEW(height)        ((height) * 2 / 3)
102 #ifndef GIT_CONFIG
103 #define GIT_CONFIG "git config"
104 #endif
106 #define TIG_LS_REMOTE \
107         "git ls-remote $(git rev-parse --git-dir) 2>/dev/null"
109 #define TIG_DIFF_CMD \
110         "git show --no-color --root --patch-with-stat --find-copies-harder -C %s 2>/dev/null"
112 #define TIG_LOG_CMD     \
113         "git log --no-color --cc --stat -n100 %s 2>/dev/null"
115 #define TIG_MAIN_CMD \
116         "git log --no-color --topo-order --boundary --pretty=raw %s 2>/dev/null"
118 #define TIG_TREE_CMD    \
119         "git ls-tree %s %s"
121 #define TIG_BLOB_CMD    \
122         "git cat-file blob %s"
124 /* XXX: Needs to be defined to the empty string. */
125 #define TIG_HELP_CMD    ""
126 #define TIG_PAGER_CMD   ""
127 #define TIG_STATUS_CMD  ""
128 #define TIG_STAGE_CMD   ""
130 /* Some ascii-shorthands fitted into the ncurses namespace. */
131 #define KEY_TAB         '\t'
132 #define KEY_RETURN      '\r'
133 #define KEY_ESC         27
136 struct ref {
137         char *name;             /* Ref name; tag or head names are shortened. */
138         char id[SIZEOF_REV];    /* Commit SHA1 ID */
139         unsigned int tag:1;     /* Is it a tag? */
140         unsigned int remote:1;  /* Is it a remote ref? */
141         unsigned int next:1;    /* For ref lists: are there more refs? */
142 };
144 static struct ref **get_refs(char *id);
146 struct int_map {
147         const char *name;
148         int namelen;
149         int value;
150 };
152 static int
153 set_from_int_map(struct int_map *map, size_t map_size,
154                  int *value, const char *name, int namelen)
157         int i;
159         for (i = 0; i < map_size; i++)
160                 if (namelen == map[i].namelen &&
161                     !strncasecmp(name, map[i].name, namelen)) {
162                         *value = map[i].value;
163                         return OK;
164                 }
166         return ERR;
170 /*
171  * String helpers
172  */
174 static inline void
175 string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t srclen)
177         if (srclen > dstlen - 1)
178                 srclen = dstlen - 1;
180         strncpy(dst, src, srclen);
181         dst[srclen] = 0;
184 /* Shorthands for safely copying into a fixed buffer. */
186 #define string_copy(dst, src) \
187         string_ncopy_do(dst, sizeof(dst), src, sizeof(src))
189 #define string_ncopy(dst, src, srclen) \
190         string_ncopy_do(dst, sizeof(dst), src, srclen)
192 #define string_copy_rev(dst, src) \
193         string_ncopy_do(dst, SIZEOF_REV, src, SIZEOF_REV - 1)
195 #define string_add(dst, from, src) \
196         string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src))
198 static char *
199 chomp_string(char *name)
201         int namelen;
203         while (isspace(*name))
204                 name++;
206         namelen = strlen(name) - 1;
207         while (namelen > 0 && isspace(name[namelen]))
208                 name[namelen--] = 0;
210         return name;
213 static bool
214 string_nformat(char *buf, size_t bufsize, size_t *bufpos, const char *fmt, ...)
216         va_list args;
217         size_t pos = bufpos ? *bufpos : 0;
219         va_start(args, fmt);
220         pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
221         va_end(args);
223         if (bufpos)
224                 *bufpos = pos;
226         return pos >= bufsize ? FALSE : TRUE;
229 #define string_format(buf, fmt, args...) \
230         string_nformat(buf, sizeof(buf), NULL, fmt, args)
232 #define string_format_from(buf, from, fmt, args...) \
233         string_nformat(buf, sizeof(buf), from, fmt, args)
235 static int
236 string_enum_compare(const char *str1, const char *str2, int len)
238         size_t i;
240 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
242         /* Diff-Header == DIFF_HEADER */
243         for (i = 0; i < len; i++) {
244                 if (toupper(str1[i]) == toupper(str2[i]))
245                         continue;
247                 if (string_enum_sep(str1[i]) &&
248                     string_enum_sep(str2[i]))
249                         continue;
251                 return str1[i] - str2[i];
252         }
254         return 0;
257 /* Shell quoting
258  *
259  * NOTE: The following is a slightly modified copy of the git project's shell
260  * quoting routines found in the quote.c file.
261  *
262  * Help to copy the thing properly quoted for the shell safety.  any single
263  * quote is replaced with '\'', any exclamation point is replaced with '\!',
264  * and the whole thing is enclosed in a
265  *
266  * E.g.
267  *  original     sq_quote     result
268  *  name     ==> name      ==> 'name'
269  *  a b      ==> a b       ==> 'a b'
270  *  a'b      ==> a'\''b    ==> 'a'\''b'
271  *  a!b      ==> a'\!'b    ==> 'a'\!'b'
272  */
274 static size_t
275 sq_quote(char buf[SIZEOF_STR], size_t bufsize, const char *src)
277         char c;
279 #define BUFPUT(x) do { if (bufsize < SIZEOF_STR) buf[bufsize++] = (x); } while (0)
281         BUFPUT('\'');
282         while ((c = *src++)) {
283                 if (c == '\'' || c == '!') {
284                         BUFPUT('\'');
285                         BUFPUT('\\');
286                         BUFPUT(c);
287                         BUFPUT('\'');
288                 } else {
289                         BUFPUT(c);
290                 }
291         }
292         BUFPUT('\'');
294         if (bufsize < SIZEOF_STR)
295                 buf[bufsize] = 0;
297         return bufsize;
301 /*
302  * User requests
303  */
305 #define REQ_INFO \
306         /* XXX: Keep the view request first and in sync with views[]. */ \
307         REQ_GROUP("View switching") \
308         REQ_(VIEW_MAIN,         "Show main view"), \
309         REQ_(VIEW_DIFF,         "Show diff view"), \
310         REQ_(VIEW_LOG,          "Show log view"), \
311         REQ_(VIEW_TREE,         "Show tree view"), \
312         REQ_(VIEW_BLOB,         "Show blob view"), \
313         REQ_(VIEW_HELP,         "Show help page"), \
314         REQ_(VIEW_PAGER,        "Show pager view"), \
315         REQ_(VIEW_STATUS,       "Show status view"), \
316         REQ_(VIEW_STAGE,        "Show stage view"), \
317         \
318         REQ_GROUP("View manipulation") \
319         REQ_(ENTER,             "Enter current line and scroll"), \
320         REQ_(NEXT,              "Move to next"), \
321         REQ_(PREVIOUS,          "Move to previous"), \
322         REQ_(VIEW_NEXT,         "Move focus to next view"), \
323         REQ_(REFRESH,           "Reload and refresh"), \
324         REQ_(VIEW_CLOSE,        "Close the current view"), \
325         REQ_(QUIT,              "Close all views and quit"), \
326         \
327         REQ_GROUP("Cursor navigation") \
328         REQ_(MOVE_UP,           "Move cursor one line up"), \
329         REQ_(MOVE_DOWN,         "Move cursor one line down"), \
330         REQ_(MOVE_PAGE_DOWN,    "Move cursor one page down"), \
331         REQ_(MOVE_PAGE_UP,      "Move cursor one page up"), \
332         REQ_(MOVE_FIRST_LINE,   "Move cursor to first line"), \
333         REQ_(MOVE_LAST_LINE,    "Move cursor to last line"), \
334         \
335         REQ_GROUP("Scrolling") \
336         REQ_(SCROLL_LINE_UP,    "Scroll one line up"), \
337         REQ_(SCROLL_LINE_DOWN,  "Scroll one line down"), \
338         REQ_(SCROLL_PAGE_UP,    "Scroll one page up"), \
339         REQ_(SCROLL_PAGE_DOWN,  "Scroll one page down"), \
340         \
341         REQ_GROUP("Searching") \
342         REQ_(SEARCH,            "Search the view"), \
343         REQ_(SEARCH_BACK,       "Search backwards in the view"), \
344         REQ_(FIND_NEXT,         "Find next search match"), \
345         REQ_(FIND_PREV,         "Find previous search match"), \
346         \
347         REQ_GROUP("Misc") \
348         REQ_(PROMPT,            "Bring up the prompt"), \
349         REQ_(SCREEN_REDRAW,     "Redraw the screen"), \
350         REQ_(SCREEN_RESIZE,     "Resize the screen"), \
351         REQ_(SHOW_VERSION,      "Show version information"), \
352         REQ_(STOP_LOADING,      "Stop all loading views"), \
353         REQ_(TOGGLE_LINENO,     "Toggle line numbers"), \
354         REQ_(TOGGLE_REV_GRAPH,  "Toggle revision graph visualization"), \
355         REQ_(STATUS_UPDATE,     "Update file status"), \
356         REQ_(STATUS_MERGE,      "Merge file using external tool"), \
357         REQ_(TREE_PARENT,       "Switch to parent directory in tree view"), \
358         REQ_(EDIT,              "Open in editor"), \
359         REQ_(NONE,              "Do nothing")
362 /* User action requests. */
363 enum request {
364 #define REQ_GROUP(help)
365 #define REQ_(req, help) REQ_##req
367         /* Offset all requests to avoid conflicts with ncurses getch values. */
368         REQ_OFFSET = KEY_MAX + 1,
369         REQ_INFO
371 #undef  REQ_GROUP
372 #undef  REQ_
373 };
375 struct request_info {
376         enum request request;
377         char *name;
378         int namelen;
379         char *help;
380 };
382 static struct request_info req_info[] = {
383 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
384 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
385         REQ_INFO
386 #undef  REQ_GROUP
387 #undef  REQ_
388 };
390 static enum request
391 get_request(const char *name)
393         int namelen = strlen(name);
394         int i;
396         for (i = 0; i < ARRAY_SIZE(req_info); i++)
397                 if (req_info[i].namelen == namelen &&
398                     !string_enum_compare(req_info[i].name, name, namelen))
399                         return req_info[i].request;
401         return REQ_NONE;
405 /*
406  * Options
407  */
409 static const char usage[] =
410 "tig " TIG_VERSION " (" __DATE__ ")\n"
411 "\n"
412 "Usage: tig        [options] [revs] [--] [paths]\n"
413 "   or: tig show   [options] [revs] [--] [paths]\n"
414 "   or: tig status\n"
415 "   or: tig <      [git command output]\n"
416 "\n"
417 "Options:\n"
418 "  -v, --version   Show version and exit\n"
419 "  -h, --help      Show help message and exit\n";
421 /* Option and state variables. */
422 static bool opt_line_number             = FALSE;
423 static bool opt_rev_graph               = FALSE;
424 static int opt_num_interval             = NUMBER_INTERVAL;
425 static int opt_tab_size                 = TABSIZE;
426 static enum request opt_request         = REQ_VIEW_MAIN;
427 static char opt_cmd[SIZEOF_STR]         = "";
428 static char opt_path[SIZEOF_STR]        = "";
429 static FILE *opt_pipe                   = NULL;
430 static char opt_encoding[20]            = "UTF-8";
431 static bool opt_utf8                    = TRUE;
432 static char opt_codeset[20]             = "UTF-8";
433 static iconv_t opt_iconv                = ICONV_NONE;
434 static char opt_search[SIZEOF_STR]      = "";
435 static char opt_cdup[SIZEOF_STR]        = "";
436 static char opt_git_dir[SIZEOF_STR]     = "";
437 static char opt_is_inside_work_tree     = -1; /* set to TRUE or FALSE */
438 static char opt_editor[SIZEOF_STR]      = "";
440 enum option_type {
441         OPT_NONE,
442         OPT_INT,
443 };
445 static bool
446 check_option(char *opt, char short_name, char *name, enum option_type type, ...)
448         va_list args;
449         char *value = "";
450         int *number;
452         if (opt[0] != '-')
453                 return FALSE;
455         if (opt[1] == '-') {
456                 int namelen = strlen(name);
458                 opt += 2;
460                 if (strncmp(opt, name, namelen))
461                         return FALSE;
463                 if (opt[namelen] == '=')
464                         value = opt + namelen + 1;
466         } else {
467                 if (!short_name || opt[1] != short_name)
468                         return FALSE;
469                 value = opt + 2;
470         }
472         va_start(args, type);
473         if (type == OPT_INT) {
474                 number = va_arg(args, int *);
475                 if (isdigit(*value))
476                         *number = atoi(value);
477         }
478         va_end(args);
480         return TRUE;
483 /* Returns the index of log or diff command or -1 to exit. */
484 static bool
485 parse_options(int argc, char *argv[])
487         char *altargv[1024];
488         int altargc = 0;
489         char *subcommand = NULL;
490         int i;
492         for (i = 1; i < argc; i++) {
493                 char *opt = argv[i];
495                 if (!strcmp(opt, "log") ||
496                     !strcmp(opt, "diff")) {
497                         subcommand = opt;
498                         opt_request = opt[0] == 'l'
499                                     ? REQ_VIEW_LOG : REQ_VIEW_DIFF;
500                         warn("`tig %s' has been deprecated", opt);
501                         break;
502                 }
504                 if (!strcmp(opt, "show")) {
505                         subcommand = opt;
506                         opt_request = REQ_VIEW_DIFF;
507                         break;
508                 }
510                 if (!strcmp(opt, "status")) {
511                         subcommand = opt;
512                         opt_request = REQ_VIEW_STATUS;
513                         break;
514                 }
516                 if (opt[0] && opt[0] != '-')
517                         break;
519                 if (!strcmp(opt, "--")) {
520                         i++;
521                         break;
522                 }
524                 if (check_option(opt, 'v', "version", OPT_NONE)) {
525                         printf("tig version %s\n", TIG_VERSION);
526                         return FALSE;
527                 }
529                 if (check_option(opt, 'h', "help", OPT_NONE)) {
530                         printf(usage);
531                         return FALSE;
532                 }
534                 if (!strcmp(opt, "-S")) {
535                         warn("`%s' has been deprecated; use `tig status' instead", opt);
536                         opt_request = REQ_VIEW_STATUS;
537                         continue;
538                 }
540                 if (!strcmp(opt, "-l")) {
541                         opt_request = REQ_VIEW_LOG;
542                 } else if (!strcmp(opt, "-d")) {
543                         opt_request = REQ_VIEW_DIFF;
544                 } else if (check_option(opt, 'n', "line-number", OPT_INT, &opt_num_interval)) {
545                         opt_line_number = TRUE;
546                 } else if (check_option(opt, 'b', "tab-size", OPT_INT, &opt_tab_size)) {
547                         opt_tab_size = MIN(opt_tab_size, TABSIZE);
548                 } else {
549                         if (altargc >= ARRAY_SIZE(altargv))
550                                 die("maximum number of arguments exceeded");
551                         altargv[altargc++] = opt;
552                         continue;
553                 }
555                 warn("`%s' has been deprecated", opt);
556         }
558         /* Check that no 'alt' arguments occured before a subcommand. */
559         if (subcommand && i < argc && altargc > 0)
560                 die("unknown arguments before `%s'", argv[i]);
562         if (!isatty(STDIN_FILENO)) {
563                 opt_request = REQ_VIEW_PAGER;
564                 opt_pipe = stdin;
566         } else if (opt_request == REQ_VIEW_STATUS) {
567                 if (argc - i > 1)
568                         warn("ignoring arguments after `%s'", argv[i]);
570         } else if (i < argc || altargc > 0) {
571                 int alti = 0;
572                 size_t buf_size;
574                 if (opt_request == REQ_VIEW_MAIN)
575                         /* XXX: This is vulnerable to the user overriding
576                          * options required for the main view parser. */
577                         string_copy(opt_cmd, "git log --no-color --pretty=raw --boundary");
578                 else
579                         string_copy(opt_cmd, "git");
580                 buf_size = strlen(opt_cmd);
582                 while (buf_size < sizeof(opt_cmd) && alti < altargc) {
583                         opt_cmd[buf_size++] = ' ';
584                         buf_size = sq_quote(opt_cmd, buf_size, altargv[alti++]);
585                 }
587                 while (buf_size < sizeof(opt_cmd) && i < argc) {
588                         opt_cmd[buf_size++] = ' ';
589                         buf_size = sq_quote(opt_cmd, buf_size, argv[i++]);
590                 }
592                 if (buf_size >= sizeof(opt_cmd))
593                         die("command too long");
595                 opt_cmd[buf_size] = 0;
596         }
598         if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
599                 opt_utf8 = FALSE;
601         return TRUE;
605 /*
606  * Line-oriented content detection.
607  */
609 #define LINE_INFO \
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_REMOTE,  "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_BOLD), \
649 LINE(MAIN_REF,     "",                  COLOR_CYAN,     COLOR_DEFAULT,  A_BOLD), \
650 LINE(MAIN_REVGRAPH,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
651 LINE(TREE_DIR,     "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
652 LINE(TREE_FILE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
653 LINE(STAT_SECTION, "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
654 LINE(STAT_NONE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
655 LINE(STAT_STAGED,  "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
656 LINE(STAT_UNSTAGED,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
657 LINE(STAT_UNTRACKED,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0)
659 enum line_type {
660 #define LINE(type, line, fg, bg, attr) \
661         LINE_##type
662         LINE_INFO
663 #undef  LINE
664 };
666 struct line_info {
667         const char *name;       /* Option name. */
668         int namelen;            /* Size of option name. */
669         const char *line;       /* The start of line to match. */
670         int linelen;            /* Size of string to match. */
671         int fg, bg, attr;       /* Color and text attributes for the lines. */
672 };
674 static struct line_info line_info[] = {
675 #define LINE(type, line, fg, bg, attr) \
676         { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
677         LINE_INFO
678 #undef  LINE
679 };
681 static enum line_type
682 get_line_type(char *line)
684         int linelen = strlen(line);
685         enum line_type type;
687         for (type = 0; type < ARRAY_SIZE(line_info); type++)
688                 /* Case insensitive search matches Signed-off-by lines better. */
689                 if (linelen >= line_info[type].linelen &&
690                     !strncasecmp(line_info[type].line, line, line_info[type].linelen))
691                         return type;
693         return LINE_DEFAULT;
696 static inline int
697 get_line_attr(enum line_type type)
699         assert(type < ARRAY_SIZE(line_info));
700         return COLOR_PAIR(type) | line_info[type].attr;
703 static struct line_info *
704 get_line_info(char *name, int namelen)
706         enum line_type type;
708         for (type = 0; type < ARRAY_SIZE(line_info); type++)
709                 if (namelen == line_info[type].namelen &&
710                     !string_enum_compare(line_info[type].name, name, namelen))
711                         return &line_info[type];
713         return NULL;
716 static void
717 init_colors(void)
719         int default_bg = line_info[LINE_DEFAULT].bg;
720         int default_fg = line_info[LINE_DEFAULT].fg;
721         enum line_type type;
723         start_color();
725         if (assume_default_colors(default_fg, default_bg) == ERR) {
726                 default_bg = COLOR_BLACK;
727                 default_fg = COLOR_WHITE;
728         }
730         for (type = 0; type < ARRAY_SIZE(line_info); type++) {
731                 struct line_info *info = &line_info[type];
732                 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
733                 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
735                 init_pair(type, fg, bg);
736         }
739 struct line {
740         enum line_type type;
742         /* State flags */
743         unsigned int selected:1;
745         void *data;             /* User data */
746 };
749 /*
750  * Keys
751  */
753 struct keybinding {
754         int alias;
755         enum request request;
756         struct keybinding *next;
757 };
759 static struct keybinding default_keybindings[] = {
760         /* View switching */
761         { 'm',          REQ_VIEW_MAIN },
762         { 'd',          REQ_VIEW_DIFF },
763         { 'l',          REQ_VIEW_LOG },
764         { 't',          REQ_VIEW_TREE },
765         { 'f',          REQ_VIEW_BLOB },
766         { 'p',          REQ_VIEW_PAGER },
767         { 'h',          REQ_VIEW_HELP },
768         { 'S',          REQ_VIEW_STATUS },
769         { 'c',          REQ_VIEW_STAGE },
771         /* View manipulation */
772         { 'q',          REQ_VIEW_CLOSE },
773         { KEY_TAB,      REQ_VIEW_NEXT },
774         { KEY_RETURN,   REQ_ENTER },
775         { KEY_UP,       REQ_PREVIOUS },
776         { KEY_DOWN,     REQ_NEXT },
777         { 'R',          REQ_REFRESH },
779         /* Cursor navigation */
780         { 'k',          REQ_MOVE_UP },
781         { 'j',          REQ_MOVE_DOWN },
782         { KEY_HOME,     REQ_MOVE_FIRST_LINE },
783         { KEY_END,      REQ_MOVE_LAST_LINE },
784         { KEY_NPAGE,    REQ_MOVE_PAGE_DOWN },
785         { ' ',          REQ_MOVE_PAGE_DOWN },
786         { KEY_PPAGE,    REQ_MOVE_PAGE_UP },
787         { 'b',          REQ_MOVE_PAGE_UP },
788         { '-',          REQ_MOVE_PAGE_UP },
790         /* Scrolling */
791         { KEY_IC,       REQ_SCROLL_LINE_UP },
792         { KEY_DC,       REQ_SCROLL_LINE_DOWN },
793         { 'w',          REQ_SCROLL_PAGE_UP },
794         { 's',          REQ_SCROLL_PAGE_DOWN },
796         /* Searching */
797         { '/',          REQ_SEARCH },
798         { '?',          REQ_SEARCH_BACK },
799         { 'n',          REQ_FIND_NEXT },
800         { 'N',          REQ_FIND_PREV },
802         /* Misc */
803         { 'Q',          REQ_QUIT },
804         { 'z',          REQ_STOP_LOADING },
805         { 'v',          REQ_SHOW_VERSION },
806         { 'r',          REQ_SCREEN_REDRAW },
807         { '.',          REQ_TOGGLE_LINENO },
808         { 'g',          REQ_TOGGLE_REV_GRAPH },
809         { ':',          REQ_PROMPT },
810         { 'u',          REQ_STATUS_UPDATE },
811         { 'M',          REQ_STATUS_MERGE },
812         { ',',          REQ_TREE_PARENT },
813         { 'e',          REQ_EDIT },
815         /* Using the ncurses SIGWINCH handler. */
816         { KEY_RESIZE,   REQ_SCREEN_RESIZE },
817 };
819 #define KEYMAP_INFO \
820         KEYMAP_(GENERIC), \
821         KEYMAP_(MAIN), \
822         KEYMAP_(DIFF), \
823         KEYMAP_(LOG), \
824         KEYMAP_(TREE), \
825         KEYMAP_(BLOB), \
826         KEYMAP_(PAGER), \
827         KEYMAP_(HELP), \
828         KEYMAP_(STATUS), \
829         KEYMAP_(STAGE)
831 enum keymap {
832 #define KEYMAP_(name) KEYMAP_##name
833         KEYMAP_INFO
834 #undef  KEYMAP_
835 };
837 static struct int_map keymap_table[] = {
838 #define KEYMAP_(name) { #name, STRING_SIZE(#name), KEYMAP_##name }
839         KEYMAP_INFO
840 #undef  KEYMAP_
841 };
843 #define set_keymap(map, name) \
844         set_from_int_map(keymap_table, ARRAY_SIZE(keymap_table), map, name, strlen(name))
846 static struct keybinding *keybindings[ARRAY_SIZE(keymap_table)];
848 static void
849 add_keybinding(enum keymap keymap, enum request request, int key)
851         struct keybinding *keybinding;
853         keybinding = calloc(1, sizeof(*keybinding));
854         if (!keybinding)
855                 die("Failed to allocate keybinding");
857         keybinding->alias = key;
858         keybinding->request = request;
859         keybinding->next = keybindings[keymap];
860         keybindings[keymap] = keybinding;
863 /* Looks for a key binding first in the given map, then in the generic map, and
864  * lastly in the default keybindings. */
865 static enum request
866 get_keybinding(enum keymap keymap, int key)
868         struct keybinding *kbd;
869         int i;
871         for (kbd = keybindings[keymap]; kbd; kbd = kbd->next)
872                 if (kbd->alias == key)
873                         return kbd->request;
875         for (kbd = keybindings[KEYMAP_GENERIC]; kbd; kbd = kbd->next)
876                 if (kbd->alias == key)
877                         return kbd->request;
879         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
880                 if (default_keybindings[i].alias == key)
881                         return default_keybindings[i].request;
883         return (enum request) key;
887 struct key {
888         char *name;
889         int value;
890 };
892 static struct key key_table[] = {
893         { "Enter",      KEY_RETURN },
894         { "Space",      ' ' },
895         { "Backspace",  KEY_BACKSPACE },
896         { "Tab",        KEY_TAB },
897         { "Escape",     KEY_ESC },
898         { "Left",       KEY_LEFT },
899         { "Right",      KEY_RIGHT },
900         { "Up",         KEY_UP },
901         { "Down",       KEY_DOWN },
902         { "Insert",     KEY_IC },
903         { "Delete",     KEY_DC },
904         { "Hash",       '#' },
905         { "Home",       KEY_HOME },
906         { "End",        KEY_END },
907         { "PageUp",     KEY_PPAGE },
908         { "PageDown",   KEY_NPAGE },
909         { "F1",         KEY_F(1) },
910         { "F2",         KEY_F(2) },
911         { "F3",         KEY_F(3) },
912         { "F4",         KEY_F(4) },
913         { "F5",         KEY_F(5) },
914         { "F6",         KEY_F(6) },
915         { "F7",         KEY_F(7) },
916         { "F8",         KEY_F(8) },
917         { "F9",         KEY_F(9) },
918         { "F10",        KEY_F(10) },
919         { "F11",        KEY_F(11) },
920         { "F12",        KEY_F(12) },
921 };
923 static int
924 get_key_value(const char *name)
926         int i;
928         for (i = 0; i < ARRAY_SIZE(key_table); i++)
929                 if (!strcasecmp(key_table[i].name, name))
930                         return key_table[i].value;
932         if (strlen(name) == 1 && isprint(*name))
933                 return (int) *name;
935         return ERR;
938 static char *
939 get_key_name(int key_value)
941         static char key_char[] = "'X'";
942         char *seq = NULL;
943         int key;
945         for (key = 0; key < ARRAY_SIZE(key_table); key++)
946                 if (key_table[key].value == key_value)
947                         seq = key_table[key].name;
949         if (seq == NULL &&
950             key_value < 127 &&
951             isprint(key_value)) {
952                 key_char[1] = (char) key_value;
953                 seq = key_char;
954         }
956         return seq ? seq : "'?'";
959 static char *
960 get_key(enum request request)
962         static char buf[BUFSIZ];
963         size_t pos = 0;
964         char *sep = "";
965         int i;
967         buf[pos] = 0;
969         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
970                 struct keybinding *keybinding = &default_keybindings[i];
972                 if (keybinding->request != request)
973                         continue;
975                 if (!string_format_from(buf, &pos, "%s%s", sep,
976                                         get_key_name(keybinding->alias)))
977                         return "Too many keybindings!";
978                 sep = ", ";
979         }
981         return buf;
984 struct run_request {
985         enum keymap keymap;
986         int key;
987         char cmd[SIZEOF_STR];
988 };
990 static struct run_request *run_request;
991 static size_t run_requests;
993 static enum request
994 add_run_request(enum keymap keymap, int key, int argc, char **argv)
996         struct run_request *tmp;
997         struct run_request req = { keymap, key };
998         size_t bufpos;
1000         for (bufpos = 0; argc > 0; argc--, argv++)
1001                 if (!string_format_from(req.cmd, &bufpos, "%s ", *argv))
1002                         return REQ_NONE;
1004         req.cmd[bufpos - 1] = 0;
1006         tmp = realloc(run_request, (run_requests + 1) * sizeof(*run_request));
1007         if (!tmp)
1008                 return REQ_NONE;
1010         run_request = tmp;
1011         run_request[run_requests++] = req;
1013         return REQ_NONE + run_requests;
1016 static struct run_request *
1017 get_run_request(enum request request)
1019         if (request <= REQ_NONE)
1020                 return NULL;
1021         return &run_request[request - REQ_NONE - 1];
1024 static void
1025 add_builtin_run_requests(void)
1027         struct {
1028                 enum keymap keymap;
1029                 int key;
1030                 char *argv[1];
1031         } reqs[] = {
1032                 { KEYMAP_MAIN,    'C', { "git cherry-pick %(commit)" } },
1033                 { KEYMAP_GENERIC, 'G', { "git gc" } },
1034         };
1035         int i;
1037         for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1038                 enum request req;
1040                 req = add_run_request(reqs[i].keymap, reqs[i].key, 1, reqs[i].argv);
1041                 if (req != REQ_NONE)
1042                         add_keybinding(reqs[i].keymap, req, reqs[i].key);
1043         }
1046 /*
1047  * User config file handling.
1048  */
1050 static struct int_map color_map[] = {
1051 #define COLOR_MAP(name) { #name, STRING_SIZE(#name), COLOR_##name }
1052         COLOR_MAP(DEFAULT),
1053         COLOR_MAP(BLACK),
1054         COLOR_MAP(BLUE),
1055         COLOR_MAP(CYAN),
1056         COLOR_MAP(GREEN),
1057         COLOR_MAP(MAGENTA),
1058         COLOR_MAP(RED),
1059         COLOR_MAP(WHITE),
1060         COLOR_MAP(YELLOW),
1061 };
1063 #define set_color(color, name) \
1064         set_from_int_map(color_map, ARRAY_SIZE(color_map), color, name, strlen(name))
1066 static struct int_map attr_map[] = {
1067 #define ATTR_MAP(name) { #name, STRING_SIZE(#name), A_##name }
1068         ATTR_MAP(NORMAL),
1069         ATTR_MAP(BLINK),
1070         ATTR_MAP(BOLD),
1071         ATTR_MAP(DIM),
1072         ATTR_MAP(REVERSE),
1073         ATTR_MAP(STANDOUT),
1074         ATTR_MAP(UNDERLINE),
1075 };
1077 #define set_attribute(attr, name) \
1078         set_from_int_map(attr_map, ARRAY_SIZE(attr_map), attr, name, strlen(name))
1080 static int   config_lineno;
1081 static bool  config_errors;
1082 static char *config_msg;
1084 /* Wants: object fgcolor bgcolor [attr] */
1085 static int
1086 option_color_command(int argc, char *argv[])
1088         struct line_info *info;
1090         if (argc != 3 && argc != 4) {
1091                 config_msg = "Wrong number of arguments given to color command";
1092                 return ERR;
1093         }
1095         info = get_line_info(argv[0], strlen(argv[0]));
1096         if (!info) {
1097                 config_msg = "Unknown color name";
1098                 return ERR;
1099         }
1101         if (set_color(&info->fg, argv[1]) == ERR ||
1102             set_color(&info->bg, argv[2]) == ERR) {
1103                 config_msg = "Unknown color";
1104                 return ERR;
1105         }
1107         if (argc == 4 && set_attribute(&info->attr, argv[3]) == ERR) {
1108                 config_msg = "Unknown attribute";
1109                 return ERR;
1110         }
1112         return OK;
1115 /* Wants: name = value */
1116 static int
1117 option_set_command(int argc, char *argv[])
1119         if (argc != 3) {
1120                 config_msg = "Wrong number of arguments given to set command";
1121                 return ERR;
1122         }
1124         if (strcmp(argv[1], "=")) {
1125                 config_msg = "No value assigned";
1126                 return ERR;
1127         }
1129         if (!strcmp(argv[0], "show-rev-graph")) {
1130                 opt_rev_graph = (!strcmp(argv[2], "1") ||
1131                                  !strcmp(argv[2], "true") ||
1132                                  !strcmp(argv[2], "yes"));
1133                 return OK;
1134         }
1136         if (!strcmp(argv[0], "line-number-interval")) {
1137                 opt_num_interval = atoi(argv[2]);
1138                 return OK;
1139         }
1141         if (!strcmp(argv[0], "tab-size")) {
1142                 opt_tab_size = atoi(argv[2]);
1143                 return OK;
1144         }
1146         if (!strcmp(argv[0], "commit-encoding")) {
1147                 char *arg = argv[2];
1148                 int delimiter = *arg;
1149                 int i;
1151                 switch (delimiter) {
1152                 case '"':
1153                 case '\'':
1154                         for (arg++, i = 0; arg[i]; i++)
1155                                 if (arg[i] == delimiter) {
1156                                         arg[i] = 0;
1157                                         break;
1158                                 }
1159                 default:
1160                         string_ncopy(opt_encoding, arg, strlen(arg));
1161                         return OK;
1162                 }
1163         }
1165         config_msg = "Unknown variable name";
1166         return ERR;
1169 /* Wants: mode request key */
1170 static int
1171 option_bind_command(int argc, char *argv[])
1173         enum request request;
1174         int keymap;
1175         int key;
1177         if (argc < 3) {
1178                 config_msg = "Wrong number of arguments given to bind command";
1179                 return ERR;
1180         }
1182         if (set_keymap(&keymap, argv[0]) == ERR) {
1183                 config_msg = "Unknown key map";
1184                 return ERR;
1185         }
1187         key = get_key_value(argv[1]);
1188         if (key == ERR) {
1189                 config_msg = "Unknown key";
1190                 return ERR;
1191         }
1193         request = get_request(argv[2]);
1194         if (request == REQ_NONE) {
1195                 const char *obsolete[] = { "cherry-pick" };
1196                 size_t namelen = strlen(argv[2]);
1197                 int i;
1199                 for (i = 0; i < ARRAY_SIZE(obsolete); i++) {
1200                         if (namelen == strlen(obsolete[i]) &&
1201                             !string_enum_compare(obsolete[i], argv[2], namelen)) {
1202                                 config_msg = "Obsolete request name";
1203                                 return ERR;
1204                         }
1205                 }
1206         }
1207         if (request == REQ_NONE && *argv[2]++ == '!')
1208                 request = add_run_request(keymap, key, argc - 2, argv + 2);
1209         if (request == REQ_NONE) {
1210                 config_msg = "Unknown request name";
1211                 return ERR;
1212         }
1214         add_keybinding(keymap, request, key);
1216         return OK;
1219 static int
1220 set_option(char *opt, char *value)
1222         char *argv[16];
1223         int valuelen;
1224         int argc = 0;
1226         /* Tokenize */
1227         while (argc < ARRAY_SIZE(argv) && (valuelen = strcspn(value, " \t"))) {
1228                 argv[argc++] = value;
1229                 value += valuelen;
1231                 /* Nothing more to tokenize or last available token. */
1232                 if (!*value || argc >= ARRAY_SIZE(argv))
1233                         break;
1235                 *value++ = 0;
1236                 while (isspace(*value))
1237                         value++;
1238         }
1240         if (!strcmp(opt, "color"))
1241                 return option_color_command(argc, argv);
1243         if (!strcmp(opt, "set"))
1244                 return option_set_command(argc, argv);
1246         if (!strcmp(opt, "bind"))
1247                 return option_bind_command(argc, argv);
1249         config_msg = "Unknown option command";
1250         return ERR;
1253 static int
1254 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
1256         int status = OK;
1258         config_lineno++;
1259         config_msg = "Internal error";
1261         /* Check for comment markers, since read_properties() will
1262          * only ensure opt and value are split at first " \t". */
1263         optlen = strcspn(opt, "#");
1264         if (optlen == 0)
1265                 return OK;
1267         if (opt[optlen] != 0) {
1268                 config_msg = "No option value";
1269                 status = ERR;
1271         }  else {
1272                 /* Look for comment endings in the value. */
1273                 size_t len = strcspn(value, "#");
1275                 if (len < valuelen) {
1276                         valuelen = len;
1277                         value[valuelen] = 0;
1278                 }
1280                 status = set_option(opt, value);
1281         }
1283         if (status == ERR) {
1284                 fprintf(stderr, "Error on line %d, near '%.*s': %s\n",
1285                         config_lineno, (int) optlen, opt, config_msg);
1286                 config_errors = TRUE;
1287         }
1289         /* Always keep going if errors are encountered. */
1290         return OK;
1293 static void
1294 load_option_file(const char *path)
1296         FILE *file;
1298         /* It's ok that the file doesn't exist. */
1299         file = fopen(path, "r");
1300         if (!file)
1301                 return;
1303         config_lineno = 0;
1304         config_errors = FALSE;
1306         if (read_properties(file, " \t", read_option) == ERR ||
1307             config_errors == TRUE)
1308                 fprintf(stderr, "Errors while loading %s.\n", path);
1311 static int
1312 load_options(void)
1314         char *home = getenv("HOME");
1315         char *tigrc_user = getenv("TIGRC_USER");
1316         char *tigrc_system = getenv("TIGRC_SYSTEM");
1317         char buf[SIZEOF_STR];
1319         add_builtin_run_requests();
1321         if (!tigrc_system) {
1322                 if (!string_format(buf, "%s/tigrc", SYSCONFDIR))
1323                         return ERR;
1324                 tigrc_system = buf;
1325         }
1326         load_option_file(tigrc_system);
1328         if (!tigrc_user) {
1329                 if (!home || !string_format(buf, "%s/.tigrc", home))
1330                         return ERR;
1331                 tigrc_user = buf;
1332         }
1333         load_option_file(tigrc_user);
1335         return OK;
1339 /*
1340  * The viewer
1341  */
1343 struct view;
1344 struct view_ops;
1346 /* The display array of active views and the index of the current view. */
1347 static struct view *display[2];
1348 static unsigned int current_view;
1350 /* Reading from the prompt? */
1351 static bool input_mode = FALSE;
1353 #define foreach_displayed_view(view, i) \
1354         for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1356 #define displayed_views()       (display[1] != NULL ? 2 : 1)
1358 /* Current head and commit ID */
1359 static char ref_blob[SIZEOF_REF]        = "";
1360 static char ref_commit[SIZEOF_REF]      = "HEAD";
1361 static char ref_head[SIZEOF_REF]        = "HEAD";
1363 struct view {
1364         const char *name;       /* View name */
1365         const char *cmd_fmt;    /* Default command line format */
1366         const char *cmd_env;    /* Command line set via environment */
1367         const char *id;         /* Points to either of ref_{head,commit,blob} */
1369         struct view_ops *ops;   /* View operations */
1371         enum keymap keymap;     /* What keymap does this view have */
1373         char cmd[SIZEOF_STR];   /* Command buffer */
1374         char ref[SIZEOF_REF];   /* Hovered commit reference */
1375         char vid[SIZEOF_REF];   /* View ID. Set to id member when updating. */
1377         int height, width;      /* The width and height of the main window */
1378         WINDOW *win;            /* The main window */
1379         WINDOW *title;          /* The title window living below the main window */
1381         /* Navigation */
1382         unsigned long offset;   /* Offset of the window top */
1383         unsigned long lineno;   /* Current line number */
1385         /* Searching */
1386         char grep[SIZEOF_STR];  /* Search string */
1387         regex_t *regex;         /* Pre-compiled regex */
1389         /* If non-NULL, points to the view that opened this view. If this view
1390          * is closed tig will switch back to the parent view. */
1391         struct view *parent;
1393         /* Buffering */
1394         unsigned long lines;    /* Total number of lines */
1395         struct line *line;      /* Line index */
1396         unsigned long line_size;/* Total number of allocated lines */
1397         unsigned int digits;    /* Number of digits in the lines member. */
1399         /* Loading */
1400         FILE *pipe;
1401         time_t start_time;
1402 };
1404 struct view_ops {
1405         /* What type of content being displayed. Used in the title bar. */
1406         const char *type;
1407         /* Open and reads in all view content. */
1408         bool (*open)(struct view *view);
1409         /* Read one line; updates view->line. */
1410         bool (*read)(struct view *view, char *data);
1411         /* Draw one line; @lineno must be < view->height. */
1412         bool (*draw)(struct view *view, struct line *line, unsigned int lineno, bool selected);
1413         /* Depending on view handle a special requests. */
1414         enum request (*request)(struct view *view, enum request request, struct line *line);
1415         /* Search for regex in a line. */
1416         bool (*grep)(struct view *view, struct line *line);
1417         /* Select line */
1418         void (*select)(struct view *view, struct line *line);
1419 };
1421 static struct view_ops pager_ops;
1422 static struct view_ops main_ops;
1423 static struct view_ops tree_ops;
1424 static struct view_ops blob_ops;
1425 static struct view_ops help_ops;
1426 static struct view_ops status_ops;
1427 static struct view_ops stage_ops;
1429 #define VIEW_STR(name, cmd, env, ref, ops, map) \
1430         { name, cmd, #env, ref, ops, map}
1432 #define VIEW_(id, name, ops, ref) \
1433         VIEW_STR(name, TIG_##id##_CMD,  TIG_##id##_CMD, ref, ops, KEYMAP_##id)
1436 static struct view views[] = {
1437         VIEW_(MAIN,   "main",   &main_ops,   ref_head),
1438         VIEW_(DIFF,   "diff",   &pager_ops,  ref_commit),
1439         VIEW_(LOG,    "log",    &pager_ops,  ref_head),
1440         VIEW_(TREE,   "tree",   &tree_ops,   ref_commit),
1441         VIEW_(BLOB,   "blob",   &blob_ops,   ref_blob),
1442         VIEW_(HELP,   "help",   &help_ops,   ""),
1443         VIEW_(PAGER,  "pager",  &pager_ops,  "stdin"),
1444         VIEW_(STATUS, "status", &status_ops, ""),
1445         VIEW_(STAGE,  "stage",  &stage_ops,  ""),
1446 };
1448 #define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
1450 #define foreach_view(view, i) \
1451         for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
1453 #define view_is_displayed(view) \
1454         (view == display[0] || view == display[1])
1456 static bool
1457 draw_view_line(struct view *view, unsigned int lineno)
1459         struct line *line;
1460         bool selected = (view->offset + lineno == view->lineno);
1461         bool draw_ok;
1463         assert(view_is_displayed(view));
1465         if (view->offset + lineno >= view->lines)
1466                 return FALSE;
1468         line = &view->line[view->offset + lineno];
1470         if (selected) {
1471                 line->selected = TRUE;
1472                 view->ops->select(view, line);
1473         } else if (line->selected) {
1474                 line->selected = FALSE;
1475                 wmove(view->win, lineno, 0);
1476                 wclrtoeol(view->win);
1477         }
1479         scrollok(view->win, FALSE);
1480         draw_ok = view->ops->draw(view, line, lineno, selected);
1481         scrollok(view->win, TRUE);
1483         return draw_ok;
1486 static void
1487 redraw_view_from(struct view *view, int lineno)
1489         assert(0 <= lineno && lineno < view->height);
1491         for (; lineno < view->height; lineno++) {
1492                 if (!draw_view_line(view, lineno))
1493                         break;
1494         }
1496         redrawwin(view->win);
1497         if (input_mode)
1498                 wnoutrefresh(view->win);
1499         else
1500                 wrefresh(view->win);
1503 static void
1504 redraw_view(struct view *view)
1506         wclear(view->win);
1507         redraw_view_from(view, 0);
1511 static void
1512 update_view_title(struct view *view)
1514         char buf[SIZEOF_STR];
1515         char state[SIZEOF_STR];
1516         size_t bufpos = 0, statelen = 0;
1518         assert(view_is_displayed(view));
1520         if (view != VIEW(REQ_VIEW_STATUS) && (view->lines || view->pipe)) {
1521                 unsigned int view_lines = view->offset + view->height;
1522                 unsigned int lines = view->lines
1523                                    ? MIN(view_lines, view->lines) * 100 / view->lines
1524                                    : 0;
1526                 string_format_from(state, &statelen, "- %s %d of %d (%d%%)",
1527                                    view->ops->type,
1528                                    view->lineno + 1,
1529                                    view->lines,
1530                                    lines);
1532                 if (view->pipe) {
1533                         time_t secs = time(NULL) - view->start_time;
1535                         /* Three git seconds are a long time ... */
1536                         if (secs > 2)
1537                                 string_format_from(state, &statelen, " %lds", secs);
1538                 }
1539         }
1541         string_format_from(buf, &bufpos, "[%s]", view->name);
1542         if (*view->ref && bufpos < view->width) {
1543                 size_t refsize = strlen(view->ref);
1544                 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
1546                 if (minsize < view->width)
1547                         refsize = view->width - minsize + 7;
1548                 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
1549         }
1551         if (statelen && bufpos < view->width) {
1552                 string_format_from(buf, &bufpos, " %s", state);
1553         }
1555         if (view == display[current_view])
1556                 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
1557         else
1558                 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
1560         mvwaddnstr(view->title, 0, 0, buf, bufpos);
1561         wclrtoeol(view->title);
1562         wmove(view->title, 0, view->width - 1);
1564         if (input_mode)
1565                 wnoutrefresh(view->title);
1566         else
1567                 wrefresh(view->title);
1570 static void
1571 resize_display(void)
1573         int offset, i;
1574         struct view *base = display[0];
1575         struct view *view = display[1] ? display[1] : display[0];
1577         /* Setup window dimensions */
1579         getmaxyx(stdscr, base->height, base->width);
1581         /* Make room for the status window. */
1582         base->height -= 1;
1584         if (view != base) {
1585                 /* Horizontal split. */
1586                 view->width   = base->width;
1587                 view->height  = SCALE_SPLIT_VIEW(base->height);
1588                 base->height -= view->height;
1590                 /* Make room for the title bar. */
1591                 view->height -= 1;
1592         }
1594         /* Make room for the title bar. */
1595         base->height -= 1;
1597         offset = 0;
1599         foreach_displayed_view (view, i) {
1600                 if (!view->win) {
1601                         view->win = newwin(view->height, 0, offset, 0);
1602                         if (!view->win)
1603                                 die("Failed to create %s view", view->name);
1605                         scrollok(view->win, TRUE);
1607                         view->title = newwin(1, 0, offset + view->height, 0);
1608                         if (!view->title)
1609                                 die("Failed to create title window");
1611                 } else {
1612                         wresize(view->win, view->height, view->width);
1613                         mvwin(view->win,   offset, 0);
1614                         mvwin(view->title, offset + view->height, 0);
1615                 }
1617                 offset += view->height + 1;
1618         }
1621 static void
1622 redraw_display(void)
1624         struct view *view;
1625         int i;
1627         foreach_displayed_view (view, i) {
1628                 redraw_view(view);
1629                 update_view_title(view);
1630         }
1633 static void
1634 update_display_cursor(struct view *view)
1636         /* Move the cursor to the right-most column of the cursor line.
1637          *
1638          * XXX: This could turn out to be a bit expensive, but it ensures that
1639          * the cursor does not jump around. */
1640         if (view->lines) {
1641                 wmove(view->win, view->lineno - view->offset, view->width - 1);
1642                 wrefresh(view->win);
1643         }
1646 /*
1647  * Navigation
1648  */
1650 /* Scrolling backend */
1651 static void
1652 do_scroll_view(struct view *view, int lines)
1654         bool redraw_current_line = FALSE;
1656         /* The rendering expects the new offset. */
1657         view->offset += lines;
1659         assert(0 <= view->offset && view->offset < view->lines);
1660         assert(lines);
1662         /* Move current line into the view. */
1663         if (view->lineno < view->offset) {
1664                 view->lineno = view->offset;
1665                 redraw_current_line = TRUE;
1666         } else if (view->lineno >= view->offset + view->height) {
1667                 view->lineno = view->offset + view->height - 1;
1668                 redraw_current_line = TRUE;
1669         }
1671         assert(view->offset <= view->lineno && view->lineno < view->lines);
1673         /* Redraw the whole screen if scrolling is pointless. */
1674         if (view->height < ABS(lines)) {
1675                 redraw_view(view);
1677         } else {
1678                 int line = lines > 0 ? view->height - lines : 0;
1679                 int end = line + ABS(lines);
1681                 wscrl(view->win, lines);
1683                 for (; line < end; line++) {
1684                         if (!draw_view_line(view, line))
1685                                 break;
1686                 }
1688                 if (redraw_current_line)
1689                         draw_view_line(view, view->lineno - view->offset);
1690         }
1692         redrawwin(view->win);
1693         wrefresh(view->win);
1694         report("");
1697 /* Scroll frontend */
1698 static void
1699 scroll_view(struct view *view, enum request request)
1701         int lines = 1;
1703         assert(view_is_displayed(view));
1705         switch (request) {
1706         case REQ_SCROLL_PAGE_DOWN:
1707                 lines = view->height;
1708         case REQ_SCROLL_LINE_DOWN:
1709                 if (view->offset + lines > view->lines)
1710                         lines = view->lines - view->offset;
1712                 if (lines == 0 || view->offset + view->height >= view->lines) {
1713                         report("Cannot scroll beyond the last line");
1714                         return;
1715                 }
1716                 break;
1718         case REQ_SCROLL_PAGE_UP:
1719                 lines = view->height;
1720         case REQ_SCROLL_LINE_UP:
1721                 if (lines > view->offset)
1722                         lines = view->offset;
1724                 if (lines == 0) {
1725                         report("Cannot scroll beyond the first line");
1726                         return;
1727                 }
1729                 lines = -lines;
1730                 break;
1732         default:
1733                 die("request %d not handled in switch", request);
1734         }
1736         do_scroll_view(view, lines);
1739 /* Cursor moving */
1740 static void
1741 move_view(struct view *view, enum request request)
1743         int scroll_steps = 0;
1744         int steps;
1746         switch (request) {
1747         case REQ_MOVE_FIRST_LINE:
1748                 steps = -view->lineno;
1749                 break;
1751         case REQ_MOVE_LAST_LINE:
1752                 steps = view->lines - view->lineno - 1;
1753                 break;
1755         case REQ_MOVE_PAGE_UP:
1756                 steps = view->height > view->lineno
1757                       ? -view->lineno : -view->height;
1758                 break;
1760         case REQ_MOVE_PAGE_DOWN:
1761                 steps = view->lineno + view->height >= view->lines
1762                       ? view->lines - view->lineno - 1 : view->height;
1763                 break;
1765         case REQ_MOVE_UP:
1766                 steps = -1;
1767                 break;
1769         case REQ_MOVE_DOWN:
1770                 steps = 1;
1771                 break;
1773         default:
1774                 die("request %d not handled in switch", request);
1775         }
1777         if (steps <= 0 && view->lineno == 0) {
1778                 report("Cannot move beyond the first line");
1779                 return;
1781         } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
1782                 report("Cannot move beyond the last line");
1783                 return;
1784         }
1786         /* Move the current line */
1787         view->lineno += steps;
1788         assert(0 <= view->lineno && view->lineno < view->lines);
1790         /* Check whether the view needs to be scrolled */
1791         if (view->lineno < view->offset ||
1792             view->lineno >= view->offset + view->height) {
1793                 scroll_steps = steps;
1794                 if (steps < 0 && -steps > view->offset) {
1795                         scroll_steps = -view->offset;
1797                 } else if (steps > 0) {
1798                         if (view->lineno == view->lines - 1 &&
1799                             view->lines > view->height) {
1800                                 scroll_steps = view->lines - view->offset - 1;
1801                                 if (scroll_steps >= view->height)
1802                                         scroll_steps -= view->height - 1;
1803                         }
1804                 }
1805         }
1807         if (!view_is_displayed(view)) {
1808                 view->offset += scroll_steps;
1809                 assert(0 <= view->offset && view->offset < view->lines);
1810                 view->ops->select(view, &view->line[view->lineno]);
1811                 return;
1812         }
1814         /* Repaint the old "current" line if we be scrolling */
1815         if (ABS(steps) < view->height)
1816                 draw_view_line(view, view->lineno - steps - view->offset);
1818         if (scroll_steps) {
1819                 do_scroll_view(view, scroll_steps);
1820                 return;
1821         }
1823         /* Draw the current line */
1824         draw_view_line(view, view->lineno - view->offset);
1826         redrawwin(view->win);
1827         wrefresh(view->win);
1828         report("");
1832 /*
1833  * Searching
1834  */
1836 static void search_view(struct view *view, enum request request);
1838 static bool
1839 find_next_line(struct view *view, unsigned long lineno, struct line *line)
1841         assert(view_is_displayed(view));
1843         if (!view->ops->grep(view, line))
1844                 return FALSE;
1846         if (lineno - view->offset >= view->height) {
1847                 view->offset = lineno;
1848                 view->lineno = lineno;
1849                 redraw_view(view);
1851         } else {
1852                 unsigned long old_lineno = view->lineno - view->offset;
1854                 view->lineno = lineno;
1855                 draw_view_line(view, old_lineno);
1857                 draw_view_line(view, view->lineno - view->offset);
1858                 redrawwin(view->win);
1859                 wrefresh(view->win);
1860         }
1862         report("Line %ld matches '%s'", lineno + 1, view->grep);
1863         return TRUE;
1866 static void
1867 find_next(struct view *view, enum request request)
1869         unsigned long lineno = view->lineno;
1870         int direction;
1872         if (!*view->grep) {
1873                 if (!*opt_search)
1874                         report("No previous search");
1875                 else
1876                         search_view(view, request);
1877                 return;
1878         }
1880         switch (request) {
1881         case REQ_SEARCH:
1882         case REQ_FIND_NEXT:
1883                 direction = 1;
1884                 break;
1886         case REQ_SEARCH_BACK:
1887         case REQ_FIND_PREV:
1888                 direction = -1;
1889                 break;
1891         default:
1892                 return;
1893         }
1895         if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
1896                 lineno += direction;
1898         /* Note, lineno is unsigned long so will wrap around in which case it
1899          * will become bigger than view->lines. */
1900         for (; lineno < view->lines; lineno += direction) {
1901                 struct line *line = &view->line[lineno];
1903                 if (find_next_line(view, lineno, line))
1904                         return;
1905         }
1907         report("No match found for '%s'", view->grep);
1910 static void
1911 search_view(struct view *view, enum request request)
1913         int regex_err;
1915         if (view->regex) {
1916                 regfree(view->regex);
1917                 *view->grep = 0;
1918         } else {
1919                 view->regex = calloc(1, sizeof(*view->regex));
1920                 if (!view->regex)
1921                         return;
1922         }
1924         regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
1925         if (regex_err != 0) {
1926                 char buf[SIZEOF_STR] = "unknown error";
1928                 regerror(regex_err, view->regex, buf, sizeof(buf));
1929                 report("Search failed: %s", buf);
1930                 return;
1931         }
1933         string_copy(view->grep, opt_search);
1935         find_next(view, request);
1938 /*
1939  * Incremental updating
1940  */
1942 static void
1943 end_update(struct view *view)
1945         if (!view->pipe)
1946                 return;
1947         set_nonblocking_input(FALSE);
1948         if (view->pipe == stdin)
1949                 fclose(view->pipe);
1950         else
1951                 pclose(view->pipe);
1952         view->pipe = NULL;
1955 static bool
1956 begin_update(struct view *view)
1958         if (view->pipe)
1959                 end_update(view);
1961         if (opt_cmd[0]) {
1962                 string_copy(view->cmd, opt_cmd);
1963                 opt_cmd[0] = 0;
1964                 /* When running random commands, initially show the
1965                  * command in the title. However, it maybe later be
1966                  * overwritten if a commit line is selected. */
1967                 if (view == VIEW(REQ_VIEW_PAGER))
1968                         string_copy(view->ref, view->cmd);
1969                 else
1970                         view->ref[0] = 0;
1972         } else if (view == VIEW(REQ_VIEW_TREE)) {
1973                 const char *format = view->cmd_env ? view->cmd_env : view->cmd_fmt;
1974                 char path[SIZEOF_STR];
1976                 if (strcmp(view->vid, view->id))
1977                         opt_path[0] = path[0] = 0;
1978                 else if (sq_quote(path, 0, opt_path) >= sizeof(path))
1979                         return FALSE;
1981                 if (!string_format(view->cmd, format, view->id, path))
1982                         return FALSE;
1984         } else {
1985                 const char *format = view->cmd_env ? view->cmd_env : view->cmd_fmt;
1986                 const char *id = view->id;
1988                 if (!string_format(view->cmd, format, id, id, id, id, id))
1989                         return FALSE;
1991                 /* Put the current ref_* value to the view title ref
1992                  * member. This is needed by the blob view. Most other
1993                  * views sets it automatically after loading because the
1994                  * first line is a commit line. */
1995                 string_copy_rev(view->ref, view->id);
1996         }
1998         /* Special case for the pager view. */
1999         if (opt_pipe) {
2000                 view->pipe = opt_pipe;
2001                 opt_pipe = NULL;
2002         } else {
2003                 view->pipe = popen(view->cmd, "r");
2004         }
2006         if (!view->pipe)
2007                 return FALSE;
2009         set_nonblocking_input(TRUE);
2011         view->offset = 0;
2012         view->lines  = 0;
2013         view->lineno = 0;
2014         string_copy_rev(view->vid, view->id);
2016         if (view->line) {
2017                 int i;
2019                 for (i = 0; i < view->lines; i++)
2020                         if (view->line[i].data)
2021                                 free(view->line[i].data);
2023                 free(view->line);
2024                 view->line = NULL;
2025         }
2027         view->start_time = time(NULL);
2029         return TRUE;
2032 static struct line *
2033 realloc_lines(struct view *view, size_t line_size)
2035         struct line *tmp = realloc(view->line, sizeof(*view->line) * line_size);
2037         if (!tmp)
2038                 return NULL;
2040         view->line = tmp;
2041         view->line_size = line_size;
2042         return view->line;
2045 static bool
2046 update_view(struct view *view)
2048         char in_buffer[BUFSIZ];
2049         char out_buffer[BUFSIZ * 2];
2050         char *line;
2051         /* The number of lines to read. If too low it will cause too much
2052          * redrawing (and possible flickering), if too high responsiveness
2053          * will suffer. */
2054         unsigned long lines = view->height;
2055         int redraw_from = -1;
2057         if (!view->pipe)
2058                 return TRUE;
2060         /* Only redraw if lines are visible. */
2061         if (view->offset + view->height >= view->lines)
2062                 redraw_from = view->lines - view->offset;
2064         /* FIXME: This is probably not perfect for backgrounded views. */
2065         if (!realloc_lines(view, view->lines + lines))
2066                 goto alloc_error;
2068         while ((line = fgets(in_buffer, sizeof(in_buffer), view->pipe))) {
2069                 size_t linelen = strlen(line);
2071                 if (linelen)
2072                         line[linelen - 1] = 0;
2074                 if (opt_iconv != ICONV_NONE) {
2075                         ICONV_CONST char *inbuf = line;
2076                         size_t inlen = linelen;
2078                         char *outbuf = out_buffer;
2079                         size_t outlen = sizeof(out_buffer);
2081                         size_t ret;
2083                         ret = iconv(opt_iconv, &inbuf, &inlen, &outbuf, &outlen);
2084                         if (ret != (size_t) -1) {
2085                                 line = out_buffer;
2086                                 linelen = strlen(out_buffer);
2087                         }
2088                 }
2090                 if (!view->ops->read(view, line))
2091                         goto alloc_error;
2093                 if (lines-- == 1)
2094                         break;
2095         }
2097         {
2098                 int digits;
2100                 lines = view->lines;
2101                 for (digits = 0; lines; digits++)
2102                         lines /= 10;
2104                 /* Keep the displayed view in sync with line number scaling. */
2105                 if (digits != view->digits) {
2106                         view->digits = digits;
2107                         redraw_from = 0;
2108                 }
2109         }
2111         if (!view_is_displayed(view))
2112                 goto check_pipe;
2114         if (view == VIEW(REQ_VIEW_TREE)) {
2115                 /* Clear the view and redraw everything since the tree sorting
2116                  * might have rearranged things. */
2117                 redraw_view(view);
2119         } else if (redraw_from >= 0) {
2120                 /* If this is an incremental update, redraw the previous line
2121                  * since for commits some members could have changed when
2122                  * loading the main view. */
2123                 if (redraw_from > 0)
2124                         redraw_from--;
2126                 /* Since revision graph visualization requires knowledge
2127                  * about the parent commit, it causes a further one-off
2128                  * needed to be redrawn for incremental updates. */
2129                 if (redraw_from > 0 && opt_rev_graph)
2130                         redraw_from--;
2132                 /* Incrementally draw avoids flickering. */
2133                 redraw_view_from(view, redraw_from);
2134         }
2136         /* Update the title _after_ the redraw so that if the redraw picks up a
2137          * commit reference in view->ref it'll be available here. */
2138         update_view_title(view);
2140 check_pipe:
2141         if (ferror(view->pipe)) {
2142                 report("Failed to read: %s", strerror(errno));
2143                 goto end;
2145         } else if (feof(view->pipe)) {
2146                 report("");
2147                 goto end;
2148         }
2150         return TRUE;
2152 alloc_error:
2153         report("Allocation failure");
2155 end:
2156         view->ops->read(view, NULL);
2157         end_update(view);
2158         return FALSE;
2161 static struct line *
2162 add_line_data(struct view *view, void *data, enum line_type type)
2164         struct line *line = &view->line[view->lines++];
2166         memset(line, 0, sizeof(*line));
2167         line->type = type;
2168         line->data = data;
2170         return line;
2173 static struct line *
2174 add_line_text(struct view *view, char *data, enum line_type type)
2176         if (data)
2177                 data = strdup(data);
2179         return data ? add_line_data(view, data, type) : NULL;
2183 /*
2184  * View opening
2185  */
2187 enum open_flags {
2188         OPEN_DEFAULT = 0,       /* Use default view switching. */
2189         OPEN_SPLIT = 1,         /* Split current view. */
2190         OPEN_BACKGROUNDED = 2,  /* Backgrounded. */
2191         OPEN_RELOAD = 4,        /* Reload view even if it is the current. */
2192 };
2194 static void
2195 open_view(struct view *prev, enum request request, enum open_flags flags)
2197         bool backgrounded = !!(flags & OPEN_BACKGROUNDED);
2198         bool split = !!(flags & OPEN_SPLIT);
2199         bool reload = !!(flags & OPEN_RELOAD);
2200         struct view *view = VIEW(request);
2201         int nviews = displayed_views();
2202         struct view *base_view = display[0];
2204         if (view == prev && nviews == 1 && !reload) {
2205                 report("Already in %s view", view->name);
2206                 return;
2207         }
2209         if (view->ops->open) {
2210                 if (!view->ops->open(view)) {
2211                         report("Failed to load %s view", view->name);
2212                         return;
2213                 }
2215         } else if ((reload || strcmp(view->vid, view->id)) &&
2216                    !begin_update(view)) {
2217                 report("Failed to load %s view", view->name);
2218                 return;
2219         }
2221         if (split) {
2222                 display[1] = view;
2223                 if (!backgrounded)
2224                         current_view = 1;
2225         } else {
2226                 /* Maximize the current view. */
2227                 memset(display, 0, sizeof(display));
2228                 current_view = 0;
2229                 display[current_view] = view;
2230         }
2232         /* Resize the view when switching between split- and full-screen,
2233          * or when switching between two different full-screen views. */
2234         if (nviews != displayed_views() ||
2235             (nviews == 1 && base_view != display[0]))
2236                 resize_display();
2238         if (split && prev->lineno - prev->offset >= prev->height) {
2239                 /* Take the title line into account. */
2240                 int lines = prev->lineno - prev->offset - prev->height + 1;
2242                 /* Scroll the view that was split if the current line is
2243                  * outside the new limited view. */
2244                 do_scroll_view(prev, lines);
2245         }
2247         if (prev && view != prev) {
2248                 if (split && !backgrounded) {
2249                         /* "Blur" the previous view. */
2250                         update_view_title(prev);
2251                 }
2253                 view->parent = prev;
2254         }
2256         if (view->pipe && view->lines == 0) {
2257                 /* Clear the old view and let the incremental updating refill
2258                  * the screen. */
2259                 wclear(view->win);
2260                 report("");
2261         } else {
2262                 redraw_view(view);
2263                 report("");
2264         }
2266         /* If the view is backgrounded the above calls to report()
2267          * won't redraw the view title. */
2268         if (backgrounded)
2269                 update_view_title(view);
2272 static void
2273 open_external_viewer(const char *cmd)
2275         def_prog_mode();           /* save current tty modes */
2276         endwin();                  /* restore original tty modes */
2277         system(cmd);
2278         fprintf(stderr, "Press Enter to continue");
2279         getc(stdin);
2280         reset_prog_mode();
2281         redraw_display();
2284 static void
2285 open_mergetool(const char *file)
2287         char cmd[SIZEOF_STR];
2288         char file_sq[SIZEOF_STR];
2290         if (sq_quote(file_sq, 0, file) < sizeof(file_sq) &&
2291             string_format(cmd, "git mergetool %s", file_sq)) {
2292                 open_external_viewer(cmd);
2293         }
2296 static void
2297 open_editor(bool from_root, const char *file)
2299         char cmd[SIZEOF_STR];
2300         char file_sq[SIZEOF_STR];
2301         char *editor;
2302         char *prefix = from_root ? opt_cdup : "";
2304         editor = getenv("GIT_EDITOR");
2305         if (!editor && *opt_editor)
2306                 editor = opt_editor;
2307         if (!editor)
2308                 editor = getenv("VISUAL");
2309         if (!editor)
2310                 editor = getenv("EDITOR");
2311         if (!editor)
2312                 editor = "vi";
2314         if (sq_quote(file_sq, 0, file) < sizeof(file_sq) &&
2315             string_format(cmd, "%s %s%s", editor, prefix, file_sq)) {
2316                 open_external_viewer(cmd);
2317         }
2320 static void
2321 open_run_request(enum request request)
2323         struct run_request *req = get_run_request(request);
2324         char buf[SIZEOF_STR * 2];
2325         size_t bufpos;
2326         char *cmd;
2328         if (!req) {
2329                 report("Unknown run request");
2330                 return;
2331         }
2333         bufpos = 0;
2334         cmd = req->cmd;
2336         while (cmd) {
2337                 char *next = strstr(cmd, "%(");
2338                 int len = next - cmd;
2339                 char *value;
2341                 if (!next) {
2342                         len = strlen(cmd);
2343                         value = "";
2345                 } else if (!strncmp(next, "%(head)", 7)) {
2346                         value = ref_head;
2348                 } else if (!strncmp(next, "%(commit)", 9)) {
2349                         value = ref_commit;
2351                 } else if (!strncmp(next, "%(blob)", 7)) {
2352                         value = ref_blob;
2354                 } else {
2355                         report("Unknown replacement in run request: `%s`", req->cmd);
2356                         return;
2357                 }
2359                 if (!string_format_from(buf, &bufpos, "%.*s%s", len, cmd, value))
2360                         return;
2362                 if (next)
2363                         next = strchr(next, ')') + 1;
2364                 cmd = next;
2365         }
2367         open_external_viewer(buf);
2370 /*
2371  * User request switch noodle
2372  */
2374 static int
2375 view_driver(struct view *view, enum request request)
2377         int i;
2379         if (request == REQ_NONE) {
2380                 doupdate();
2381                 return TRUE;
2382         }
2384         if (request > REQ_NONE) {
2385                 open_run_request(request);
2386                 return TRUE;
2387         }
2389         if (view && view->lines) {
2390                 request = view->ops->request(view, request, &view->line[view->lineno]);
2391                 if (request == REQ_NONE)
2392                         return TRUE;
2393         }
2395         switch (request) {
2396         case REQ_MOVE_UP:
2397         case REQ_MOVE_DOWN:
2398         case REQ_MOVE_PAGE_UP:
2399         case REQ_MOVE_PAGE_DOWN:
2400         case REQ_MOVE_FIRST_LINE:
2401         case REQ_MOVE_LAST_LINE:
2402                 move_view(view, request);
2403                 break;
2405         case REQ_SCROLL_LINE_DOWN:
2406         case REQ_SCROLL_LINE_UP:
2407         case REQ_SCROLL_PAGE_DOWN:
2408         case REQ_SCROLL_PAGE_UP:
2409                 scroll_view(view, request);
2410                 break;
2412         case REQ_VIEW_BLOB:
2413                 if (!ref_blob[0]) {
2414                         report("No file chosen, press %s to open tree view",
2415                                get_key(REQ_VIEW_TREE));
2416                         break;
2417                 }
2418                 open_view(view, request, OPEN_DEFAULT);
2419                 break;
2421         case REQ_VIEW_PAGER:
2422                 if (!opt_pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
2423                         report("No pager content, press %s to run command from prompt",
2424                                get_key(REQ_PROMPT));
2425                         break;
2426                 }
2427                 open_view(view, request, OPEN_DEFAULT);
2428                 break;
2430         case REQ_VIEW_STAGE:
2431                 if (!VIEW(REQ_VIEW_STAGE)->lines) {
2432                         report("No stage content, press %s to open the status view and choose file",
2433                                get_key(REQ_VIEW_STATUS));
2434                         break;
2435                 }
2436                 open_view(view, request, OPEN_DEFAULT);
2437                 break;
2439         case REQ_VIEW_STATUS:
2440                 if (opt_is_inside_work_tree == FALSE) {
2441                         report("The status view requires a working tree");
2442                         break;
2443                 }
2444                 open_view(view, request, OPEN_DEFAULT);
2445                 break;
2447         case REQ_VIEW_MAIN:
2448         case REQ_VIEW_DIFF:
2449         case REQ_VIEW_LOG:
2450         case REQ_VIEW_TREE:
2451         case REQ_VIEW_HELP:
2452                 open_view(view, request, OPEN_DEFAULT);
2453                 break;
2455         case REQ_NEXT:
2456         case REQ_PREVIOUS:
2457                 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
2459                 if ((view == VIEW(REQ_VIEW_DIFF) &&
2460                      view->parent == VIEW(REQ_VIEW_MAIN)) ||
2461                    (view == VIEW(REQ_VIEW_STAGE) &&
2462                      view->parent == VIEW(REQ_VIEW_STATUS)) ||
2463                    (view == VIEW(REQ_VIEW_BLOB) &&
2464                      view->parent == VIEW(REQ_VIEW_TREE))) {
2465                         int line;
2467                         view = view->parent;
2468                         line = view->lineno;
2469                         move_view(view, request);
2470                         if (view_is_displayed(view))
2471                                 update_view_title(view);
2472                         if (line != view->lineno)
2473                                 view->ops->request(view, REQ_ENTER,
2474                                                    &view->line[view->lineno]);
2476                 } else {
2477                         move_view(view, request);
2478                 }
2479                 break;
2481         case REQ_VIEW_NEXT:
2482         {
2483                 int nviews = displayed_views();
2484                 int next_view = (current_view + 1) % nviews;
2486                 if (next_view == current_view) {
2487                         report("Only one view is displayed");
2488                         break;
2489                 }
2491                 current_view = next_view;
2492                 /* Blur out the title of the previous view. */
2493                 update_view_title(view);
2494                 report("");
2495                 break;
2496         }
2497         case REQ_REFRESH:
2498                 report("Refreshing is not yet supported for the %s view", view->name);
2499                 break;
2501         case REQ_TOGGLE_LINENO:
2502                 opt_line_number = !opt_line_number;
2503                 redraw_display();
2504                 break;
2506         case REQ_TOGGLE_REV_GRAPH:
2507                 opt_rev_graph = !opt_rev_graph;
2508                 redraw_display();
2509                 break;
2511         case REQ_PROMPT:
2512                 /* Always reload^Wrerun commands from the prompt. */
2513                 open_view(view, opt_request, OPEN_RELOAD);
2514                 break;
2516         case REQ_SEARCH:
2517         case REQ_SEARCH_BACK:
2518                 search_view(view, request);
2519                 break;
2521         case REQ_FIND_NEXT:
2522         case REQ_FIND_PREV:
2523                 find_next(view, request);
2524                 break;
2526         case REQ_STOP_LOADING:
2527                 for (i = 0; i < ARRAY_SIZE(views); i++) {
2528                         view = &views[i];
2529                         if (view->pipe)
2530                                 report("Stopped loading the %s view", view->name),
2531                         end_update(view);
2532                 }
2533                 break;
2535         case REQ_SHOW_VERSION:
2536                 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
2537                 return TRUE;
2539         case REQ_SCREEN_RESIZE:
2540                 resize_display();
2541                 /* Fall-through */
2542         case REQ_SCREEN_REDRAW:
2543                 redraw_display();
2544                 break;
2546         case REQ_EDIT:
2547                 report("Nothing to edit");
2548                 break;
2551         case REQ_ENTER:
2552                 report("Nothing to enter");
2553                 break;
2556         case REQ_VIEW_CLOSE:
2557                 /* XXX: Mark closed views by letting view->parent point to the
2558                  * view itself. Parents to closed view should never be
2559                  * followed. */
2560                 if (view->parent &&
2561                     view->parent->parent != view->parent) {
2562                         memset(display, 0, sizeof(display));
2563                         current_view = 0;
2564                         display[current_view] = view->parent;
2565                         view->parent = view;
2566                         resize_display();
2567                         redraw_display();
2568                         break;
2569                 }
2570                 /* Fall-through */
2571         case REQ_QUIT:
2572                 return FALSE;
2574         default:
2575                 /* An unknown key will show most commonly used commands. */
2576                 report("Unknown key, press 'h' for help");
2577                 return TRUE;
2578         }
2580         return TRUE;
2584 /*
2585  * Pager backend
2586  */
2588 static bool
2589 pager_draw(struct view *view, struct line *line, unsigned int lineno, bool selected)
2591         char *text = line->data;
2592         enum line_type type = line->type;
2593         int textlen = strlen(text);
2594         int attr;
2596         wmove(view->win, lineno, 0);
2598         if (selected) {
2599                 type = LINE_CURSOR;
2600                 wchgat(view->win, -1, 0, type, NULL);
2601         }
2603         attr = get_line_attr(type);
2604         wattrset(view->win, attr);
2606         if (opt_line_number || opt_tab_size < TABSIZE) {
2607                 static char spaces[] = "                    ";
2608                 int col_offset = 0, col = 0;
2610                 if (opt_line_number) {
2611                         unsigned long real_lineno = view->offset + lineno + 1;
2613                         if (real_lineno == 1 ||
2614                             (real_lineno % opt_num_interval) == 0) {
2615                                 wprintw(view->win, "%.*d", view->digits, real_lineno);
2617                         } else {
2618                                 waddnstr(view->win, spaces,
2619                                          MIN(view->digits, STRING_SIZE(spaces)));
2620                         }
2621                         waddstr(view->win, ": ");
2622                         col_offset = view->digits + 2;
2623                 }
2625                 while (text && col_offset + col < view->width) {
2626                         int cols_max = view->width - col_offset - col;
2627                         char *pos = text;
2628                         int cols;
2630                         if (*text == '\t') {
2631                                 text++;
2632                                 assert(sizeof(spaces) > TABSIZE);
2633                                 pos = spaces;
2634                                 cols = opt_tab_size - (col % opt_tab_size);
2636                         } else {
2637                                 text = strchr(text, '\t');
2638                                 cols = line ? text - pos : strlen(pos);
2639                         }
2641                         waddnstr(view->win, pos, MIN(cols, cols_max));
2642                         col += cols;
2643                 }
2645         } else {
2646                 int col = 0, pos = 0;
2648                 for (; pos < textlen && col < view->width; pos++, col++)
2649                         if (text[pos] == '\t')
2650                                 col += TABSIZE - (col % TABSIZE) - 1;
2652                 waddnstr(view->win, text, pos);
2653         }
2655         return TRUE;
2658 static bool
2659 add_describe_ref(char *buf, size_t *bufpos, char *commit_id, const char *sep)
2661         char refbuf[SIZEOF_STR];
2662         char *ref = NULL;
2663         FILE *pipe;
2665         if (!string_format(refbuf, "git describe %s 2>/dev/null", commit_id))
2666                 return TRUE;
2668         pipe = popen(refbuf, "r");
2669         if (!pipe)
2670                 return TRUE;
2672         if ((ref = fgets(refbuf, sizeof(refbuf), pipe)))
2673                 ref = chomp_string(ref);
2674         pclose(pipe);
2676         if (!ref || !*ref)
2677                 return TRUE;
2679         /* This is the only fatal call, since it can "corrupt" the buffer. */
2680         if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
2681                 return FALSE;
2683         return TRUE;
2686 static void
2687 add_pager_refs(struct view *view, struct line *line)
2689         char buf[SIZEOF_STR];
2690         char *commit_id = line->data + STRING_SIZE("commit ");
2691         struct ref **refs;
2692         size_t bufpos = 0, refpos = 0;
2693         const char *sep = "Refs: ";
2694         bool is_tag = FALSE;
2696         assert(line->type == LINE_COMMIT);
2698         refs = get_refs(commit_id);
2699         if (!refs) {
2700                 if (view == VIEW(REQ_VIEW_DIFF))
2701                         goto try_add_describe_ref;
2702                 return;
2703         }
2705         do {
2706                 struct ref *ref = refs[refpos];
2707                 char *fmt = ref->tag    ? "%s[%s]" :
2708                             ref->remote ? "%s<%s>" : "%s%s";
2710                 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
2711                         return;
2712                 sep = ", ";
2713                 if (ref->tag)
2714                         is_tag = TRUE;
2715         } while (refs[refpos++]->next);
2717         if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
2718 try_add_describe_ref:
2719                 /* Add <tag>-g<commit_id> "fake" reference. */
2720                 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
2721                         return;
2722         }
2724         if (bufpos == 0)
2725                 return;
2727         if (!realloc_lines(view, view->line_size + 1))
2728                 return;
2730         add_line_text(view, buf, LINE_PP_REFS);
2733 static bool
2734 pager_read(struct view *view, char *data)
2736         struct line *line;
2738         if (!data)
2739                 return TRUE;
2741         line = add_line_text(view, data, get_line_type(data));
2742         if (!line)
2743                 return FALSE;
2745         if (line->type == LINE_COMMIT &&
2746             (view == VIEW(REQ_VIEW_DIFF) ||
2747              view == VIEW(REQ_VIEW_LOG)))
2748                 add_pager_refs(view, line);
2750         return TRUE;
2753 static enum request
2754 pager_request(struct view *view, enum request request, struct line *line)
2756         int split = 0;
2758         if (request != REQ_ENTER)
2759                 return request;
2761         if (line->type == LINE_COMMIT &&
2762            (view == VIEW(REQ_VIEW_LOG) ||
2763             view == VIEW(REQ_VIEW_PAGER))) {
2764                 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
2765                 split = 1;
2766         }
2768         /* Always scroll the view even if it was split. That way
2769          * you can use Enter to scroll through the log view and
2770          * split open each commit diff. */
2771         scroll_view(view, REQ_SCROLL_LINE_DOWN);
2773         /* FIXME: A minor workaround. Scrolling the view will call report("")
2774          * but if we are scrolling a non-current view this won't properly
2775          * update the view title. */
2776         if (split)
2777                 update_view_title(view);
2779         return REQ_NONE;
2782 static bool
2783 pager_grep(struct view *view, struct line *line)
2785         regmatch_t pmatch;
2786         char *text = line->data;
2788         if (!*text)
2789                 return FALSE;
2791         if (regexec(view->regex, text, 1, &pmatch, 0) == REG_NOMATCH)
2792                 return FALSE;
2794         return TRUE;
2797 static void
2798 pager_select(struct view *view, struct line *line)
2800         if (line->type == LINE_COMMIT) {
2801                 char *text = line->data + STRING_SIZE("commit ");
2803                 if (view != VIEW(REQ_VIEW_PAGER))
2804                         string_copy_rev(view->ref, text);
2805                 string_copy_rev(ref_commit, text);
2806         }
2809 static struct view_ops pager_ops = {
2810         "line",
2811         NULL,
2812         pager_read,
2813         pager_draw,
2814         pager_request,
2815         pager_grep,
2816         pager_select,
2817 };
2820 /*
2821  * Help backend
2822  */
2824 static bool
2825 help_open(struct view *view)
2827         char buf[BUFSIZ];
2828         int lines = ARRAY_SIZE(req_info) + 2;
2829         int i;
2831         if (view->lines > 0)
2832                 return TRUE;
2834         for (i = 0; i < ARRAY_SIZE(req_info); i++)
2835                 if (!req_info[i].request)
2836                         lines++;
2838         lines += run_requests + 1;
2840         view->line = calloc(lines, sizeof(*view->line));
2841         if (!view->line)
2842                 return FALSE;
2844         add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
2846         for (i = 0; i < ARRAY_SIZE(req_info); i++) {
2847                 char *key;
2849                 if (req_info[i].request == REQ_NONE)
2850                         continue;
2852                 if (!req_info[i].request) {
2853                         add_line_text(view, "", LINE_DEFAULT);
2854                         add_line_text(view, req_info[i].help, LINE_DEFAULT);
2855                         continue;
2856                 }
2858                 key = get_key(req_info[i].request);
2859                 if (!*key)
2860                         key = "(no key defined)";
2862                 if (!string_format(buf, "    %-25s %s", key, req_info[i].help))
2863                         continue;
2865                 add_line_text(view, buf, LINE_DEFAULT);
2866         }
2868         if (run_requests) {
2869                 add_line_text(view, "", LINE_DEFAULT);
2870                 add_line_text(view, "External commands:", LINE_DEFAULT);
2871         }
2873         for (i = 0; i < run_requests; i++) {
2874                 struct run_request *req = get_run_request(REQ_NONE + i + 1);
2875                 char *key;
2877                 if (!req)
2878                         continue;
2880                 key = get_key_name(req->key);
2881                 if (!*key)
2882                         key = "(no key defined)";
2884                 if (!string_format(buf, "    %-10s %-14s `%s`",
2885                                    keymap_table[req->keymap].name,
2886                                    key, req->cmd))
2887                         continue;
2889                 add_line_text(view, buf, LINE_DEFAULT);
2890         }
2892         return TRUE;
2895 static struct view_ops help_ops = {
2896         "line",
2897         help_open,
2898         NULL,
2899         pager_draw,
2900         pager_request,
2901         pager_grep,
2902         pager_select,
2903 };
2906 /*
2907  * Tree backend
2908  */
2910 struct tree_stack_entry {
2911         struct tree_stack_entry *prev;  /* Entry below this in the stack */
2912         unsigned long lineno;           /* Line number to restore */
2913         char *name;                     /* Position of name in opt_path */
2914 };
2916 /* The top of the path stack. */
2917 static struct tree_stack_entry *tree_stack = NULL;
2918 unsigned long tree_lineno = 0;
2920 static void
2921 pop_tree_stack_entry(void)
2923         struct tree_stack_entry *entry = tree_stack;
2925         tree_lineno = entry->lineno;
2926         entry->name[0] = 0;
2927         tree_stack = entry->prev;
2928         free(entry);
2931 static void
2932 push_tree_stack_entry(char *name, unsigned long lineno)
2934         struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
2935         size_t pathlen = strlen(opt_path);
2937         if (!entry)
2938                 return;
2940         entry->prev = tree_stack;
2941         entry->name = opt_path + pathlen;
2942         tree_stack = entry;
2944         if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
2945                 pop_tree_stack_entry();
2946                 return;
2947         }
2949         /* Move the current line to the first tree entry. */
2950         tree_lineno = 1;
2951         entry->lineno = lineno;
2954 /* Parse output from git-ls-tree(1):
2955  *
2956  * 100644 blob fb0e31ea6cc679b7379631188190e975f5789c26 Makefile
2957  * 100644 blob 5304ca4260aaddaee6498f9630e7d471b8591ea6 README
2958  * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
2959  * 100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38 web.conf
2960  */
2962 #define SIZEOF_TREE_ATTR \
2963         STRING_SIZE("100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38\t")
2965 #define TREE_UP_FORMAT "040000 tree %s\t.."
2967 static int
2968 tree_compare_entry(enum line_type type1, char *name1,
2969                    enum line_type type2, char *name2)
2971         if (type1 != type2) {
2972                 if (type1 == LINE_TREE_DIR)
2973                         return -1;
2974                 return 1;
2975         }
2977         return strcmp(name1, name2);
2980 static bool
2981 tree_read(struct view *view, char *text)
2983         size_t textlen = text ? strlen(text) : 0;
2984         char buf[SIZEOF_STR];
2985         unsigned long pos;
2986         enum line_type type;
2987         bool first_read = view->lines == 0;
2989         if (textlen <= SIZEOF_TREE_ATTR)
2990                 return FALSE;
2992         type = text[STRING_SIZE("100644 ")] == 't'
2993              ? LINE_TREE_DIR : LINE_TREE_FILE;
2995         if (first_read) {
2996                 /* Add path info line */
2997                 if (!string_format(buf, "Directory path /%s", opt_path) ||
2998                     !realloc_lines(view, view->line_size + 1) ||
2999                     !add_line_text(view, buf, LINE_DEFAULT))
3000                         return FALSE;
3002                 /* Insert "link" to parent directory. */
3003                 if (*opt_path) {
3004                         if (!string_format(buf, TREE_UP_FORMAT, view->ref) ||
3005                             !realloc_lines(view, view->line_size + 1) ||
3006                             !add_line_text(view, buf, LINE_TREE_DIR))
3007                                 return FALSE;
3008                 }
3009         }
3011         /* Strip the path part ... */
3012         if (*opt_path) {
3013                 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
3014                 size_t striplen = strlen(opt_path);
3015                 char *path = text + SIZEOF_TREE_ATTR;
3017                 if (pathlen > striplen)
3018                         memmove(path, path + striplen,
3019                                 pathlen - striplen + 1);
3020         }
3022         /* Skip "Directory ..." and ".." line. */
3023         for (pos = 1 + !!*opt_path; pos < view->lines; pos++) {
3024                 struct line *line = &view->line[pos];
3025                 char *path1 = ((char *) line->data) + SIZEOF_TREE_ATTR;
3026                 char *path2 = text + SIZEOF_TREE_ATTR;
3027                 int cmp = tree_compare_entry(line->type, path1, type, path2);
3029                 if (cmp <= 0)
3030                         continue;
3032                 text = strdup(text);
3033                 if (!text)
3034                         return FALSE;
3036                 if (view->lines > pos)
3037                         memmove(&view->line[pos + 1], &view->line[pos],
3038                                 (view->lines - pos) * sizeof(*line));
3040                 line = &view->line[pos];
3041                 line->data = text;
3042                 line->type = type;
3043                 view->lines++;
3044                 return TRUE;
3045         }
3047         if (!add_line_text(view, text, type))
3048                 return FALSE;
3050         if (tree_lineno > view->lineno) {
3051                 view->lineno = tree_lineno;
3052                 tree_lineno = 0;
3053         }
3055         return TRUE;
3058 static enum request
3059 tree_request(struct view *view, enum request request, struct line *line)
3061         enum open_flags flags;
3063         if (request == REQ_TREE_PARENT) {
3064                 if (*opt_path) {
3065                         /* fake 'cd  ..' */
3066                         request = REQ_ENTER;
3067                         line = &view->line[1];
3068                 } else {
3069                         /* quit view if at top of tree */
3070                         return REQ_VIEW_CLOSE;
3071                 }
3072         }
3073         if (request != REQ_ENTER)
3074                 return request;
3076         /* Cleanup the stack if the tree view is at a different tree. */
3077         while (!*opt_path && tree_stack)
3078                 pop_tree_stack_entry();
3080         switch (line->type) {
3081         case LINE_TREE_DIR:
3082                 /* Depending on whether it is a subdir or parent (updir?) link
3083                  * mangle the path buffer. */
3084                 if (line == &view->line[1] && *opt_path) {
3085                         pop_tree_stack_entry();
3087                 } else {
3088                         char *data = line->data;
3089                         char *basename = data + SIZEOF_TREE_ATTR;
3091                         push_tree_stack_entry(basename, view->lineno);
3092                 }
3094                 /* Trees and subtrees share the same ID, so they are not not
3095                  * unique like blobs. */
3096                 flags = OPEN_RELOAD;
3097                 request = REQ_VIEW_TREE;
3098                 break;
3100         case LINE_TREE_FILE:
3101                 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
3102                 request = REQ_VIEW_BLOB;
3103                 break;
3105         default:
3106                 return TRUE;
3107         }
3109         open_view(view, request, flags);
3110         if (request == REQ_VIEW_TREE) {
3111                 view->lineno = tree_lineno;
3112         }
3114         return REQ_NONE;
3117 static void
3118 tree_select(struct view *view, struct line *line)
3120         char *text = line->data + STRING_SIZE("100644 blob ");
3122         if (line->type == LINE_TREE_FILE) {
3123                 string_copy_rev(ref_blob, text);
3125         } else if (line->type != LINE_TREE_DIR) {
3126                 return;
3127         }
3129         string_copy_rev(view->ref, text);
3132 static struct view_ops tree_ops = {
3133         "file",
3134         NULL,
3135         tree_read,
3136         pager_draw,
3137         tree_request,
3138         pager_grep,
3139         tree_select,
3140 };
3142 static bool
3143 blob_read(struct view *view, char *line)
3145         return add_line_text(view, line, LINE_DEFAULT) != NULL;
3148 static struct view_ops blob_ops = {
3149         "line",
3150         NULL,
3151         blob_read,
3152         pager_draw,
3153         pager_request,
3154         pager_grep,
3155         pager_select,
3156 };
3159 /*
3160  * Status backend
3161  */
3163 struct status {
3164         char status;
3165         struct {
3166                 mode_t mode;
3167                 char rev[SIZEOF_REV];
3168         } old;
3169         struct {
3170                 mode_t mode;
3171                 char rev[SIZEOF_REV];
3172         } new;
3173         char name[SIZEOF_STR];
3174 };
3176 static struct status stage_status;
3177 static enum line_type stage_line_type;
3179 /* Get fields from the diff line:
3180  * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
3181  */
3182 static inline bool
3183 status_get_diff(struct status *file, char *buf, size_t bufsize)
3185         char *old_mode = buf +  1;
3186         char *new_mode = buf +  8;
3187         char *old_rev  = buf + 15;
3188         char *new_rev  = buf + 56;
3189         char *status   = buf + 97;
3191         if (bufsize != 99 ||
3192             old_mode[-1] != ':' ||
3193             new_mode[-1] != ' ' ||
3194             old_rev[-1]  != ' ' ||
3195             new_rev[-1]  != ' ' ||
3196             status[-1]   != ' ')
3197                 return FALSE;
3199         file->status = *status;
3201         string_copy_rev(file->old.rev, old_rev);
3202         string_copy_rev(file->new.rev, new_rev);
3204         file->old.mode = strtoul(old_mode, NULL, 8);
3205         file->new.mode = strtoul(new_mode, NULL, 8);
3207         file->name[0] = 0;
3209         return TRUE;
3212 static bool
3213 status_run(struct view *view, const char cmd[], bool diff, enum line_type type)
3215         struct status *file = NULL;
3216         struct status *unmerged = NULL;
3217         char buf[SIZEOF_STR * 4];
3218         size_t bufsize = 0;
3219         FILE *pipe;
3221         pipe = popen(cmd, "r");
3222         if (!pipe)
3223                 return FALSE;
3225         add_line_data(view, NULL, type);
3227         while (!feof(pipe) && !ferror(pipe)) {
3228                 char *sep;
3229                 size_t readsize;
3231                 readsize = fread(buf + bufsize, 1, sizeof(buf) - bufsize, pipe);
3232                 if (!readsize)
3233                         break;
3234                 bufsize += readsize;
3236                 /* Process while we have NUL chars. */
3237                 while ((sep = memchr(buf, 0, bufsize))) {
3238                         size_t sepsize = sep - buf + 1;
3240                         if (!file) {
3241                                 if (!realloc_lines(view, view->line_size + 1))
3242                                         goto error_out;
3244                                 file = calloc(1, sizeof(*file));
3245                                 if (!file)
3246                                         goto error_out;
3248                                 add_line_data(view, file, type);
3249                         }
3251                         /* Parse diff info part. */
3252                         if (!diff) {
3253                                 file->status = '?';
3255                         } else if (!file->status) {
3256                                 if (!status_get_diff(file, buf, sepsize))
3257                                         goto error_out;
3259                                 bufsize -= sepsize;
3260                                 memmove(buf, sep + 1, bufsize);
3262                                 sep = memchr(buf, 0, bufsize);
3263                                 if (!sep)
3264                                         break;
3265                                 sepsize = sep - buf + 1;
3267                                 /* Collapse all 'M'odified entries that
3268                                  * follow a associated 'U'nmerged entry.
3269                                  */
3270                                 if (file->status == 'U') {
3271                                         unmerged = file;
3273                                 } else if (unmerged) {
3274                                         int collapse = !strcmp(buf, unmerged->name);
3276                                         unmerged = NULL;
3277                                         if (collapse) {
3278                                                 free(file);
3279                                                 view->lines--;
3280                                                 continue;
3281                                         }
3282                                 }
3283                         }
3285                         /* git-ls-files just delivers a NUL separated
3286                          * list of file names similar to the second half
3287                          * of the git-diff-* output. */
3288                         string_ncopy(file->name, buf, sepsize);
3289                         bufsize -= sepsize;
3290                         memmove(buf, sep + 1, bufsize);
3291                         file = NULL;
3292                 }
3293         }
3295         if (ferror(pipe)) {
3296 error_out:
3297                 pclose(pipe);
3298                 return FALSE;
3299         }
3301         if (!view->line[view->lines - 1].data)
3302                 add_line_data(view, NULL, LINE_STAT_NONE);
3304         pclose(pipe);
3305         return TRUE;
3308 /* Don't show unmerged entries in the staged section. */
3309 #define STATUS_DIFF_INDEX_CMD "git diff-index -z --diff-filter=ACDMRTXB --cached HEAD"
3310 #define STATUS_DIFF_FILES_CMD "git diff-files -z"
3311 #define STATUS_LIST_OTHER_CMD \
3312         "git ls-files -z --others --exclude-per-directory=.gitignore"
3314 #define STATUS_DIFF_INDEX_SHOW_CMD \
3315         "git diff-index --root --patch-with-stat --find-copies-harder -C --cached HEAD -- %s 2>/dev/null"
3317 #define STATUS_DIFF_FILES_SHOW_CMD \
3318         "git diff-files --root --patch-with-stat --find-copies-harder -C -- %s 2>/dev/null"
3320 /* First parse staged info using git-diff-index(1), then parse unstaged
3321  * info using git-diff-files(1), and finally untracked files using
3322  * git-ls-files(1). */
3323 static bool
3324 status_open(struct view *view)
3326         struct stat statbuf;
3327         char exclude[SIZEOF_STR];
3328         char cmd[SIZEOF_STR];
3329         unsigned long prev_lineno = view->lineno;
3330         size_t i;
3332         for (i = 0; i < view->lines; i++)
3333                 free(view->line[i].data);
3334         free(view->line);
3335         view->lines = view->line_size = view->lineno = 0;
3336         view->line = NULL;
3338         if (!realloc_lines(view, view->line_size + 6))
3339                 return FALSE;
3341         if (!string_format(exclude, "%s/info/exclude", opt_git_dir))
3342                 return FALSE;
3344         string_copy(cmd, STATUS_LIST_OTHER_CMD);
3346         if (stat(exclude, &statbuf) >= 0) {
3347                 size_t cmdsize = strlen(cmd);
3349                 if (!string_format_from(cmd, &cmdsize, " %s", "--exclude-from=") ||
3350                     sq_quote(cmd, cmdsize, exclude) >= sizeof(cmd))
3351                         return FALSE;
3352         }
3354         if (!status_run(view, STATUS_DIFF_INDEX_CMD, TRUE, LINE_STAT_STAGED) ||
3355             !status_run(view, STATUS_DIFF_FILES_CMD, TRUE, LINE_STAT_UNSTAGED) ||
3356             !status_run(view, cmd, FALSE, LINE_STAT_UNTRACKED))
3357                 return FALSE;
3359         /* If all went well restore the previous line number to stay in
3360          * the context. */
3361         if (prev_lineno < view->lines)
3362                 view->lineno = prev_lineno;
3363         else
3364                 view->lineno = view->lines - 1;
3366         return TRUE;
3369 static bool
3370 status_draw(struct view *view, struct line *line, unsigned int lineno, bool selected)
3372         struct status *status = line->data;
3374         wmove(view->win, lineno, 0);
3376         if (selected) {
3377                 wattrset(view->win, get_line_attr(LINE_CURSOR));
3378                 wchgat(view->win, -1, 0, LINE_CURSOR, NULL);
3380         } else if (!status && line->type != LINE_STAT_NONE) {
3381                 wattrset(view->win, get_line_attr(LINE_STAT_SECTION));
3382                 wchgat(view->win, -1, 0, LINE_STAT_SECTION, NULL);
3384         } else {
3385                 wattrset(view->win, get_line_attr(line->type));
3386         }
3388         if (!status) {
3389                 char *text;
3391                 switch (line->type) {
3392                 case LINE_STAT_STAGED:
3393                         text = "Changes to be committed:";
3394                         break;
3396                 case LINE_STAT_UNSTAGED:
3397                         text = "Changed but not updated:";
3398                         break;
3400                 case LINE_STAT_UNTRACKED:
3401                         text = "Untracked files:";
3402                         break;
3404                 case LINE_STAT_NONE:
3405                         text = "    (no files)";
3406                         break;
3408                 default:
3409                         return FALSE;
3410                 }
3412                 waddstr(view->win, text);
3413                 return TRUE;
3414         }
3416         waddch(view->win, status->status);
3417         if (!selected)
3418                 wattrset(view->win, A_NORMAL);
3419         wmove(view->win, lineno, 4);
3420         waddstr(view->win, status->name);
3422         return TRUE;
3425 static enum request
3426 status_enter(struct view *view, struct line *line)
3428         struct status *status = line->data;
3429         char path[SIZEOF_STR] = "";
3430         char *info;
3431         size_t cmdsize = 0;
3433         if (line->type == LINE_STAT_NONE ||
3434             (!status && line[1].type == LINE_STAT_NONE)) {
3435                 report("No file to diff");
3436                 return REQ_NONE;
3437         }
3439         if (status && sq_quote(path, 0, status->name) >= sizeof(path))
3440                 return REQ_QUIT;
3442         if (opt_cdup[0] &&
3443             line->type != LINE_STAT_UNTRACKED &&
3444             !string_format_from(opt_cmd, &cmdsize, "cd %s;", opt_cdup))
3445                 return REQ_QUIT;
3447         switch (line->type) {
3448         case LINE_STAT_STAGED:
3449                 if (!string_format_from(opt_cmd, &cmdsize,
3450                                         STATUS_DIFF_INDEX_SHOW_CMD, path))
3451                         return REQ_QUIT;
3452                 if (status)
3453                         info = "Staged changes to %s";
3454                 else
3455                         info = "Staged changes";
3456                 break;
3458         case LINE_STAT_UNSTAGED:
3459                 if (!string_format_from(opt_cmd, &cmdsize,
3460                                         STATUS_DIFF_FILES_SHOW_CMD, path))
3461                         return REQ_QUIT;
3462                 if (status)
3463                         info = "Unstaged changes to %s";
3464                 else
3465                         info = "Unstaged changes";
3466                 break;
3468         case LINE_STAT_UNTRACKED:
3469                 if (opt_pipe)
3470                         return REQ_QUIT;
3473                 if (!status) {
3474                         report("No file to show");
3475                         return REQ_NONE;
3476                 }
3478                 opt_pipe = fopen(status->name, "r");
3479                 info = "Untracked file %s";
3480                 break;
3482         default:
3483                 die("line type %d not handled in switch", line->type);
3484         }
3486         open_view(view, REQ_VIEW_STAGE, OPEN_RELOAD | OPEN_SPLIT);
3487         if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
3488                 if (status) {
3489                         stage_status = *status;
3490                 } else {
3491                         memset(&stage_status, 0, sizeof(stage_status));
3492                 }
3494                 stage_line_type = line->type;
3495                 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.name);
3496         }
3498         return REQ_NONE;
3502 static bool
3503 status_update_file(struct view *view, struct status *status, enum line_type type)
3505         char cmd[SIZEOF_STR];
3506         char buf[SIZEOF_STR];
3507         size_t cmdsize = 0;
3508         size_t bufsize = 0;
3509         size_t written = 0;
3510         FILE *pipe;
3512         if (opt_cdup[0] &&
3513             type != LINE_STAT_UNTRACKED &&
3514             !string_format_from(cmd, &cmdsize, "cd %s;", opt_cdup))
3515                 return FALSE;
3517         switch (type) {
3518         case LINE_STAT_STAGED:
3519                 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
3520                                         status->old.mode,
3521                                         status->old.rev,
3522                                         status->name, 0))
3523                         return FALSE;
3525                 string_add(cmd, cmdsize, "git update-index -z --index-info");
3526                 break;
3528         case LINE_STAT_UNSTAGED:
3529         case LINE_STAT_UNTRACKED:
3530                 if (!string_format_from(buf, &bufsize, "%s%c", status->name, 0))
3531                         return FALSE;
3533                 string_add(cmd, cmdsize, "git update-index -z --add --remove --stdin");
3534                 break;
3536         default:
3537                 die("line type %d not handled in switch", type);
3538         }
3540         pipe = popen(cmd, "w");
3541         if (!pipe)
3542                 return FALSE;
3544         while (!ferror(pipe) && written < bufsize) {
3545                 written += fwrite(buf + written, 1, bufsize - written, pipe);
3546         }
3548         pclose(pipe);
3550         if (written != bufsize)
3551                 return FALSE;
3553         return TRUE;
3556 static void
3557 status_update(struct view *view)
3559         struct line *line = &view->line[view->lineno];
3561         assert(view->lines);
3563         if (!line->data) {
3564                 while (++line < view->line + view->lines && line->data) {
3565                         if (!status_update_file(view, line->data, line->type))
3566                                 report("Failed to update file status");
3567                 }
3569                 if (!line[-1].data) {
3570                         report("Nothing to update");
3571                         return;
3572                 }
3574         } else if (!status_update_file(view, line->data, line->type)) {
3575                 report("Failed to update file status");
3576         }
3579 static enum request
3580 status_request(struct view *view, enum request request, struct line *line)
3582         struct status *status = line->data;
3584         switch (request) {
3585         case REQ_STATUS_UPDATE:
3586                 status_update(view);
3587                 break;
3589         case REQ_STATUS_MERGE:
3590                 if (!status || status->status != 'U') {
3591                         report("Merging only possible for files with unmerged status ('U').");
3592                         return REQ_NONE;
3593                 }
3594                 open_mergetool(status->name);
3595                 break;
3597         case REQ_EDIT:
3598                 if (!status)
3599                         return request;
3601                 open_editor(status->status != '?', status->name);
3602                 break;
3604         case REQ_ENTER:
3605                 /* After returning the status view has been split to
3606                  * show the stage view. No further reloading is
3607                  * necessary. */
3608                 status_enter(view, line);
3609                 return REQ_NONE;
3611         case REQ_REFRESH:
3612                 /* Simply reload the view. */
3613                 break;
3615         default:
3616                 return request;
3617         }
3619         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
3621         return REQ_NONE;
3624 static void
3625 status_select(struct view *view, struct line *line)
3627         struct status *status = line->data;
3628         char file[SIZEOF_STR] = "all files";
3629         char *text;
3630         char *key;
3632         if (status && !string_format(file, "'%s'", status->name))
3633                 return;
3635         if (!status && line[1].type == LINE_STAT_NONE)
3636                 line++;
3638         switch (line->type) {
3639         case LINE_STAT_STAGED:
3640                 text = "Press %s to unstage %s for commit";
3641                 break;
3643         case LINE_STAT_UNSTAGED:
3644                 text = "Press %s to stage %s for commit";
3645                 break;
3647         case LINE_STAT_UNTRACKED:
3648                 text = "Press %s to stage %s for addition";
3649                 break;
3651         case LINE_STAT_NONE:
3652                 text = "Nothing to update";
3653                 break;
3655         default:
3656                 die("line type %d not handled in switch", line->type);
3657         }
3659         if (status && status->status == 'U') {
3660                 text = "Press %s to resolve conflict in %s";
3661                 key = get_key(REQ_STATUS_MERGE);
3663         } else {
3664                 key = get_key(REQ_STATUS_UPDATE);
3665         }
3667         string_format(view->ref, text, key, file);
3670 static bool
3671 status_grep(struct view *view, struct line *line)
3673         struct status *status = line->data;
3674         enum { S_STATUS, S_NAME, S_END } state;
3675         char buf[2] = "?";
3676         regmatch_t pmatch;
3678         if (!status)
3679                 return FALSE;
3681         for (state = S_STATUS; state < S_END; state++) {
3682                 char *text;
3684                 switch (state) {
3685                 case S_NAME:    text = status->name;    break;
3686                 case S_STATUS:
3687                         buf[0] = status->status;
3688                         text = buf;
3689                         break;
3691                 default:
3692                         return FALSE;
3693                 }
3695                 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
3696                         return TRUE;
3697         }
3699         return FALSE;
3702 static struct view_ops status_ops = {
3703         "file",
3704         status_open,
3705         NULL,
3706         status_draw,
3707         status_request,
3708         status_grep,
3709         status_select,
3710 };
3713 static bool
3714 stage_diff_line(FILE *pipe, struct line *line)
3716         char *buf = line->data;
3717         size_t bufsize = strlen(buf);
3718         size_t written = 0;
3720         while (!ferror(pipe) && written < bufsize) {
3721                 written += fwrite(buf + written, 1, bufsize - written, pipe);
3722         }
3724         fputc('\n', pipe);
3726         return written == bufsize;
3729 static struct line *
3730 stage_diff_hdr(struct view *view, struct line *line)
3732         int diff_hdr_dir = line->type == LINE_DIFF_CHUNK ? -1 : 1;
3733         struct line *diff_hdr;
3735         if (line->type == LINE_DIFF_CHUNK)
3736                 diff_hdr = line - 1;
3737         else
3738                 diff_hdr = view->line + 1;
3740         while (diff_hdr > view->line && diff_hdr < view->line + view->lines) {
3741                 if (diff_hdr->type == LINE_DIFF_HEADER)
3742                         return diff_hdr;
3744                 diff_hdr += diff_hdr_dir;
3745         }
3747         return NULL;
3750 static bool
3751 stage_update_chunk(struct view *view, struct line *line)
3753         char cmd[SIZEOF_STR];
3754         size_t cmdsize = 0;
3755         struct line *diff_hdr, *diff_chunk, *diff_end;
3756         FILE *pipe;
3758         diff_hdr = stage_diff_hdr(view, line);
3759         if (!diff_hdr)
3760                 return FALSE;
3762         if (opt_cdup[0] &&
3763             !string_format_from(cmd, &cmdsize, "cd %s;", opt_cdup))
3764                 return FALSE;
3766         if (!string_format_from(cmd, &cmdsize,
3767                                 "git apply --cached %s - && "
3768                                 "git update-index -q --unmerged --refresh 2>/dev/null",
3769                                 stage_line_type == LINE_STAT_STAGED ? "-R" : ""))
3770                 return FALSE;
3772         pipe = popen(cmd, "w");
3773         if (!pipe)
3774                 return FALSE;
3776         diff_end = view->line + view->lines;
3777         if (line->type != LINE_DIFF_CHUNK) {
3778                 diff_chunk = diff_hdr;
3780         } else {
3781                 for (diff_chunk = line + 1; diff_chunk < diff_end; diff_chunk++)
3782                         if (diff_chunk->type == LINE_DIFF_CHUNK ||
3783                             diff_chunk->type == LINE_DIFF_HEADER)
3784                                 diff_end = diff_chunk;
3786                 diff_chunk = line;
3788                 while (diff_hdr->type != LINE_DIFF_CHUNK) {
3789                         switch (diff_hdr->type) {
3790                         case LINE_DIFF_HEADER:
3791                         case LINE_DIFF_INDEX:
3792                         case LINE_DIFF_ADD:
3793                         case LINE_DIFF_DEL:
3794                                 break;
3796                         default:
3797                                 diff_hdr++;
3798                                 continue;
3799                         }
3801                         if (!stage_diff_line(pipe, diff_hdr++)) {
3802                                 pclose(pipe);
3803                                 return FALSE;
3804                         }
3805                 }
3806         }
3808         while (diff_chunk < diff_end && stage_diff_line(pipe, diff_chunk))
3809                 diff_chunk++;
3811         pclose(pipe);
3813         if (diff_chunk != diff_end)
3814                 return FALSE;
3816         return TRUE;
3819 static void
3820 stage_update(struct view *view, struct line *line)
3822         if (stage_line_type != LINE_STAT_UNTRACKED &&
3823             (line->type == LINE_DIFF_CHUNK || !stage_status.status)) {
3824                 if (!stage_update_chunk(view, line)) {
3825                         report("Failed to apply chunk");
3826                         return;
3827                 }
3829         } else if (!status_update_file(view, &stage_status, stage_line_type)) {
3830                 report("Failed to update file");
3831                 return;
3832         }
3834         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
3836         view = VIEW(REQ_VIEW_STATUS);
3837         if (view_is_displayed(view))
3838                 status_enter(view, &view->line[view->lineno]);
3841 static enum request
3842 stage_request(struct view *view, enum request request, struct line *line)
3844         switch (request) {
3845         case REQ_STATUS_UPDATE:
3846                 stage_update(view, line);
3847                 break;
3849         case REQ_EDIT:
3850                 if (!stage_status.name[0])
3851                         return request;
3853                 open_editor(stage_status.status != '?', stage_status.name);
3854                 break;
3856         case REQ_ENTER:
3857                 pager_request(view, request, line);
3858                 break;
3860         default:
3861                 return request;
3862         }
3864         return REQ_NONE;
3867 static struct view_ops stage_ops = {
3868         "line",
3869         NULL,
3870         pager_read,
3871         pager_draw,
3872         stage_request,
3873         pager_grep,
3874         pager_select,
3875 };
3878 /*
3879  * Revision graph
3880  */
3882 struct commit {
3883         char id[SIZEOF_REV];            /* SHA1 ID. */
3884         char title[128];                /* First line of the commit message. */
3885         char author[75];                /* Author of the commit. */
3886         struct tm time;                 /* Date from the author ident. */
3887         struct ref **refs;              /* Repository references. */
3888         chtype graph[SIZEOF_REVGRAPH];  /* Ancestry chain graphics. */
3889         size_t graph_size;              /* The width of the graph array. */
3890 };
3892 /* Size of rev graph with no  "padding" columns */
3893 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
3895 struct rev_graph {
3896         struct rev_graph *prev, *next, *parents;
3897         char rev[SIZEOF_REVITEMS][SIZEOF_REV];
3898         size_t size;
3899         struct commit *commit;
3900         size_t pos;
3901         unsigned int boundary:1;
3902 };
3904 /* Parents of the commit being visualized. */
3905 static struct rev_graph graph_parents[4];
3907 /* The current stack of revisions on the graph. */
3908 static struct rev_graph graph_stacks[4] = {
3909         { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
3910         { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
3911         { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
3912         { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
3913 };
3915 static inline bool
3916 graph_parent_is_merge(struct rev_graph *graph)
3918         return graph->parents->size > 1;
3921 static inline void
3922 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
3924         struct commit *commit = graph->commit;
3926         if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
3927                 commit->graph[commit->graph_size++] = symbol;
3930 static void
3931 done_rev_graph(struct rev_graph *graph)
3933         if (graph_parent_is_merge(graph) &&
3934             graph->pos < graph->size - 1 &&
3935             graph->next->size == graph->size + graph->parents->size - 1) {
3936                 size_t i = graph->pos + graph->parents->size - 1;
3938                 graph->commit->graph_size = i * 2;
3939                 while (i < graph->next->size - 1) {
3940                         append_to_rev_graph(graph, ' ');
3941                         append_to_rev_graph(graph, '\\');
3942                         i++;
3943                 }
3944         }
3946         graph->size = graph->pos = 0;
3947         graph->commit = NULL;
3948         memset(graph->parents, 0, sizeof(*graph->parents));
3951 static void
3952 push_rev_graph(struct rev_graph *graph, char *parent)
3954         int i;
3956         /* "Collapse" duplicate parents lines.
3957          *
3958          * FIXME: This needs to also update update the drawn graph but
3959          * for now it just serves as a method for pruning graph lines. */
3960         for (i = 0; i < graph->size; i++)
3961                 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
3962                         return;
3964         if (graph->size < SIZEOF_REVITEMS) {
3965                 string_copy_rev(graph->rev[graph->size++], parent);
3966         }
3969 static chtype
3970 get_rev_graph_symbol(struct rev_graph *graph)
3972         chtype symbol;
3974         if (graph->boundary)
3975                 symbol = REVGRAPH_BOUND;
3976         else if (graph->parents->size == 0)
3977                 symbol = REVGRAPH_INIT;
3978         else if (graph_parent_is_merge(graph))
3979                 symbol = REVGRAPH_MERGE;
3980         else if (graph->pos >= graph->size)
3981                 symbol = REVGRAPH_BRANCH;
3982         else
3983                 symbol = REVGRAPH_COMMIT;
3985         return symbol;
3988 static void
3989 draw_rev_graph(struct rev_graph *graph)
3991         struct rev_filler {
3992                 chtype separator, line;
3993         };
3994         enum { DEFAULT, RSHARP, RDIAG, LDIAG };
3995         static struct rev_filler fillers[] = {
3996                 { ' ',  REVGRAPH_LINE },
3997                 { '`',  '.' },
3998                 { '\'', ' ' },
3999                 { '/',  ' ' },
4000         };
4001         chtype symbol = get_rev_graph_symbol(graph);
4002         struct rev_filler *filler;
4003         size_t i;
4005         filler = &fillers[DEFAULT];
4007         for (i = 0; i < graph->pos; i++) {
4008                 append_to_rev_graph(graph, filler->line);
4009                 if (graph_parent_is_merge(graph->prev) &&
4010                     graph->prev->pos == i)
4011                         filler = &fillers[RSHARP];
4013                 append_to_rev_graph(graph, filler->separator);
4014         }
4016         /* Place the symbol for this revision. */
4017         append_to_rev_graph(graph, symbol);
4019         if (graph->prev->size > graph->size)
4020                 filler = &fillers[RDIAG];
4021         else
4022                 filler = &fillers[DEFAULT];
4024         i++;
4026         for (; i < graph->size; i++) {
4027                 append_to_rev_graph(graph, filler->separator);
4028                 append_to_rev_graph(graph, filler->line);
4029                 if (graph_parent_is_merge(graph->prev) &&
4030                     i < graph->prev->pos + graph->parents->size)
4031                         filler = &fillers[RSHARP];
4032                 if (graph->prev->size > graph->size)
4033                         filler = &fillers[LDIAG];
4034         }
4036         if (graph->prev->size > graph->size) {
4037                 append_to_rev_graph(graph, filler->separator);
4038                 if (filler->line != ' ')
4039                         append_to_rev_graph(graph, filler->line);
4040         }
4043 /* Prepare the next rev graph */
4044 static void
4045 prepare_rev_graph(struct rev_graph *graph)
4047         size_t i;
4049         /* First, traverse all lines of revisions up to the active one. */
4050         for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
4051                 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
4052                         break;
4054                 push_rev_graph(graph->next, graph->rev[graph->pos]);
4055         }
4057         /* Interleave the new revision parent(s). */
4058         for (i = 0; !graph->boundary && i < graph->parents->size; i++)
4059                 push_rev_graph(graph->next, graph->parents->rev[i]);
4061         /* Lastly, put any remaining revisions. */
4062         for (i = graph->pos + 1; i < graph->size; i++)
4063                 push_rev_graph(graph->next, graph->rev[i]);
4066 static void
4067 update_rev_graph(struct rev_graph *graph)
4069         /* If this is the finalizing update ... */
4070         if (graph->commit)
4071                 prepare_rev_graph(graph);
4073         /* Graph visualization needs a one rev look-ahead,
4074          * so the first update doesn't visualize anything. */
4075         if (!graph->prev->commit)
4076                 return;
4078         draw_rev_graph(graph->prev);
4079         done_rev_graph(graph->prev->prev);
4083 /*
4084  * Main view backend
4085  */
4087 static bool
4088 main_draw(struct view *view, struct line *line, unsigned int lineno, bool selected)
4090         char buf[DATE_COLS + 1];
4091         struct commit *commit = line->data;
4092         enum line_type type;
4093         int col = 0;
4094         size_t timelen;
4095         size_t authorlen;
4096         int trimmed = 1;
4098         if (!*commit->author)
4099                 return FALSE;
4101         wmove(view->win, lineno, col);
4103         if (selected) {
4104                 type = LINE_CURSOR;
4105                 wattrset(view->win, get_line_attr(type));
4106                 wchgat(view->win, -1, 0, type, NULL);
4108         } else {
4109                 type = LINE_MAIN_COMMIT;
4110                 wattrset(view->win, get_line_attr(LINE_MAIN_DATE));
4111         }
4113         timelen = strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time);
4114         waddnstr(view->win, buf, timelen);
4115         waddstr(view->win, " ");
4117         col += DATE_COLS;
4118         wmove(view->win, lineno, col);
4119         if (type != LINE_CURSOR)
4120                 wattrset(view->win, get_line_attr(LINE_MAIN_AUTHOR));
4122         if (opt_utf8) {
4123                 authorlen = utf8_length(commit->author, AUTHOR_COLS - 2, &col, &trimmed);
4124         } else {
4125                 authorlen = strlen(commit->author);
4126                 if (authorlen > AUTHOR_COLS - 2) {
4127                         authorlen = AUTHOR_COLS - 2;
4128                         trimmed = 1;
4129                 }
4130         }
4132         if (trimmed) {
4133                 waddnstr(view->win, commit->author, authorlen);
4134                 if (type != LINE_CURSOR)
4135                         wattrset(view->win, get_line_attr(LINE_MAIN_DELIM));
4136                 waddch(view->win, '~');
4137         } else {
4138                 waddstr(view->win, commit->author);
4139         }
4141         col += AUTHOR_COLS;
4143         if (opt_rev_graph && commit->graph_size) {
4144                 size_t i;
4146                 if (type != LINE_CURSOR)
4147                         wattrset(view->win, get_line_attr(LINE_MAIN_REVGRAPH));
4148                 wmove(view->win, lineno, col);
4149                 /* Using waddch() instead of waddnstr() ensures that
4150                  * they'll be rendered correctly for the cursor line. */
4151                 for (i = 0; i < commit->graph_size; i++)
4152                         waddch(view->win, commit->graph[i]);
4154                 waddch(view->win, ' ');
4155                 col += commit->graph_size + 1;
4156         }
4157         if (type != LINE_CURSOR)
4158                 wattrset(view->win, A_NORMAL);
4160         wmove(view->win, lineno, col);
4162         if (commit->refs) {
4163                 size_t i = 0;
4165                 do {
4166                         if (type == LINE_CURSOR)
4167                                 ;
4168                         else if (commit->refs[i]->tag)
4169                                 wattrset(view->win, get_line_attr(LINE_MAIN_TAG));
4170                         else if (commit->refs[i]->remote)
4171                                 wattrset(view->win, get_line_attr(LINE_MAIN_REMOTE));
4172                         else
4173                                 wattrset(view->win, get_line_attr(LINE_MAIN_REF));
4174                         waddstr(view->win, "[");
4175                         waddstr(view->win, commit->refs[i]->name);
4176                         waddstr(view->win, "]");
4177                         if (type != LINE_CURSOR)
4178                                 wattrset(view->win, A_NORMAL);
4179                         waddstr(view->win, " ");
4180                         col += strlen(commit->refs[i]->name) + STRING_SIZE("[] ");
4181                 } while (commit->refs[i++]->next);
4182         }
4184         if (type != LINE_CURSOR)
4185                 wattrset(view->win, get_line_attr(type));
4187         {
4188                 int titlelen = strlen(commit->title);
4190                 if (col + titlelen > view->width)
4191                         titlelen = view->width - col;
4193                 waddnstr(view->win, commit->title, titlelen);
4194         }
4196         return TRUE;
4199 /* Reads git log --pretty=raw output and parses it into the commit struct. */
4200 static bool
4201 main_read(struct view *view, char *line)
4203         static struct rev_graph *graph = graph_stacks;
4204         enum line_type type;
4205         struct commit *commit;
4207         if (!line) {
4208                 update_rev_graph(graph);
4209                 return TRUE;
4210         }
4212         type = get_line_type(line);
4213         if (type == LINE_COMMIT) {
4214                 commit = calloc(1, sizeof(struct commit));
4215                 if (!commit)
4216                         return FALSE;
4218                 line += STRING_SIZE("commit ");
4219                 if (*line == '-') {
4220                         graph->boundary = 1;
4221                         line++;
4222                 }
4224                 string_copy_rev(commit->id, line);
4225                 commit->refs = get_refs(commit->id);
4226                 graph->commit = commit;
4227                 add_line_data(view, commit, LINE_MAIN_COMMIT);
4228                 return TRUE;
4229         }
4231         if (!view->lines)
4232                 return TRUE;
4233         commit = view->line[view->lines - 1].data;
4235         switch (type) {
4236         case LINE_PARENT:
4237                 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
4238                 break;
4240         case LINE_AUTHOR:
4241         {
4242                 /* Parse author lines where the name may be empty:
4243                  *      author  <email@address.tld> 1138474660 +0100
4244                  */
4245                 char *ident = line + STRING_SIZE("author ");
4246                 char *nameend = strchr(ident, '<');
4247                 char *emailend = strchr(ident, '>');
4249                 if (!nameend || !emailend)
4250                         break;
4252                 update_rev_graph(graph);
4253                 graph = graph->next;
4255                 *nameend = *emailend = 0;
4256                 ident = chomp_string(ident);
4257                 if (!*ident) {
4258                         ident = chomp_string(nameend + 1);
4259                         if (!*ident)
4260                                 ident = "Unknown";
4261                 }
4263                 string_ncopy(commit->author, ident, strlen(ident));
4265                 /* Parse epoch and timezone */
4266                 if (emailend[1] == ' ') {
4267                         char *secs = emailend + 2;
4268                         char *zone = strchr(secs, ' ');
4269                         time_t time = (time_t) atol(secs);
4271                         if (zone && strlen(zone) == STRING_SIZE(" +0700")) {
4272                                 long tz;
4274                                 zone++;
4275                                 tz  = ('0' - zone[1]) * 60 * 60 * 10;
4276                                 tz += ('0' - zone[2]) * 60 * 60;
4277                                 tz += ('0' - zone[3]) * 60;
4278                                 tz += ('0' - zone[4]) * 60;
4280                                 if (zone[0] == '-')
4281                                         tz = -tz;
4283                                 time -= tz;
4284                         }
4286                         gmtime_r(&time, &commit->time);
4287                 }
4288                 break;
4289         }
4290         default:
4291                 /* Fill in the commit title if it has not already been set. */
4292                 if (commit->title[0])
4293                         break;
4295                 /* Require titles to start with a non-space character at the
4296                  * offset used by git log. */
4297                 if (strncmp(line, "    ", 4))
4298                         break;
4299                 line += 4;
4300                 /* Well, if the title starts with a whitespace character,
4301                  * try to be forgiving.  Otherwise we end up with no title. */
4302                 while (isspace(*line))
4303                         line++;
4304                 if (*line == '\0')
4305                         break;
4306                 /* FIXME: More graceful handling of titles; append "..." to
4307                  * shortened titles, etc. */
4309                 string_ncopy(commit->title, line, strlen(line));
4310         }
4312         return TRUE;
4315 static enum request
4316 main_request(struct view *view, enum request request, struct line *line)
4318         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4320         if (request == REQ_ENTER)
4321                 open_view(view, REQ_VIEW_DIFF, flags);
4322         else
4323                 return request;
4325         return REQ_NONE;
4328 static bool
4329 main_grep(struct view *view, struct line *line)
4331         struct commit *commit = line->data;
4332         enum { S_TITLE, S_AUTHOR, S_DATE, S_END } state;
4333         char buf[DATE_COLS + 1];
4334         regmatch_t pmatch;
4336         for (state = S_TITLE; state < S_END; state++) {
4337                 char *text;
4339                 switch (state) {
4340                 case S_TITLE:   text = commit->title;   break;
4341                 case S_AUTHOR:  text = commit->author;  break;
4342                 case S_DATE:
4343                         if (!strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time))
4344                                 continue;
4345                         text = buf;
4346                         break;
4348                 default:
4349                         return FALSE;
4350                 }
4352                 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
4353                         return TRUE;
4354         }
4356         return FALSE;
4359 static void
4360 main_select(struct view *view, struct line *line)
4362         struct commit *commit = line->data;
4364         string_copy_rev(view->ref, commit->id);
4365         string_copy_rev(ref_commit, view->ref);
4368 static struct view_ops main_ops = {
4369         "commit",
4370         NULL,
4371         main_read,
4372         main_draw,
4373         main_request,
4374         main_grep,
4375         main_select,
4376 };
4379 /*
4380  * Unicode / UTF-8 handling
4381  *
4382  * NOTE: Much of the following code for dealing with unicode is derived from
4383  * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
4384  * src/intl/charset.c from the utf8 branch commit elinks-0.11.0-g31f2c28.
4385  */
4387 /* I've (over)annotated a lot of code snippets because I am not entirely
4388  * confident that the approach taken by this small UTF-8 interface is correct.
4389  * --jonas */
4391 static inline int
4392 unicode_width(unsigned long c)
4394         if (c >= 0x1100 &&
4395            (c <= 0x115f                         /* Hangul Jamo */
4396             || c == 0x2329
4397             || c == 0x232a
4398             || (c >= 0x2e80  && c <= 0xa4cf && c != 0x303f)
4399                                                 /* CJK ... Yi */
4400             || (c >= 0xac00  && c <= 0xd7a3)    /* Hangul Syllables */
4401             || (c >= 0xf900  && c <= 0xfaff)    /* CJK Compatibility Ideographs */
4402             || (c >= 0xfe30  && c <= 0xfe6f)    /* CJK Compatibility Forms */
4403             || (c >= 0xff00  && c <= 0xff60)    /* Fullwidth Forms */
4404             || (c >= 0xffe0  && c <= 0xffe6)
4405             || (c >= 0x20000 && c <= 0x2fffd)
4406             || (c >= 0x30000 && c <= 0x3fffd)))
4407                 return 2;
4409         return 1;
4412 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
4413  * Illegal bytes are set one. */
4414 static const unsigned char utf8_bytes[256] = {
4415         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,
4416         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,
4417         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,
4418         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,
4419         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,
4420         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,
4421         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,
4422         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,
4423 };
4425 /* Decode UTF-8 multi-byte representation into a unicode character. */
4426 static inline unsigned long
4427 utf8_to_unicode(const char *string, size_t length)
4429         unsigned long unicode;
4431         switch (length) {
4432         case 1:
4433                 unicode  =   string[0];
4434                 break;
4435         case 2:
4436                 unicode  =  (string[0] & 0x1f) << 6;
4437                 unicode +=  (string[1] & 0x3f);
4438                 break;
4439         case 3:
4440                 unicode  =  (string[0] & 0x0f) << 12;
4441                 unicode += ((string[1] & 0x3f) << 6);
4442                 unicode +=  (string[2] & 0x3f);
4443                 break;
4444         case 4:
4445                 unicode  =  (string[0] & 0x0f) << 18;
4446                 unicode += ((string[1] & 0x3f) << 12);
4447                 unicode += ((string[2] & 0x3f) << 6);
4448                 unicode +=  (string[3] & 0x3f);
4449                 break;
4450         case 5:
4451                 unicode  =  (string[0] & 0x0f) << 24;
4452                 unicode += ((string[1] & 0x3f) << 18);
4453                 unicode += ((string[2] & 0x3f) << 12);
4454                 unicode += ((string[3] & 0x3f) << 6);
4455                 unicode +=  (string[4] & 0x3f);
4456                 break;
4457         case 6:
4458                 unicode  =  (string[0] & 0x01) << 30;
4459                 unicode += ((string[1] & 0x3f) << 24);
4460                 unicode += ((string[2] & 0x3f) << 18);
4461                 unicode += ((string[3] & 0x3f) << 12);
4462                 unicode += ((string[4] & 0x3f) << 6);
4463                 unicode +=  (string[5] & 0x3f);
4464                 break;
4465         default:
4466                 die("Invalid unicode length");
4467         }
4469         /* Invalid characters could return the special 0xfffd value but NUL
4470          * should be just as good. */
4471         return unicode > 0xffff ? 0 : unicode;
4474 /* Calculates how much of string can be shown within the given maximum width
4475  * and sets trimmed parameter to non-zero value if all of string could not be
4476  * shown.
4477  *
4478  * Additionally, adds to coloffset how many many columns to move to align with
4479  * the expected position. Takes into account how multi-byte and double-width
4480  * characters will effect the cursor position.
4481  *
4482  * Returns the number of bytes to output from string to satisfy max_width. */
4483 static size_t
4484 utf8_length(const char *string, size_t max_width, int *coloffset, int *trimmed)
4486         const char *start = string;
4487         const char *end = strchr(string, '\0');
4488         size_t mbwidth = 0;
4489         size_t width = 0;
4491         *trimmed = 0;
4493         while (string < end) {
4494                 int c = *(unsigned char *) string;
4495                 unsigned char bytes = utf8_bytes[c];
4496                 size_t ucwidth;
4497                 unsigned long unicode;
4499                 if (string + bytes > end)
4500                         break;
4502                 /* Change representation to figure out whether
4503                  * it is a single- or double-width character. */
4505                 unicode = utf8_to_unicode(string, bytes);
4506                 /* FIXME: Graceful handling of invalid unicode character. */
4507                 if (!unicode)
4508                         break;
4510                 ucwidth = unicode_width(unicode);
4511                 width  += ucwidth;
4512                 if (width > max_width) {
4513                         *trimmed = 1;
4514                         break;
4515                 }
4517                 /* The column offset collects the differences between the
4518                  * number of bytes encoding a character and the number of
4519                  * columns will be used for rendering said character.
4520                  *
4521                  * So if some character A is encoded in 2 bytes, but will be
4522                  * represented on the screen using only 1 byte this will and up
4523                  * adding 1 to the multi-byte column offset.
4524                  *
4525                  * Assumes that no double-width character can be encoding in
4526                  * less than two bytes. */
4527                 if (bytes > ucwidth)
4528                         mbwidth += bytes - ucwidth;
4530                 string  += bytes;
4531         }
4533         *coloffset += mbwidth;
4535         return string - start;
4539 /*
4540  * Status management
4541  */
4543 /* Whether or not the curses interface has been initialized. */
4544 static bool cursed = FALSE;
4546 /* The status window is used for polling keystrokes. */
4547 static WINDOW *status_win;
4549 static bool status_empty = TRUE;
4551 /* Update status and title window. */
4552 static void
4553 report(const char *msg, ...)
4555         struct view *view = display[current_view];
4557         if (input_mode)
4558                 return;
4560         if (!view) {
4561                 char buf[SIZEOF_STR];
4562                 va_list args;
4564                 va_start(args, msg);
4565                 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
4566                         buf[sizeof(buf) - 1] = 0;
4567                         buf[sizeof(buf) - 2] = '.';
4568                         buf[sizeof(buf) - 3] = '.';
4569                         buf[sizeof(buf) - 4] = '.';
4570                 }
4571                 va_end(args);
4572                 die("%s", buf);
4573         }
4575         if (!status_empty || *msg) {
4576                 va_list args;
4578                 va_start(args, msg);
4580                 wmove(status_win, 0, 0);
4581                 if (*msg) {
4582                         vwprintw(status_win, msg, args);
4583                         status_empty = FALSE;
4584                 } else {
4585                         status_empty = TRUE;
4586                 }
4587                 wclrtoeol(status_win);
4588                 wrefresh(status_win);
4590                 va_end(args);
4591         }
4593         update_view_title(view);
4594         update_display_cursor(view);
4597 /* Controls when nodelay should be in effect when polling user input. */
4598 static void
4599 set_nonblocking_input(bool loading)
4601         static unsigned int loading_views;
4603         if ((loading == FALSE && loading_views-- == 1) ||
4604             (loading == TRUE  && loading_views++ == 0))
4605                 nodelay(status_win, loading);
4608 static void
4609 init_display(void)
4611         int x, y;
4613         /* Initialize the curses library */
4614         if (isatty(STDIN_FILENO)) {
4615                 cursed = !!initscr();
4616         } else {
4617                 /* Leave stdin and stdout alone when acting as a pager. */
4618                 FILE *io = fopen("/dev/tty", "r+");
4620                 if (!io)
4621                         die("Failed to open /dev/tty");
4622                 cursed = !!newterm(NULL, io, io);
4623         }
4625         if (!cursed)
4626                 die("Failed to initialize curses");
4628         nonl();         /* Tell curses not to do NL->CR/NL on output */
4629         cbreak();       /* Take input chars one at a time, no wait for \n */
4630         noecho();       /* Don't echo input */
4631         leaveok(stdscr, TRUE);
4633         if (has_colors())
4634                 init_colors();
4636         getmaxyx(stdscr, y, x);
4637         status_win = newwin(1, 0, y - 1, 0);
4638         if (!status_win)
4639                 die("Failed to create status window");
4641         /* Enable keyboard mapping */
4642         keypad(status_win, TRUE);
4643         wbkgdset(status_win, get_line_attr(LINE_STATUS));
4646 static char *
4647 read_prompt(const char *prompt)
4649         enum { READING, STOP, CANCEL } status = READING;
4650         static char buf[sizeof(opt_cmd) - STRING_SIZE("git \0")];
4651         int pos = 0;
4653         while (status == READING) {
4654                 struct view *view;
4655                 int i, key;
4657                 input_mode = TRUE;
4659                 foreach_view (view, i)
4660                         update_view(view);
4662                 input_mode = FALSE;
4664                 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
4665                 wclrtoeol(status_win);
4667                 /* Refresh, accept single keystroke of input */
4668                 key = wgetch(status_win);
4669                 switch (key) {
4670                 case KEY_RETURN:
4671                 case KEY_ENTER:
4672                 case '\n':
4673                         status = pos ? STOP : CANCEL;
4674                         break;
4676                 case KEY_BACKSPACE:
4677                         if (pos > 0)
4678                                 pos--;
4679                         else
4680                                 status = CANCEL;
4681                         break;
4683                 case KEY_ESC:
4684                         status = CANCEL;
4685                         break;
4687                 case ERR:
4688                         break;
4690                 default:
4691                         if (pos >= sizeof(buf)) {
4692                                 report("Input string too long");
4693                                 return NULL;
4694                         }
4696                         if (isprint(key))
4697                                 buf[pos++] = (char) key;
4698                 }
4699         }
4701         /* Clear the status window */
4702         status_empty = FALSE;
4703         report("");
4705         if (status == CANCEL)
4706                 return NULL;
4708         buf[pos++] = 0;
4710         return buf;
4713 /*
4714  * Repository references
4715  */
4717 static struct ref *refs;
4718 static size_t refs_size;
4720 /* Id <-> ref store */
4721 static struct ref ***id_refs;
4722 static size_t id_refs_size;
4724 static struct ref **
4725 get_refs(char *id)
4727         struct ref ***tmp_id_refs;
4728         struct ref **ref_list = NULL;
4729         size_t ref_list_size = 0;
4730         size_t i;
4732         for (i = 0; i < id_refs_size; i++)
4733                 if (!strcmp(id, id_refs[i][0]->id))
4734                         return id_refs[i];
4736         tmp_id_refs = realloc(id_refs, (id_refs_size + 1) * sizeof(*id_refs));
4737         if (!tmp_id_refs)
4738                 return NULL;
4740         id_refs = tmp_id_refs;
4742         for (i = 0; i < refs_size; i++) {
4743                 struct ref **tmp;
4745                 if (strcmp(id, refs[i].id))
4746                         continue;
4748                 tmp = realloc(ref_list, (ref_list_size + 1) * sizeof(*ref_list));
4749                 if (!tmp) {
4750                         if (ref_list)
4751                                 free(ref_list);
4752                         return NULL;
4753                 }
4755                 ref_list = tmp;
4756                 if (ref_list_size > 0)
4757                         ref_list[ref_list_size - 1]->next = 1;
4758                 ref_list[ref_list_size] = &refs[i];
4760                 /* XXX: The properties of the commit chains ensures that we can
4761                  * safely modify the shared ref. The repo references will
4762                  * always be similar for the same id. */
4763                 ref_list[ref_list_size]->next = 0;
4764                 ref_list_size++;
4765         }
4767         if (ref_list)
4768                 id_refs[id_refs_size++] = ref_list;
4770         return ref_list;
4773 static int
4774 read_ref(char *id, size_t idlen, char *name, size_t namelen)
4776         struct ref *ref;
4777         bool tag = FALSE;
4778         bool remote = FALSE;
4780         if (!strncmp(name, "refs/tags/", STRING_SIZE("refs/tags/"))) {
4781                 /* Commits referenced by tags has "^{}" appended. */
4782                 if (name[namelen - 1] != '}')
4783                         return OK;
4785                 while (namelen > 0 && name[namelen] != '^')
4786                         namelen--;
4788                 tag = TRUE;
4789                 namelen -= STRING_SIZE("refs/tags/");
4790                 name    += STRING_SIZE("refs/tags/");
4792         } else if (!strncmp(name, "refs/remotes/", STRING_SIZE("refs/remotes/"))) {
4793                 remote = TRUE;
4794                 namelen -= STRING_SIZE("refs/remotes/");
4795                 name    += STRING_SIZE("refs/remotes/");
4797         } else if (!strncmp(name, "refs/heads/", STRING_SIZE("refs/heads/"))) {
4798                 namelen -= STRING_SIZE("refs/heads/");
4799                 name    += STRING_SIZE("refs/heads/");
4801         } else if (!strcmp(name, "HEAD")) {
4802                 return OK;
4803         }
4805         refs = realloc(refs, sizeof(*refs) * (refs_size + 1));
4806         if (!refs)
4807                 return ERR;
4809         ref = &refs[refs_size++];
4810         ref->name = malloc(namelen + 1);
4811         if (!ref->name)
4812                 return ERR;
4814         strncpy(ref->name, name, namelen);
4815         ref->name[namelen] = 0;
4816         ref->tag = tag;
4817         ref->remote = remote;
4818         string_copy_rev(ref->id, id);
4820         return OK;
4823 static int
4824 load_refs(void)
4826         const char *cmd_env = getenv("TIG_LS_REMOTE");
4827         const char *cmd = cmd_env && *cmd_env ? cmd_env : TIG_LS_REMOTE;
4829         return read_properties(popen(cmd, "r"), "\t", read_ref);
4832 static int
4833 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
4835         if (!strcmp(name, "i18n.commitencoding"))
4836                 string_ncopy(opt_encoding, value, valuelen);
4838         if (!strcmp(name, "core.editor"))
4839                 string_ncopy(opt_editor, value, valuelen);
4841         return OK;
4844 static int
4845 load_repo_config(void)
4847         return read_properties(popen(GIT_CONFIG " --list", "r"),
4848                                "=", read_repo_config_option);
4851 static int
4852 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
4854         if (!opt_git_dir[0]) {
4855                 string_ncopy(opt_git_dir, name, namelen);
4857         } else if (opt_is_inside_work_tree == -1) {
4858                 /* This can be 3 different values depending on the
4859                  * version of git being used. If git-rev-parse does not
4860                  * understand --is-inside-work-tree it will simply echo
4861                  * the option else either "true" or "false" is printed.
4862                  * Default to true for the unknown case. */
4863                 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
4865         } else {
4866                 string_ncopy(opt_cdup, name, namelen);
4867         }
4869         return OK;
4872 /* XXX: The line outputted by "--show-cdup" can be empty so the option
4873  * must be the last one! */
4874 static int
4875 load_repo_info(void)
4877         return read_properties(popen("git rev-parse --git-dir --is-inside-work-tree --show-cdup 2>/dev/null", "r"),
4878                                "=", read_repo_info);
4881 static int
4882 read_properties(FILE *pipe, const char *separators,
4883                 int (*read_property)(char *, size_t, char *, size_t))
4885         char buffer[BUFSIZ];
4886         char *name;
4887         int state = OK;
4889         if (!pipe)
4890                 return ERR;
4892         while (state == OK && (name = fgets(buffer, sizeof(buffer), pipe))) {
4893                 char *value;
4894                 size_t namelen;
4895                 size_t valuelen;
4897                 name = chomp_string(name);
4898                 namelen = strcspn(name, separators);
4900                 if (name[namelen]) {
4901                         name[namelen] = 0;
4902                         value = chomp_string(name + namelen + 1);
4903                         valuelen = strlen(value);
4905                 } else {
4906                         value = "";
4907                         valuelen = 0;
4908                 }
4910                 state = read_property(name, namelen, value, valuelen);
4911         }
4913         if (state != ERR && ferror(pipe))
4914                 state = ERR;
4916         pclose(pipe);
4918         return state;
4922 /*
4923  * Main
4924  */
4926 static void __NORETURN
4927 quit(int sig)
4929         /* XXX: Restore tty modes and let the OS cleanup the rest! */
4930         if (cursed)
4931                 endwin();
4932         exit(0);
4935 static void __NORETURN
4936 die(const char *err, ...)
4938         va_list args;
4940         endwin();
4942         va_start(args, err);
4943         fputs("tig: ", stderr);
4944         vfprintf(stderr, err, args);
4945         fputs("\n", stderr);
4946         va_end(args);
4948         exit(1);
4951 static void
4952 warn(const char *msg, ...)
4954         va_list args;
4956         va_start(args, msg);
4957         fputs("tig warning: ", stderr);
4958         vfprintf(stderr, msg, args);
4959         fputs("\n", stderr);
4960         va_end(args);
4963 int
4964 main(int argc, char *argv[])
4966         struct view *view;
4967         enum request request;
4968         size_t i;
4970         signal(SIGINT, quit);
4972         if (setlocale(LC_ALL, "")) {
4973                 char *codeset = nl_langinfo(CODESET);
4975                 string_ncopy(opt_codeset, codeset, strlen(codeset));
4976         }
4978         if (load_repo_info() == ERR)
4979                 die("Failed to load repo info.");
4981         if (load_options() == ERR)
4982                 die("Failed to load user config.");
4984         /* Load the repo config file so options can be overwritten from
4985          * the command line. */
4986         if (load_repo_config() == ERR)
4987                 die("Failed to load repo config.");
4989         if (!parse_options(argc, argv))
4990                 return 0;
4992         /* Require a git repository unless when running in pager mode. */
4993         if (!opt_git_dir[0])
4994                 die("Not a git repository");
4996         if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
4997                 opt_iconv = iconv_open(opt_codeset, opt_encoding);
4998                 if (opt_iconv == ICONV_NONE)
4999                         die("Failed to initialize character set conversion");
5000         }
5002         if (load_refs() == ERR)
5003                 die("Failed to load refs.");
5005         for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
5006                 view->cmd_env = getenv(view->cmd_env);
5008         request = opt_request;
5010         init_display();
5012         while (view_driver(display[current_view], request)) {
5013                 int key;
5014                 int i;
5016                 foreach_view (view, i)
5017                         update_view(view);
5019                 /* Refresh, accept single keystroke of input */
5020                 key = wgetch(status_win);
5022                 /* wgetch() with nodelay() enabled returns ERR when there's no
5023                  * input. */
5024                 if (key == ERR) {
5025                         request = REQ_NONE;
5026                         continue;
5027                 }
5029                 request = get_keybinding(display[current_view]->keymap, key);
5031                 /* Some low-level request handling. This keeps access to
5032                  * status_win restricted. */
5033                 switch (request) {
5034                 case REQ_PROMPT:
5035                 {
5036                         char *cmd = read_prompt(":");
5038                         if (cmd && string_format(opt_cmd, "git %s", cmd)) {
5039                                 if (strncmp(cmd, "show", 4) && isspace(cmd[4])) {
5040                                         opt_request = REQ_VIEW_DIFF;
5041                                 } else {
5042                                         opt_request = REQ_VIEW_PAGER;
5043                                 }
5044                                 break;
5045                         }
5047                         request = REQ_NONE;
5048                         break;
5049                 }
5050                 case REQ_SEARCH:
5051                 case REQ_SEARCH_BACK:
5052                 {
5053                         const char *prompt = request == REQ_SEARCH
5054                                            ? "/" : "?";
5055                         char *search = read_prompt(prompt);
5057                         if (search)
5058                                 string_ncopy(opt_search, search, strlen(search));
5059                         else
5060                                 request = REQ_NONE;
5061                         break;
5062                 }
5063                 case REQ_SCREEN_RESIZE:
5064                 {
5065                         int height, width;
5067                         getmaxyx(stdscr, height, width);
5069                         /* Resize the status view and let the view driver take
5070                          * care of resizing the displayed views. */
5071                         wresize(status_win, 1, width);
5072                         mvwin(status_win, height - 1, 0);
5073                         wrefresh(status_win);
5074                         break;
5075                 }
5076                 default:
5077                         break;
5078                 }
5079         }
5081         quit(0);
5083         return 0;