Code

Move application of string_expand to draw_text
[tig.git] / tig.c
1 /* Copyright (c) 2006-2010 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/wait.h>
36 #include <sys/stat.h>
37 #include <sys/select.h>
38 #include <unistd.h>
39 #include <sys/time.h>
40 #include <time.h>
41 #include <fcntl.h>
43 #include <regex.h>
45 #include <locale.h>
46 #include <langinfo.h>
47 #include <iconv.h>
49 /* ncurses(3): Must be defined to have extended wide-character functions. */
50 #define _XOPEN_SOURCE_EXTENDED
52 #ifdef HAVE_NCURSESW_NCURSES_H
53 #include <ncursesw/ncurses.h>
54 #else
55 #ifdef HAVE_NCURSES_NCURSES_H
56 #include <ncurses/ncurses.h>
57 #else
58 #include <ncurses.h>
59 #endif
60 #endif
62 #if __GNUC__ >= 3
63 #define __NORETURN __attribute__((__noreturn__))
64 #else
65 #define __NORETURN
66 #endif
68 static void __NORETURN die(const char *err, ...);
69 static void warn(const char *msg, ...);
70 static void report(const char *msg, ...);
72 #define ABS(x)          ((x) >= 0  ? (x) : -(x))
73 #define MIN(x, y)       ((x) < (y) ? (x) :  (y))
74 #define MAX(x, y)       ((x) > (y) ? (x) :  (y))
76 #define ARRAY_SIZE(x)   (sizeof(x) / sizeof(x[0]))
77 #define STRING_SIZE(x)  (sizeof(x) - 1)
79 #define SIZEOF_STR      1024    /* Default string size. */
80 #define SIZEOF_REF      256     /* Size of symbolic or SHA1 ID. */
81 #define SIZEOF_REV      41      /* Holds a SHA-1 and an ending NUL. */
82 #define SIZEOF_ARG      32      /* Default argument array size. */
84 /* Revision graph */
86 #define REVGRAPH_INIT   'I'
87 #define REVGRAPH_MERGE  'M'
88 #define REVGRAPH_BRANCH '+'
89 #define REVGRAPH_COMMIT '*'
90 #define REVGRAPH_BOUND  '^'
92 #define SIZEOF_REVGRAPH 19      /* Size of revision ancestry graphics. */
94 /* This color name can be used to refer to the default term colors. */
95 #define COLOR_DEFAULT   (-1)
97 #define ICONV_NONE      ((iconv_t) -1)
98 #ifndef ICONV_CONST
99 #define ICONV_CONST     /* nothing */
100 #endif
102 /* The format and size of the date column in the main view. */
103 #define DATE_FORMAT     "%Y-%m-%d %H:%M"
104 #define DATE_COLS       STRING_SIZE("2006-04-29 14:21 ")
105 #define DATE_SHORT_COLS STRING_SIZE("2006-04-29 ")
107 #define ID_COLS         8
108 #define AUTHOR_COLS     19
110 #define MIN_VIEW_HEIGHT 4
112 #define NULL_ID         "0000000000000000000000000000000000000000"
114 #define S_ISGITLINK(mode) (((mode) & S_IFMT) == 0160000)
116 /* Some ASCII-shorthands fitted into the ncurses namespace. */
117 #define KEY_TAB         '\t'
118 #define KEY_RETURN      '\r'
119 #define KEY_ESC         27
122 struct ref {
123         char id[SIZEOF_REV];    /* Commit SHA1 ID */
124         unsigned int head:1;    /* Is it the current HEAD? */
125         unsigned int tag:1;     /* Is it a tag? */
126         unsigned int ltag:1;    /* If so, is the tag local? */
127         unsigned int remote:1;  /* Is it a remote ref? */
128         unsigned int tracked:1; /* Is it the remote for the current HEAD? */
129         char name[1];           /* Ref name; tag or head names are shortened. */
130 };
132 struct ref_list {
133         char id[SIZEOF_REV];    /* Commit SHA1 ID */
134         size_t size;            /* Number of refs. */
135         struct ref **refs;      /* References for this ID. */
136 };
138 static struct ref *get_ref_head();
139 static struct ref_list *get_ref_list(const char *id);
140 static void foreach_ref(bool (*visitor)(void *data, const struct ref *ref), void *data);
141 static int load_refs(void);
143 enum input_status {
144         INPUT_OK,
145         INPUT_SKIP,
146         INPUT_STOP,
147         INPUT_CANCEL
148 };
150 typedef enum input_status (*input_handler)(void *data, char *buf, int c);
152 static char *prompt_input(const char *prompt, input_handler handler, void *data);
153 static bool prompt_yesno(const char *prompt);
155 struct menu_item {
156         int hotkey;
157         const char *text;
158         void *data;
159 };
161 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected);
163 /*
164  * Allocation helpers ... Entering macro hell to never be seen again.
165  */
167 #define DEFINE_ALLOCATOR(name, type, chunk_size)                                \
168 static type *                                                                   \
169 name(type **mem, size_t size, size_t increase)                                  \
170 {                                                                               \
171         size_t num_chunks = (size + chunk_size - 1) / chunk_size;               \
172         size_t num_chunks_new = (size + increase + chunk_size - 1) / chunk_size;\
173         type *tmp = *mem;                                                       \
174                                                                                 \
175         if (mem == NULL || num_chunks != num_chunks_new) {                      \
176                 tmp = realloc(tmp, num_chunks_new * chunk_size * sizeof(type)); \
177                 if (tmp)                                                        \
178                         *mem = tmp;                                             \
179         }                                                                       \
180                                                                                 \
181         return tmp;                                                             \
184 /*
185  * String helpers
186  */
188 static inline void
189 string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t srclen)
191         if (srclen > dstlen - 1)
192                 srclen = dstlen - 1;
194         strncpy(dst, src, srclen);
195         dst[srclen] = 0;
198 /* Shorthands for safely copying into a fixed buffer. */
200 #define string_copy(dst, src) \
201         string_ncopy_do(dst, sizeof(dst), src, sizeof(src))
203 #define string_ncopy(dst, src, srclen) \
204         string_ncopy_do(dst, sizeof(dst), src, srclen)
206 #define string_copy_rev(dst, src) \
207         string_ncopy_do(dst, SIZEOF_REV, src, SIZEOF_REV - 1)
209 #define string_add(dst, from, src) \
210         string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src))
212 static size_t
213 string_expand(char *dst, size_t dstlen, const char *src, int tabsize)
215         size_t size, pos;
217         for (size = pos = 0; size < dstlen - 1 && src[pos]; pos++) {
218                 if (src[pos] == '\t') {
219                         size_t expanded = tabsize - (size % tabsize);
221                         if (expanded + size >= dstlen - 1)
222                                 expanded = dstlen - size - 1;
223                         memcpy(dst + size, "        ", expanded);
224                         size += expanded;
225                 } else {
226                         dst[size++] = src[pos];
227                 }
228         }
230         dst[size] = 0;
231         return pos;
234 static char *
235 chomp_string(char *name)
237         int namelen;
239         while (isspace(*name))
240                 name++;
242         namelen = strlen(name) - 1;
243         while (namelen > 0 && isspace(name[namelen]))
244                 name[namelen--] = 0;
246         return name;
249 static bool
250 string_nformat(char *buf, size_t bufsize, size_t *bufpos, const char *fmt, ...)
252         va_list args;
253         size_t pos = bufpos ? *bufpos : 0;
255         va_start(args, fmt);
256         pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
257         va_end(args);
259         if (bufpos)
260                 *bufpos = pos;
262         return pos >= bufsize ? FALSE : TRUE;
265 #define string_format(buf, fmt, args...) \
266         string_nformat(buf, sizeof(buf), NULL, fmt, args)
268 #define string_format_from(buf, from, fmt, args...) \
269         string_nformat(buf, sizeof(buf), from, fmt, args)
271 static int
272 string_enum_compare(const char *str1, const char *str2, int len)
274         size_t i;
276 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
278         /* Diff-Header == DIFF_HEADER */
279         for (i = 0; i < len; i++) {
280                 if (toupper(str1[i]) == toupper(str2[i]))
281                         continue;
283                 if (string_enum_sep(str1[i]) &&
284                     string_enum_sep(str2[i]))
285                         continue;
287                 return str1[i] - str2[i];
288         }
290         return 0;
293 #define enum_equals(entry, str, len) \
294         ((entry).namelen == (len) && !string_enum_compare((entry).name, str, len))
296 struct enum_map {
297         const char *name;
298         int namelen;
299         int value;
300 };
302 #define ENUM_MAP(name, value) { name, STRING_SIZE(name), value }
304 static char *
305 enum_map_name(const char *name, size_t namelen)
307         static char buf[SIZEOF_STR];
308         int bufpos;
310         for (bufpos = 0; bufpos <= namelen; bufpos++) {
311                 buf[bufpos] = tolower(name[bufpos]);
312                 if (buf[bufpos] == '_')
313                         buf[bufpos] = '-';
314         }
316         buf[bufpos] = 0;
317         return buf;
320 #define enum_name(entry) enum_map_name((entry).name, (entry).namelen)
322 static bool
323 map_enum_do(const struct enum_map *map, size_t map_size, int *value, const char *name)
325         size_t namelen = strlen(name);
326         int i;
328         for (i = 0; i < map_size; i++)
329                 if (enum_equals(map[i], name, namelen)) {
330                         *value = map[i].value;
331                         return TRUE;
332                 }
334         return FALSE;
337 #define map_enum(attr, map, name) \
338         map_enum_do(map, ARRAY_SIZE(map), attr, name)
340 #define prefixcmp(str1, str2) \
341         strncmp(str1, str2, STRING_SIZE(str2))
343 static inline int
344 suffixcmp(const char *str, int slen, const char *suffix)
346         size_t len = slen >= 0 ? slen : strlen(str);
347         size_t suffixlen = strlen(suffix);
349         return suffixlen < len ? strcmp(str + len - suffixlen, suffix) : -1;
353 /*
354  * Unicode / UTF-8 handling
355  *
356  * NOTE: Much of the following code for dealing with Unicode is derived from
357  * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
358  * src/intl/charset.c from the UTF-8 branch commit elinks-0.11.0-g31f2c28.
359  */
361 static inline int
362 unicode_width(unsigned long c, int tab_size)
364         if (c >= 0x1100 &&
365            (c <= 0x115f                         /* Hangul Jamo */
366             || c == 0x2329
367             || c == 0x232a
368             || (c >= 0x2e80  && c <= 0xa4cf && c != 0x303f)
369                                                 /* CJK ... Yi */
370             || (c >= 0xac00  && c <= 0xd7a3)    /* Hangul Syllables */
371             || (c >= 0xf900  && c <= 0xfaff)    /* CJK Compatibility Ideographs */
372             || (c >= 0xfe30  && c <= 0xfe6f)    /* CJK Compatibility Forms */
373             || (c >= 0xff00  && c <= 0xff60)    /* Fullwidth Forms */
374             || (c >= 0xffe0  && c <= 0xffe6)
375             || (c >= 0x20000 && c <= 0x2fffd)
376             || (c >= 0x30000 && c <= 0x3fffd)))
377                 return 2;
379         if (c == '\t')
380                 return tab_size;
382         return 1;
385 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
386  * Illegal bytes are set one. */
387 static const unsigned char utf8_bytes[256] = {
388         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,
389         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,
390         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,
391         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,
392         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,
393         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,
394         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,
395         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,
396 };
398 static inline unsigned char
399 utf8_char_length(const char *string, const char *end)
401         int c = *(unsigned char *) string;
403         return utf8_bytes[c];
406 /* Decode UTF-8 multi-byte representation into a Unicode character. */
407 static inline unsigned long
408 utf8_to_unicode(const char *string, size_t length)
410         unsigned long unicode;
412         switch (length) {
413         case 1:
414                 unicode  =   string[0];
415                 break;
416         case 2:
417                 unicode  =  (string[0] & 0x1f) << 6;
418                 unicode +=  (string[1] & 0x3f);
419                 break;
420         case 3:
421                 unicode  =  (string[0] & 0x0f) << 12;
422                 unicode += ((string[1] & 0x3f) << 6);
423                 unicode +=  (string[2] & 0x3f);
424                 break;
425         case 4:
426                 unicode  =  (string[0] & 0x0f) << 18;
427                 unicode += ((string[1] & 0x3f) << 12);
428                 unicode += ((string[2] & 0x3f) << 6);
429                 unicode +=  (string[3] & 0x3f);
430                 break;
431         case 5:
432                 unicode  =  (string[0] & 0x0f) << 24;
433                 unicode += ((string[1] & 0x3f) << 18);
434                 unicode += ((string[2] & 0x3f) << 12);
435                 unicode += ((string[3] & 0x3f) << 6);
436                 unicode +=  (string[4] & 0x3f);
437                 break;
438         case 6:
439                 unicode  =  (string[0] & 0x01) << 30;
440                 unicode += ((string[1] & 0x3f) << 24);
441                 unicode += ((string[2] & 0x3f) << 18);
442                 unicode += ((string[3] & 0x3f) << 12);
443                 unicode += ((string[4] & 0x3f) << 6);
444                 unicode +=  (string[5] & 0x3f);
445                 break;
446         default:
447                 return 0;
448         }
450         /* Invalid characters could return the special 0xfffd value but NUL
451          * should be just as good. */
452         return unicode > 0xffff ? 0 : unicode;
455 /* Calculates how much of string can be shown within the given maximum width
456  * and sets trimmed parameter to non-zero value if all of string could not be
457  * shown. If the reserve flag is TRUE, it will reserve at least one
458  * trailing character, which can be useful when drawing a delimiter.
459  *
460  * Returns the number of bytes to output from string to satisfy max_width. */
461 static size_t
462 utf8_length(const char **start, size_t skip, int *width, size_t max_width, int *trimmed, bool reserve, int tab_size)
464         const char *string = *start;
465         const char *end = strchr(string, '\0');
466         unsigned char last_bytes = 0;
467         size_t last_ucwidth = 0;
469         *width = 0;
470         *trimmed = 0;
472         while (string < end) {
473                 unsigned char bytes = utf8_char_length(string, end);
474                 size_t ucwidth;
475                 unsigned long unicode;
477                 if (string + bytes > end)
478                         break;
480                 /* Change representation to figure out whether
481                  * it is a single- or double-width character. */
483                 unicode = utf8_to_unicode(string, bytes);
484                 /* FIXME: Graceful handling of invalid Unicode character. */
485                 if (!unicode)
486                         break;
488                 ucwidth = unicode_width(unicode, tab_size);
489                 if (skip > 0) {
490                         skip -= ucwidth <= skip ? ucwidth : skip;
491                         *start += bytes;
492                 }
493                 *width  += ucwidth;
494                 if (*width > max_width) {
495                         *trimmed = 1;
496                         *width -= ucwidth;
497                         if (reserve && *width == max_width) {
498                                 string -= last_bytes;
499                                 *width -= last_ucwidth;
500                         }
501                         break;
502                 }
504                 string  += bytes;
505                 last_bytes = ucwidth ? bytes : 0;
506                 last_ucwidth = ucwidth;
507         }
509         return string - *start;
513 #define DATE_INFO \
514         DATE_(NO), \
515         DATE_(DEFAULT), \
516         DATE_(LOCAL), \
517         DATE_(RELATIVE), \
518         DATE_(SHORT)
520 enum date {
521 #define DATE_(name) DATE_##name
522         DATE_INFO
523 #undef  DATE_
524 };
526 static const struct enum_map date_map[] = {
527 #define DATE_(name) ENUM_MAP(#name, DATE_##name)
528         DATE_INFO
529 #undef  DATE_
530 };
532 struct time {
533         time_t sec;
534         int tz;
535 };
537 static inline int timecmp(const struct time *t1, const struct time *t2)
539         return t1->sec - t2->sec;
542 static const char *
543 mkdate(const struct time *time, enum date date)
545         static char buf[DATE_COLS + 1];
546         static const struct enum_map reldate[] = {
547                 { "second", 1,                  60 * 2 },
548                 { "minute", 60,                 60 * 60 * 2 },
549                 { "hour",   60 * 60,            60 * 60 * 24 * 2 },
550                 { "day",    60 * 60 * 24,       60 * 60 * 24 * 7 * 2 },
551                 { "week",   60 * 60 * 24 * 7,   60 * 60 * 24 * 7 * 5 },
552                 { "month",  60 * 60 * 24 * 30,  60 * 60 * 24 * 30 * 12 },
553         };
554         struct tm tm;
556         if (!date || !time || !time->sec)
557                 return "";
559         if (date == DATE_RELATIVE) {
560                 struct timeval now;
561                 time_t date = time->sec + time->tz;
562                 time_t seconds;
563                 int i;
565                 gettimeofday(&now, NULL);
566                 seconds = now.tv_sec < date ? date - now.tv_sec : now.tv_sec - date;
567                 for (i = 0; i < ARRAY_SIZE(reldate); i++) {
568                         if (seconds >= reldate[i].value)
569                                 continue;
571                         seconds /= reldate[i].namelen;
572                         if (!string_format(buf, "%ld %s%s %s",
573                                            seconds, reldate[i].name,
574                                            seconds > 1 ? "s" : "",
575                                            now.tv_sec >= date ? "ago" : "ahead"))
576                                 break;
577                         return buf;
578                 }
579         }
581         if (date == DATE_LOCAL) {
582                 time_t date = time->sec + time->tz;
583                 localtime_r(&date, &tm);
584         }
585         else {
586                 gmtime_r(&time->sec, &tm);
587         }
588         return strftime(buf, sizeof(buf), DATE_FORMAT, &tm) ? buf : NULL;
592 #define AUTHOR_VALUES \
593         AUTHOR_(NO), \
594         AUTHOR_(FULL), \
595         AUTHOR_(ABBREVIATED)
597 enum author {
598 #define AUTHOR_(name) AUTHOR_##name
599         AUTHOR_VALUES,
600 #undef  AUTHOR_
601         AUTHOR_DEFAULT = AUTHOR_FULL
602 };
604 static const struct enum_map author_map[] = {
605 #define AUTHOR_(name) ENUM_MAP(#name, AUTHOR_##name)
606         AUTHOR_VALUES
607 #undef  AUTHOR_
608 };
610 static const char *
611 get_author_initials(const char *author)
613         static char initials[AUTHOR_COLS * 6 + 1];
614         size_t pos = 0;
615         const char *end = strchr(author, '\0');
617 #define is_initial_sep(c) (isspace(c) || ispunct(c) || (c) == '@' || (c) == '-')
619         memset(initials, 0, sizeof(initials));
620         while (author < end) {
621                 unsigned char bytes;
622                 size_t i;
624                 while (is_initial_sep(*author))
625                         author++;
627                 bytes = utf8_char_length(author, end);
628                 if (bytes < sizeof(initials) - 1 - pos) {
629                         while (bytes--) {
630                                 initials[pos++] = *author++;
631                         }
632                 }
634                 for (i = pos; author < end && !is_initial_sep(*author); author++) {
635                         if (i < sizeof(initials) - 1)
636                                 initials[i++] = *author;
637                 }
639                 initials[i++] = 0;
640         }
642         return initials;
646 static bool
647 argv_from_string(const char *argv[SIZEOF_ARG], int *argc, char *cmd)
649         int valuelen;
651         while (*cmd && *argc < SIZEOF_ARG && (valuelen = strcspn(cmd, " \t"))) {
652                 bool advance = cmd[valuelen] != 0;
654                 cmd[valuelen] = 0;
655                 argv[(*argc)++] = chomp_string(cmd);
656                 cmd = chomp_string(cmd + valuelen + advance);
657         }
659         if (*argc < SIZEOF_ARG)
660                 argv[*argc] = NULL;
661         return *argc < SIZEOF_ARG;
664 static bool
665 argv_from_env(const char **argv, const char *name)
667         char *env = argv ? getenv(name) : NULL;
668         int argc = 0;
670         if (env && *env)
671                 env = strdup(env);
672         return !env || argv_from_string(argv, &argc, env);
675 static void
676 argv_free(const char *argv[])
678         int argc;
680         if (!argv)
681                 return;
682         for (argc = 0; argv[argc]; argc++)
683                 free((void *) argv[argc]);
684         argv[0] = NULL;
687 DEFINE_ALLOCATOR(argv_realloc, const char *, SIZEOF_ARG)
689 static bool
690 argv_append(const char ***argv, const char *arg)
692         int argc = 0;
694         while (*argv && (*argv)[argc])
695                 argc++;
697         if (!argv_realloc(argv, argc, 2))
698                 return FALSE;
700         (*argv)[argc++] = strdup(arg);
701         (*argv)[argc] = NULL;
702         return TRUE;
705 static bool
706 argv_append_array(const char ***dst_argv, const char *src_argv[])
708         int i;
710         for (i = 0; src_argv && src_argv[i]; i++)
711                 if (!argv_append(dst_argv, src_argv[i]))
712                         return FALSE;
713         return TRUE;
716 static bool
717 argv_copy(const char ***dst, const char *src[])
719         int argc;
721         for (argc = 0; src[argc]; argc++)
722                 if (!argv_append(dst, src[argc]))
723                         return FALSE;
724         return TRUE;
728 /*
729  * Executing external commands.
730  */
732 enum io_type {
733         IO_FD,                  /* File descriptor based IO. */
734         IO_BG,                  /* Execute command in the background. */
735         IO_FG,                  /* Execute command with same std{in,out,err}. */
736         IO_RD,                  /* Read only fork+exec IO. */
737         IO_WR,                  /* Write only fork+exec IO. */
738         IO_AP,                  /* Append fork+exec output to file. */
739 };
741 struct io {
742         int pipe;               /* Pipe end for reading or writing. */
743         pid_t pid;              /* PID of spawned process. */
744         int error;              /* Error status. */
745         char *buf;              /* Read buffer. */
746         size_t bufalloc;        /* Allocated buffer size. */
747         size_t bufsize;         /* Buffer content size. */
748         char *bufpos;           /* Current buffer position. */
749         unsigned int eof:1;     /* Has end of file been reached. */
750 };
752 static void
753 io_init(struct io *io)
755         memset(io, 0, sizeof(*io));
756         io->pipe = -1;
759 static bool
760 io_open(struct io *io, const char *fmt, ...)
762         char name[SIZEOF_STR] = "";
763         bool fits;
764         va_list args;
766         io_init(io);
768         va_start(args, fmt);
769         fits = vsnprintf(name, sizeof(name), fmt, args) < sizeof(name);
770         va_end(args);
772         if (!fits) {
773                 io->error = ENAMETOOLONG;
774                 return FALSE;
775         }
776         io->pipe = *name ? open(name, O_RDONLY) : STDIN_FILENO;
777         if (io->pipe == -1)
778                 io->error = errno;
779         return io->pipe != -1;
782 static bool
783 io_kill(struct io *io)
785         return io->pid == 0 || kill(io->pid, SIGKILL) != -1;
788 static bool
789 io_done(struct io *io)
791         pid_t pid = io->pid;
793         if (io->pipe != -1)
794                 close(io->pipe);
795         free(io->buf);
796         io_init(io);
798         while (pid > 0) {
799                 int status;
800                 pid_t waiting = waitpid(pid, &status, 0);
802                 if (waiting < 0) {
803                         if (errno == EINTR)
804                                 continue;
805                         io->error = errno;
806                         return FALSE;
807                 }
809                 return waiting == pid &&
810                        !WIFSIGNALED(status) &&
811                        WIFEXITED(status) &&
812                        !WEXITSTATUS(status);
813         }
815         return TRUE;
818 static bool
819 io_run(struct io *io, enum io_type type, const char *dir, const char *argv[], ...)
821         int pipefds[2] = { -1, -1 };
822         va_list args;
824         io_init(io);
826         if ((type == IO_RD || type == IO_WR) && pipe(pipefds) < 0) {
827                 io->error = errno;
828                 return FALSE;
829         } else if (type == IO_AP) {
830                 va_start(args, argv);
831                 pipefds[1] = va_arg(args, int);
832                 va_end(args);
833         }
835         if ((io->pid = fork())) {
836                 if (io->pid == -1)
837                         io->error = errno;
838                 if (pipefds[!(type == IO_WR)] != -1)
839                         close(pipefds[!(type == IO_WR)]);
840                 if (io->pid != -1) {
841                         io->pipe = pipefds[!!(type == IO_WR)];
842                         return TRUE;
843                 }
845         } else {
846                 if (type != IO_FG) {
847                         int devnull = open("/dev/null", O_RDWR);
848                         int readfd  = type == IO_WR ? pipefds[0] : devnull;
849                         int writefd = (type == IO_RD || type == IO_AP)
850                                                         ? pipefds[1] : devnull;
852                         dup2(readfd,  STDIN_FILENO);
853                         dup2(writefd, STDOUT_FILENO);
854                         dup2(devnull, STDERR_FILENO);
856                         close(devnull);
857                         if (pipefds[0] != -1)
858                                 close(pipefds[0]);
859                         if (pipefds[1] != -1)
860                                 close(pipefds[1]);
861                 }
863                 if (dir && *dir && chdir(dir) == -1)
864                         exit(errno);
866                 execvp(argv[0], (char *const*) argv);
867                 exit(errno);
868         }
870         if (pipefds[!!(type == IO_WR)] != -1)
871                 close(pipefds[!!(type == IO_WR)]);
872         return FALSE;
875 static bool
876 io_complete(enum io_type type, const char **argv, const char *dir, int fd)
878         struct io io;
880         return io_run(&io, type, dir, argv, fd) && io_done(&io);
883 static bool
884 io_run_bg(const char **argv)
886         return io_complete(IO_BG, argv, NULL, -1);
889 static bool
890 io_run_fg(const char **argv, const char *dir)
892         return io_complete(IO_FG, argv, dir, -1);
895 static bool
896 io_run_append(const char **argv, int fd)
898         return io_complete(IO_AP, argv, NULL, fd);
901 static bool
902 io_eof(struct io *io)
904         return io->eof;
907 static int
908 io_error(struct io *io)
910         return io->error;
913 static char *
914 io_strerror(struct io *io)
916         return strerror(io->error);
919 static bool
920 io_can_read(struct io *io)
922         struct timeval tv = { 0, 500 };
923         fd_set fds;
925         FD_ZERO(&fds);
926         FD_SET(io->pipe, &fds);
928         return select(io->pipe + 1, &fds, NULL, NULL, &tv) > 0;
931 static ssize_t
932 io_read(struct io *io, void *buf, size_t bufsize)
934         do {
935                 ssize_t readsize = read(io->pipe, buf, bufsize);
937                 if (readsize < 0 && (errno == EAGAIN || errno == EINTR))
938                         continue;
939                 else if (readsize == -1)
940                         io->error = errno;
941                 else if (readsize == 0)
942                         io->eof = 1;
943                 return readsize;
944         } while (1);
947 DEFINE_ALLOCATOR(io_realloc_buf, char, BUFSIZ)
949 static char *
950 io_get(struct io *io, int c, bool can_read)
952         char *eol;
953         ssize_t readsize;
955         while (TRUE) {
956                 if (io->bufsize > 0) {
957                         eol = memchr(io->bufpos, c, io->bufsize);
958                         if (eol) {
959                                 char *line = io->bufpos;
961                                 *eol = 0;
962                                 io->bufpos = eol + 1;
963                                 io->bufsize -= io->bufpos - line;
964                                 return line;
965                         }
966                 }
968                 if (io_eof(io)) {
969                         if (io->bufsize) {
970                                 io->bufpos[io->bufsize] = 0;
971                                 io->bufsize = 0;
972                                 return io->bufpos;
973                         }
974                         return NULL;
975                 }
977                 if (!can_read)
978                         return NULL;
980                 if (io->bufsize > 0 && io->bufpos > io->buf)
981                         memmove(io->buf, io->bufpos, io->bufsize);
983                 if (io->bufalloc == io->bufsize) {
984                         if (!io_realloc_buf(&io->buf, io->bufalloc, BUFSIZ))
985                                 return NULL;
986                         io->bufalloc += BUFSIZ;
987                 }
989                 io->bufpos = io->buf;
990                 readsize = io_read(io, io->buf + io->bufsize, io->bufalloc - io->bufsize);
991                 if (io_error(io))
992                         return NULL;
993                 io->bufsize += readsize;
994         }
997 static bool
998 io_write(struct io *io, const void *buf, size_t bufsize)
1000         size_t written = 0;
1002         while (!io_error(io) && written < bufsize) {
1003                 ssize_t size;
1005                 size = write(io->pipe, buf + written, bufsize - written);
1006                 if (size < 0 && (errno == EAGAIN || errno == EINTR))
1007                         continue;
1008                 else if (size == -1)
1009                         io->error = errno;
1010                 else
1011                         written += size;
1012         }
1014         return written == bufsize;
1017 static bool
1018 io_read_buf(struct io *io, char buf[], size_t bufsize)
1020         char *result = io_get(io, '\n', TRUE);
1022         if (result) {
1023                 result = chomp_string(result);
1024                 string_ncopy_do(buf, bufsize, result, strlen(result));
1025         }
1027         return io_done(io) && result;
1030 static bool
1031 io_run_buf(const char **argv, char buf[], size_t bufsize)
1033         struct io io;
1035         return io_run(&io, IO_RD, NULL, argv) && io_read_buf(&io, buf, bufsize);
1038 static int
1039 io_load(struct io *io, const char *separators,
1040         int (*read_property)(char *, size_t, char *, size_t))
1042         char *name;
1043         int state = OK;
1045         while (state == OK && (name = io_get(io, '\n', TRUE))) {
1046                 char *value;
1047                 size_t namelen;
1048                 size_t valuelen;
1050                 name = chomp_string(name);
1051                 namelen = strcspn(name, separators);
1053                 if (name[namelen]) {
1054                         name[namelen] = 0;
1055                         value = chomp_string(name + namelen + 1);
1056                         valuelen = strlen(value);
1058                 } else {
1059                         value = "";
1060                         valuelen = 0;
1061                 }
1063                 state = read_property(name, namelen, value, valuelen);
1064         }
1066         if (state != ERR && io_error(io))
1067                 state = ERR;
1068         io_done(io);
1070         return state;
1073 static int
1074 io_run_load(const char **argv, const char *separators,
1075             int (*read_property)(char *, size_t, char *, size_t))
1077         struct io io;
1079         if (!io_run(&io, IO_RD, NULL, argv))
1080                 return ERR;
1081         return io_load(&io, separators, read_property);
1085 /*
1086  * User requests
1087  */
1089 #define REQ_INFO \
1090         /* XXX: Keep the view request first and in sync with views[]. */ \
1091         REQ_GROUP("View switching") \
1092         REQ_(VIEW_MAIN,         "Show main view"), \
1093         REQ_(VIEW_DIFF,         "Show diff view"), \
1094         REQ_(VIEW_LOG,          "Show log view"), \
1095         REQ_(VIEW_TREE,         "Show tree view"), \
1096         REQ_(VIEW_BLOB,         "Show blob view"), \
1097         REQ_(VIEW_BLAME,        "Show blame view"), \
1098         REQ_(VIEW_BRANCH,       "Show branch view"), \
1099         REQ_(VIEW_HELP,         "Show help page"), \
1100         REQ_(VIEW_PAGER,        "Show pager view"), \
1101         REQ_(VIEW_STATUS,       "Show status view"), \
1102         REQ_(VIEW_STAGE,        "Show stage view"), \
1103         \
1104         REQ_GROUP("View manipulation") \
1105         REQ_(ENTER,             "Enter current line and scroll"), \
1106         REQ_(NEXT,              "Move to next"), \
1107         REQ_(PREVIOUS,          "Move to previous"), \
1108         REQ_(PARENT,            "Move to parent"), \
1109         REQ_(VIEW_NEXT,         "Move focus to next view"), \
1110         REQ_(REFRESH,           "Reload and refresh"), \
1111         REQ_(MAXIMIZE,          "Maximize the current view"), \
1112         REQ_(VIEW_CLOSE,        "Close the current view"), \
1113         REQ_(QUIT,              "Close all views and quit"), \
1114         \
1115         REQ_GROUP("View specific requests") \
1116         REQ_(STATUS_UPDATE,     "Update file status"), \
1117         REQ_(STATUS_REVERT,     "Revert file changes"), \
1118         REQ_(STATUS_MERGE,      "Merge file using external tool"), \
1119         REQ_(STAGE_NEXT,        "Find next chunk to stage"), \
1120         \
1121         REQ_GROUP("Cursor navigation") \
1122         REQ_(MOVE_UP,           "Move cursor one line up"), \
1123         REQ_(MOVE_DOWN,         "Move cursor one line down"), \
1124         REQ_(MOVE_PAGE_DOWN,    "Move cursor one page down"), \
1125         REQ_(MOVE_PAGE_UP,      "Move cursor one page up"), \
1126         REQ_(MOVE_FIRST_LINE,   "Move cursor to first line"), \
1127         REQ_(MOVE_LAST_LINE,    "Move cursor to last line"), \
1128         \
1129         REQ_GROUP("Scrolling") \
1130         REQ_(SCROLL_LEFT,       "Scroll two columns left"), \
1131         REQ_(SCROLL_RIGHT,      "Scroll two columns right"), \
1132         REQ_(SCROLL_LINE_UP,    "Scroll one line up"), \
1133         REQ_(SCROLL_LINE_DOWN,  "Scroll one line down"), \
1134         REQ_(SCROLL_PAGE_UP,    "Scroll one page up"), \
1135         REQ_(SCROLL_PAGE_DOWN,  "Scroll one page down"), \
1136         \
1137         REQ_GROUP("Searching") \
1138         REQ_(SEARCH,            "Search the view"), \
1139         REQ_(SEARCH_BACK,       "Search backwards in the view"), \
1140         REQ_(FIND_NEXT,         "Find next search match"), \
1141         REQ_(FIND_PREV,         "Find previous search match"), \
1142         \
1143         REQ_GROUP("Option manipulation") \
1144         REQ_(OPTIONS,           "Open option menu"), \
1145         REQ_(TOGGLE_LINENO,     "Toggle line numbers"), \
1146         REQ_(TOGGLE_DATE,       "Toggle date display"), \
1147         REQ_(TOGGLE_DATE_SHORT, "Toggle short (date-only) dates"), \
1148         REQ_(TOGGLE_AUTHOR,     "Toggle author display"), \
1149         REQ_(TOGGLE_REV_GRAPH,  "Toggle revision graph visualization"), \
1150         REQ_(TOGGLE_REFS,       "Toggle reference display (tags/branches)"), \
1151         REQ_(TOGGLE_SORT_ORDER, "Toggle ascending/descending sort order"), \
1152         REQ_(TOGGLE_SORT_FIELD, "Toggle field to sort by"), \
1153         \
1154         REQ_GROUP("Misc") \
1155         REQ_(PROMPT,            "Bring up the prompt"), \
1156         REQ_(SCREEN_REDRAW,     "Redraw the screen"), \
1157         REQ_(SHOW_VERSION,      "Show version information"), \
1158         REQ_(STOP_LOADING,      "Stop all loading views"), \
1159         REQ_(EDIT,              "Open in editor"), \
1160         REQ_(NONE,              "Do nothing")
1163 /* User action requests. */
1164 enum request {
1165 #define REQ_GROUP(help)
1166 #define REQ_(req, help) REQ_##req
1168         /* Offset all requests to avoid conflicts with ncurses getch values. */
1169         REQ_UNKNOWN = KEY_MAX + 1,
1170         REQ_OFFSET,
1171         REQ_INFO
1173 #undef  REQ_GROUP
1174 #undef  REQ_
1175 };
1177 struct request_info {
1178         enum request request;
1179         const char *name;
1180         int namelen;
1181         const char *help;
1182 };
1184 static const struct request_info req_info[] = {
1185 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
1186 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
1187         REQ_INFO
1188 #undef  REQ_GROUP
1189 #undef  REQ_
1190 };
1192 static enum request
1193 get_request(const char *name)
1195         int namelen = strlen(name);
1196         int i;
1198         for (i = 0; i < ARRAY_SIZE(req_info); i++)
1199                 if (enum_equals(req_info[i], name, namelen))
1200                         return req_info[i].request;
1202         return REQ_UNKNOWN;
1206 /*
1207  * Options
1208  */
1210 /* Option and state variables. */
1211 static enum date opt_date               = DATE_DEFAULT;
1212 static enum author opt_author           = AUTHOR_DEFAULT;
1213 static bool opt_line_number             = FALSE;
1214 static bool opt_line_graphics           = TRUE;
1215 static bool opt_rev_graph               = FALSE;
1216 static bool opt_show_refs               = TRUE;
1217 static int opt_num_interval             = 5;
1218 static double opt_hscroll               = 0.50;
1219 static double opt_scale_split_view      = 2.0 / 3.0;
1220 static int opt_tab_size                 = 8;
1221 static int opt_author_cols              = AUTHOR_COLS;
1222 static char opt_path[SIZEOF_STR]        = "";
1223 static char opt_file[SIZEOF_STR]        = "";
1224 static char opt_ref[SIZEOF_REF]         = "";
1225 static char opt_head[SIZEOF_REF]        = "";
1226 static char opt_remote[SIZEOF_REF]      = "";
1227 static char opt_encoding[20]            = "UTF-8";
1228 static iconv_t opt_iconv_in             = ICONV_NONE;
1229 static iconv_t opt_iconv_out            = ICONV_NONE;
1230 static char opt_search[SIZEOF_STR]      = "";
1231 static char opt_cdup[SIZEOF_STR]        = "";
1232 static char opt_prefix[SIZEOF_STR]      = "";
1233 static char opt_git_dir[SIZEOF_STR]     = "";
1234 static signed char opt_is_inside_work_tree      = -1; /* set to TRUE or FALSE */
1235 static char opt_editor[SIZEOF_STR]      = "";
1236 static FILE *opt_tty                    = NULL;
1237 static const char **opt_diff_args       = NULL;
1238 static const char **opt_rev_args        = NULL;
1239 static const char **opt_file_args       = NULL;
1241 #define is_initial_commit()     (!get_ref_head())
1242 #define is_head_commit(rev)     (!strcmp((rev), "HEAD") || (get_ref_head() && !strcmp(rev, get_ref_head()->id)))
1245 /*
1246  * Line-oriented content detection.
1247  */
1249 #define LINE_INFO \
1250 LINE(DIFF_HEADER,  "diff --git ",       COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1251 LINE(DIFF_CHUNK,   "@@",                COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1252 LINE(DIFF_ADD,     "+",                 COLOR_GREEN,    COLOR_DEFAULT,  0), \
1253 LINE(DIFF_DEL,     "-",                 COLOR_RED,      COLOR_DEFAULT,  0), \
1254 LINE(DIFF_INDEX,        "index ",         COLOR_BLUE,   COLOR_DEFAULT,  0), \
1255 LINE(DIFF_OLDMODE,      "old file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
1256 LINE(DIFF_NEWMODE,      "new file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
1257 LINE(DIFF_COPY_FROM,    "copy from",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
1258 LINE(DIFF_COPY_TO,      "copy to",        COLOR_YELLOW, COLOR_DEFAULT,  0), \
1259 LINE(DIFF_RENAME_FROM,  "rename from",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
1260 LINE(DIFF_RENAME_TO,    "rename to",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
1261 LINE(DIFF_SIMILARITY,   "similarity ",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
1262 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
1263 LINE(DIFF_TREE,         "diff-tree ",     COLOR_BLUE,   COLOR_DEFAULT,  0), \
1264 LINE(PP_AUTHOR,    "Author: ",          COLOR_CYAN,     COLOR_DEFAULT,  0), \
1265 LINE(PP_COMMIT,    "Commit: ",          COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1266 LINE(PP_MERGE,     "Merge: ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
1267 LINE(PP_DATE,      "Date:   ",          COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1268 LINE(PP_ADATE,     "AuthorDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1269 LINE(PP_CDATE,     "CommitDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1270 LINE(PP_REFS,      "Refs: ",            COLOR_RED,      COLOR_DEFAULT,  0), \
1271 LINE(COMMIT,       "commit ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
1272 LINE(PARENT,       "parent ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
1273 LINE(TREE,         "tree ",             COLOR_BLUE,     COLOR_DEFAULT,  0), \
1274 LINE(AUTHOR,       "author ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
1275 LINE(COMMITTER,    "committer ",        COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1276 LINE(SIGNOFF,      "    Signed-off-by", COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1277 LINE(ACKED,        "    Acked-by",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1278 LINE(TESTED,       "    Tested-by",     COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1279 LINE(REVIEWED,     "    Reviewed-by",   COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1280 LINE(DEFAULT,      "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
1281 LINE(CURSOR,       "",                  COLOR_WHITE,    COLOR_GREEN,    A_BOLD), \
1282 LINE(STATUS,       "",                  COLOR_GREEN,    COLOR_DEFAULT,  0), \
1283 LINE(DELIMITER,    "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1284 LINE(DATE,         "",                  COLOR_BLUE,     COLOR_DEFAULT,  0), \
1285 LINE(MODE,         "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1286 LINE(LINE_NUMBER,  "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1287 LINE(TITLE_BLUR,   "",                  COLOR_WHITE,    COLOR_BLUE,     0), \
1288 LINE(TITLE_FOCUS,  "",                  COLOR_WHITE,    COLOR_BLUE,     A_BOLD), \
1289 LINE(MAIN_COMMIT,  "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
1290 LINE(MAIN_TAG,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  A_BOLD), \
1291 LINE(MAIN_LOCAL_TAG,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1292 LINE(MAIN_REMOTE,  "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1293 LINE(MAIN_TRACKED, "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_BOLD), \
1294 LINE(MAIN_REF,     "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1295 LINE(MAIN_HEAD,    "",                  COLOR_CYAN,     COLOR_DEFAULT,  A_BOLD), \
1296 LINE(MAIN_REVGRAPH,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1297 LINE(TREE_HEAD,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_BOLD), \
1298 LINE(TREE_DIR,     "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_NORMAL), \
1299 LINE(TREE_FILE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
1300 LINE(STAT_HEAD,    "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1301 LINE(STAT_SECTION, "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1302 LINE(STAT_NONE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
1303 LINE(STAT_STAGED,  "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1304 LINE(STAT_UNSTAGED,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1305 LINE(STAT_UNTRACKED,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1306 LINE(HELP_KEYMAP,  "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1307 LINE(HELP_GROUP,   "",                  COLOR_BLUE,     COLOR_DEFAULT,  0), \
1308 LINE(BLAME_ID,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0)
1310 enum line_type {
1311 #define LINE(type, line, fg, bg, attr) \
1312         LINE_##type
1313         LINE_INFO,
1314         LINE_NONE
1315 #undef  LINE
1316 };
1318 struct line_info {
1319         const char *name;       /* Option name. */
1320         int namelen;            /* Size of option name. */
1321         const char *line;       /* The start of line to match. */
1322         int linelen;            /* Size of string to match. */
1323         int fg, bg, attr;       /* Color and text attributes for the lines. */
1324 };
1326 static struct line_info line_info[] = {
1327 #define LINE(type, line, fg, bg, attr) \
1328         { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
1329         LINE_INFO
1330 #undef  LINE
1331 };
1333 static enum line_type
1334 get_line_type(const char *line)
1336         int linelen = strlen(line);
1337         enum line_type type;
1339         for (type = 0; type < ARRAY_SIZE(line_info); type++)
1340                 /* Case insensitive search matches Signed-off-by lines better. */
1341                 if (linelen >= line_info[type].linelen &&
1342                     !strncasecmp(line_info[type].line, line, line_info[type].linelen))
1343                         return type;
1345         return LINE_DEFAULT;
1348 static inline int
1349 get_line_attr(enum line_type type)
1351         assert(type < ARRAY_SIZE(line_info));
1352         return COLOR_PAIR(type) | line_info[type].attr;
1355 static struct line_info *
1356 get_line_info(const char *name)
1358         size_t namelen = strlen(name);
1359         enum line_type type;
1361         for (type = 0; type < ARRAY_SIZE(line_info); type++)
1362                 if (enum_equals(line_info[type], name, namelen))
1363                         return &line_info[type];
1365         return NULL;
1368 static void
1369 init_colors(void)
1371         int default_bg = line_info[LINE_DEFAULT].bg;
1372         int default_fg = line_info[LINE_DEFAULT].fg;
1373         enum line_type type;
1375         start_color();
1377         if (assume_default_colors(default_fg, default_bg) == ERR) {
1378                 default_bg = COLOR_BLACK;
1379                 default_fg = COLOR_WHITE;
1380         }
1382         for (type = 0; type < ARRAY_SIZE(line_info); type++) {
1383                 struct line_info *info = &line_info[type];
1384                 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
1385                 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
1387                 init_pair(type, fg, bg);
1388         }
1391 struct line {
1392         enum line_type type;
1394         /* State flags */
1395         unsigned int selected:1;
1396         unsigned int dirty:1;
1397         unsigned int cleareol:1;
1398         unsigned int other:16;
1400         void *data;             /* User data */
1401 };
1404 /*
1405  * Keys
1406  */
1408 struct keybinding {
1409         int alias;
1410         enum request request;
1411 };
1413 static struct keybinding default_keybindings[] = {
1414         /* View switching */
1415         { 'm',          REQ_VIEW_MAIN },
1416         { 'd',          REQ_VIEW_DIFF },
1417         { 'l',          REQ_VIEW_LOG },
1418         { 't',          REQ_VIEW_TREE },
1419         { 'f',          REQ_VIEW_BLOB },
1420         { 'B',          REQ_VIEW_BLAME },
1421         { 'H',          REQ_VIEW_BRANCH },
1422         { 'p',          REQ_VIEW_PAGER },
1423         { 'h',          REQ_VIEW_HELP },
1424         { 'S',          REQ_VIEW_STATUS },
1425         { 'c',          REQ_VIEW_STAGE },
1427         /* View manipulation */
1428         { 'q',          REQ_VIEW_CLOSE },
1429         { KEY_TAB,      REQ_VIEW_NEXT },
1430         { KEY_RETURN,   REQ_ENTER },
1431         { KEY_UP,       REQ_PREVIOUS },
1432         { KEY_DOWN,     REQ_NEXT },
1433         { 'R',          REQ_REFRESH },
1434         { KEY_F(5),     REQ_REFRESH },
1435         { 'O',          REQ_MAXIMIZE },
1437         /* Cursor navigation */
1438         { 'k',          REQ_MOVE_UP },
1439         { 'j',          REQ_MOVE_DOWN },
1440         { KEY_HOME,     REQ_MOVE_FIRST_LINE },
1441         { KEY_END,      REQ_MOVE_LAST_LINE },
1442         { KEY_NPAGE,    REQ_MOVE_PAGE_DOWN },
1443         { ' ',          REQ_MOVE_PAGE_DOWN },
1444         { KEY_PPAGE,    REQ_MOVE_PAGE_UP },
1445         { 'b',          REQ_MOVE_PAGE_UP },
1446         { '-',          REQ_MOVE_PAGE_UP },
1448         /* Scrolling */
1449         { KEY_LEFT,     REQ_SCROLL_LEFT },
1450         { KEY_RIGHT,    REQ_SCROLL_RIGHT },
1451         { KEY_IC,       REQ_SCROLL_LINE_UP },
1452         { KEY_DC,       REQ_SCROLL_LINE_DOWN },
1453         { 'w',          REQ_SCROLL_PAGE_UP },
1454         { 's',          REQ_SCROLL_PAGE_DOWN },
1456         /* Searching */
1457         { '/',          REQ_SEARCH },
1458         { '?',          REQ_SEARCH_BACK },
1459         { 'n',          REQ_FIND_NEXT },
1460         { 'N',          REQ_FIND_PREV },
1462         /* Misc */
1463         { 'Q',          REQ_QUIT },
1464         { 'z',          REQ_STOP_LOADING },
1465         { 'v',          REQ_SHOW_VERSION },
1466         { 'r',          REQ_SCREEN_REDRAW },
1467         { 'o',          REQ_OPTIONS },
1468         { '.',          REQ_TOGGLE_LINENO },
1469         { 'D',          REQ_TOGGLE_DATE },
1470         { 'A',          REQ_TOGGLE_AUTHOR },
1471         { 'g',          REQ_TOGGLE_REV_GRAPH },
1472         { 'F',          REQ_TOGGLE_REFS },
1473         { 'I',          REQ_TOGGLE_SORT_ORDER },
1474         { 'i',          REQ_TOGGLE_SORT_FIELD },
1475         { ':',          REQ_PROMPT },
1476         { 'u',          REQ_STATUS_UPDATE },
1477         { '!',          REQ_STATUS_REVERT },
1478         { 'M',          REQ_STATUS_MERGE },
1479         { '@',          REQ_STAGE_NEXT },
1480         { ',',          REQ_PARENT },
1481         { 'e',          REQ_EDIT },
1482 };
1484 #define KEYMAP_INFO \
1485         KEYMAP_(GENERIC), \
1486         KEYMAP_(MAIN), \
1487         KEYMAP_(DIFF), \
1488         KEYMAP_(LOG), \
1489         KEYMAP_(TREE), \
1490         KEYMAP_(BLOB), \
1491         KEYMAP_(BLAME), \
1492         KEYMAP_(BRANCH), \
1493         KEYMAP_(PAGER), \
1494         KEYMAP_(HELP), \
1495         KEYMAP_(STATUS), \
1496         KEYMAP_(STAGE)
1498 enum keymap {
1499 #define KEYMAP_(name) KEYMAP_##name
1500         KEYMAP_INFO
1501 #undef  KEYMAP_
1502 };
1504 static const struct enum_map keymap_table[] = {
1505 #define KEYMAP_(name) ENUM_MAP(#name, KEYMAP_##name)
1506         KEYMAP_INFO
1507 #undef  KEYMAP_
1508 };
1510 #define set_keymap(map, name) map_enum(map, keymap_table, name)
1512 struct keybinding_table {
1513         struct keybinding *data;
1514         size_t size;
1515 };
1517 static struct keybinding_table keybindings[ARRAY_SIZE(keymap_table)];
1519 static void
1520 add_keybinding(enum keymap keymap, enum request request, int key)
1522         struct keybinding_table *table = &keybindings[keymap];
1523         size_t i;
1525         for (i = 0; i < keybindings[keymap].size; i++) {
1526                 if (keybindings[keymap].data[i].alias == key) {
1527                         keybindings[keymap].data[i].request = request;
1528                         return;
1529                 }
1530         }
1532         table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data));
1533         if (!table->data)
1534                 die("Failed to allocate keybinding");
1535         table->data[table->size].alias = key;
1536         table->data[table->size++].request = request;
1538         if (request == REQ_NONE && keymap == KEYMAP_GENERIC) {
1539                 int i;
1541                 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1542                         if (default_keybindings[i].alias == key)
1543                                 default_keybindings[i].request = REQ_NONE;
1544         }
1547 /* Looks for a key binding first in the given map, then in the generic map, and
1548  * lastly in the default keybindings. */
1549 static enum request
1550 get_keybinding(enum keymap keymap, int key)
1552         size_t i;
1554         for (i = 0; i < keybindings[keymap].size; i++)
1555                 if (keybindings[keymap].data[i].alias == key)
1556                         return keybindings[keymap].data[i].request;
1558         for (i = 0; i < keybindings[KEYMAP_GENERIC].size; i++)
1559                 if (keybindings[KEYMAP_GENERIC].data[i].alias == key)
1560                         return keybindings[KEYMAP_GENERIC].data[i].request;
1562         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1563                 if (default_keybindings[i].alias == key)
1564                         return default_keybindings[i].request;
1566         return (enum request) key;
1570 struct key {
1571         const char *name;
1572         int value;
1573 };
1575 static const struct key key_table[] = {
1576         { "Enter",      KEY_RETURN },
1577         { "Space",      ' ' },
1578         { "Backspace",  KEY_BACKSPACE },
1579         { "Tab",        KEY_TAB },
1580         { "Escape",     KEY_ESC },
1581         { "Left",       KEY_LEFT },
1582         { "Right",      KEY_RIGHT },
1583         { "Up",         KEY_UP },
1584         { "Down",       KEY_DOWN },
1585         { "Insert",     KEY_IC },
1586         { "Delete",     KEY_DC },
1587         { "Hash",       '#' },
1588         { "Home",       KEY_HOME },
1589         { "End",        KEY_END },
1590         { "PageUp",     KEY_PPAGE },
1591         { "PageDown",   KEY_NPAGE },
1592         { "F1",         KEY_F(1) },
1593         { "F2",         KEY_F(2) },
1594         { "F3",         KEY_F(3) },
1595         { "F4",         KEY_F(4) },
1596         { "F5",         KEY_F(5) },
1597         { "F6",         KEY_F(6) },
1598         { "F7",         KEY_F(7) },
1599         { "F8",         KEY_F(8) },
1600         { "F9",         KEY_F(9) },
1601         { "F10",        KEY_F(10) },
1602         { "F11",        KEY_F(11) },
1603         { "F12",        KEY_F(12) },
1604 };
1606 static int
1607 get_key_value(const char *name)
1609         int i;
1611         for (i = 0; i < ARRAY_SIZE(key_table); i++)
1612                 if (!strcasecmp(key_table[i].name, name))
1613                         return key_table[i].value;
1615         if (strlen(name) == 1 && isprint(*name))
1616                 return (int) *name;
1618         return ERR;
1621 static const char *
1622 get_key_name(int key_value)
1624         static char key_char[] = "'X'";
1625         const char *seq = NULL;
1626         int key;
1628         for (key = 0; key < ARRAY_SIZE(key_table); key++)
1629                 if (key_table[key].value == key_value)
1630                         seq = key_table[key].name;
1632         if (seq == NULL &&
1633             key_value < 127 &&
1634             isprint(key_value)) {
1635                 key_char[1] = (char) key_value;
1636                 seq = key_char;
1637         }
1639         return seq ? seq : "(no key)";
1642 static bool
1643 append_key(char *buf, size_t *pos, const struct keybinding *keybinding)
1645         const char *sep = *pos > 0 ? ", " : "";
1646         const char *keyname = get_key_name(keybinding->alias);
1648         return string_nformat(buf, BUFSIZ, pos, "%s%s", sep, keyname);
1651 static bool
1652 append_keymap_request_keys(char *buf, size_t *pos, enum request request,
1653                            enum keymap keymap, bool all)
1655         int i;
1657         for (i = 0; i < keybindings[keymap].size; i++) {
1658                 if (keybindings[keymap].data[i].request == request) {
1659                         if (!append_key(buf, pos, &keybindings[keymap].data[i]))
1660                                 return FALSE;
1661                         if (!all)
1662                                 break;
1663                 }
1664         }
1666         return TRUE;
1669 #define get_key(keymap, request) get_keys(keymap, request, FALSE)
1671 static const char *
1672 get_keys(enum keymap keymap, enum request request, bool all)
1674         static char buf[BUFSIZ];
1675         size_t pos = 0;
1676         int i;
1678         buf[pos] = 0;
1680         if (!append_keymap_request_keys(buf, &pos, request, keymap, all))
1681                 return "Too many keybindings!";
1682         if (pos > 0 && !all)
1683                 return buf;
1685         if (keymap != KEYMAP_GENERIC) {
1686                 /* Only the generic keymap includes the default keybindings when
1687                  * listing all keys. */
1688                 if (all)
1689                         return buf;
1691                 if (!append_keymap_request_keys(buf, &pos, request, KEYMAP_GENERIC, all))
1692                         return "Too many keybindings!";
1693                 if (pos)
1694                         return buf;
1695         }
1697         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1698                 if (default_keybindings[i].request == request) {
1699                         if (!append_key(buf, &pos, &default_keybindings[i]))
1700                                 return "Too many keybindings!";
1701                         if (!all)
1702                                 return buf;
1703                 }
1704         }
1706         return buf;
1709 struct run_request {
1710         enum keymap keymap;
1711         int key;
1712         const char **argv;
1713 };
1715 static struct run_request *run_request;
1716 static size_t run_requests;
1718 DEFINE_ALLOCATOR(realloc_run_requests, struct run_request, 8)
1720 static enum request
1721 add_run_request(enum keymap keymap, int key, const char **argv)
1723         struct run_request *req;
1725         if (!realloc_run_requests(&run_request, run_requests, 1))
1726                 return REQ_NONE;
1728         req = &run_request[run_requests];
1729         req->keymap = keymap;
1730         req->key = key;
1731         req->argv = NULL;
1733         if (!argv_copy(&req->argv, argv))
1734                 return REQ_NONE;
1736         return REQ_NONE + ++run_requests;
1739 static struct run_request *
1740 get_run_request(enum request request)
1742         if (request <= REQ_NONE)
1743                 return NULL;
1744         return &run_request[request - REQ_NONE - 1];
1747 static void
1748 add_builtin_run_requests(void)
1750         const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1751         const char *checkout[] = { "git", "checkout", "%(branch)", NULL };
1752         const char *commit[] = { "git", "commit", NULL };
1753         const char *gc[] = { "git", "gc", NULL };
1754         struct run_request reqs[] = {
1755                 { KEYMAP_MAIN,    'C', cherry_pick },
1756                 { KEYMAP_STATUS,  'C', commit },
1757                 { KEYMAP_BRANCH,  'C', checkout },
1758                 { KEYMAP_GENERIC, 'G', gc },
1759         };
1760         int i;
1762         for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1763                 enum request req = get_keybinding(reqs[i].keymap, reqs[i].key);
1765                 if (req != reqs[i].key)
1766                         continue;
1767                 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argv);
1768                 if (req != REQ_NONE)
1769                         add_keybinding(reqs[i].keymap, req, reqs[i].key);
1770         }
1773 /*
1774  * User config file handling.
1775  */
1777 static int   config_lineno;
1778 static bool  config_errors;
1779 static const char *config_msg;
1781 static const struct enum_map color_map[] = {
1782 #define COLOR_MAP(name) ENUM_MAP(#name, COLOR_##name)
1783         COLOR_MAP(DEFAULT),
1784         COLOR_MAP(BLACK),
1785         COLOR_MAP(BLUE),
1786         COLOR_MAP(CYAN),
1787         COLOR_MAP(GREEN),
1788         COLOR_MAP(MAGENTA),
1789         COLOR_MAP(RED),
1790         COLOR_MAP(WHITE),
1791         COLOR_MAP(YELLOW),
1792 };
1794 static const struct enum_map attr_map[] = {
1795 #define ATTR_MAP(name) ENUM_MAP(#name, A_##name)
1796         ATTR_MAP(NORMAL),
1797         ATTR_MAP(BLINK),
1798         ATTR_MAP(BOLD),
1799         ATTR_MAP(DIM),
1800         ATTR_MAP(REVERSE),
1801         ATTR_MAP(STANDOUT),
1802         ATTR_MAP(UNDERLINE),
1803 };
1805 #define set_attribute(attr, name)       map_enum(attr, attr_map, name)
1807 static int parse_step(double *opt, const char *arg)
1809         *opt = atoi(arg);
1810         if (!strchr(arg, '%'))
1811                 return OK;
1813         /* "Shift down" so 100% and 1 does not conflict. */
1814         *opt = (*opt - 1) / 100;
1815         if (*opt >= 1.0) {
1816                 *opt = 0.99;
1817                 config_msg = "Step value larger than 100%";
1818                 return ERR;
1819         }
1820         if (*opt < 0.0) {
1821                 *opt = 1;
1822                 config_msg = "Invalid step value";
1823                 return ERR;
1824         }
1825         return OK;
1828 static int
1829 parse_int(int *opt, const char *arg, int min, int max)
1831         int value = atoi(arg);
1833         if (min <= value && value <= max) {
1834                 *opt = value;
1835                 return OK;
1836         }
1838         config_msg = "Integer value out of bound";
1839         return ERR;
1842 static bool
1843 set_color(int *color, const char *name)
1845         if (map_enum(color, color_map, name))
1846                 return TRUE;
1847         if (!prefixcmp(name, "color"))
1848                 return parse_int(color, name + 5, 0, 255) == OK;
1849         return FALSE;
1852 /* Wants: object fgcolor bgcolor [attribute] */
1853 static int
1854 option_color_command(int argc, const char *argv[])
1856         struct line_info *info;
1858         if (argc < 3) {
1859                 config_msg = "Wrong number of arguments given to color command";
1860                 return ERR;
1861         }
1863         info = get_line_info(argv[0]);
1864         if (!info) {
1865                 static const struct enum_map obsolete[] = {
1866                         ENUM_MAP("main-delim",  LINE_DELIMITER),
1867                         ENUM_MAP("main-date",   LINE_DATE),
1868                         ENUM_MAP("main-author", LINE_AUTHOR),
1869                 };
1870                 int index;
1872                 if (!map_enum(&index, obsolete, argv[0])) {
1873                         config_msg = "Unknown color name";
1874                         return ERR;
1875                 }
1876                 info = &line_info[index];
1877         }
1879         if (!set_color(&info->fg, argv[1]) ||
1880             !set_color(&info->bg, argv[2])) {
1881                 config_msg = "Unknown color";
1882                 return ERR;
1883         }
1885         info->attr = 0;
1886         while (argc-- > 3) {
1887                 int attr;
1889                 if (!set_attribute(&attr, argv[argc])) {
1890                         config_msg = "Unknown attribute";
1891                         return ERR;
1892                 }
1893                 info->attr |= attr;
1894         }
1896         return OK;
1899 static int parse_bool(bool *opt, const char *arg)
1901         *opt = (!strcmp(arg, "1") || !strcmp(arg, "true") || !strcmp(arg, "yes"))
1902                 ? TRUE : FALSE;
1903         return OK;
1906 static int parse_enum_do(unsigned int *opt, const char *arg,
1907                          const struct enum_map *map, size_t map_size)
1909         bool is_true;
1911         assert(map_size > 1);
1913         if (map_enum_do(map, map_size, (int *) opt, arg))
1914                 return OK;
1916         if (parse_bool(&is_true, arg) != OK)
1917                 return ERR;
1919         *opt = is_true ? map[1].value : map[0].value;
1920         return OK;
1923 #define parse_enum(opt, arg, map) \
1924         parse_enum_do(opt, arg, map, ARRAY_SIZE(map))
1926 static int
1927 parse_string(char *opt, const char *arg, size_t optsize)
1929         int arglen = strlen(arg);
1931         switch (arg[0]) {
1932         case '\"':
1933         case '\'':
1934                 if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1935                         config_msg = "Unmatched quotation";
1936                         return ERR;
1937                 }
1938                 arg += 1; arglen -= 2;
1939         default:
1940                 string_ncopy_do(opt, optsize, arg, arglen);
1941                 return OK;
1942         }
1945 /* Wants: name = value */
1946 static int
1947 option_set_command(int argc, const char *argv[])
1949         if (argc != 3) {
1950                 config_msg = "Wrong number of arguments given to set command";
1951                 return ERR;
1952         }
1954         if (strcmp(argv[1], "=")) {
1955                 config_msg = "No value assigned";
1956                 return ERR;
1957         }
1959         if (!strcmp(argv[0], "show-author"))
1960                 return parse_enum(&opt_author, argv[2], author_map);
1962         if (!strcmp(argv[0], "show-date"))
1963                 return parse_enum(&opt_date, argv[2], date_map);
1965         if (!strcmp(argv[0], "show-rev-graph"))
1966                 return parse_bool(&opt_rev_graph, argv[2]);
1968         if (!strcmp(argv[0], "show-refs"))
1969                 return parse_bool(&opt_show_refs, argv[2]);
1971         if (!strcmp(argv[0], "show-line-numbers"))
1972                 return parse_bool(&opt_line_number, argv[2]);
1974         if (!strcmp(argv[0], "line-graphics"))
1975                 return parse_bool(&opt_line_graphics, argv[2]);
1977         if (!strcmp(argv[0], "line-number-interval"))
1978                 return parse_int(&opt_num_interval, argv[2], 1, 1024);
1980         if (!strcmp(argv[0], "author-width"))
1981                 return parse_int(&opt_author_cols, argv[2], 0, 1024);
1983         if (!strcmp(argv[0], "horizontal-scroll"))
1984                 return parse_step(&opt_hscroll, argv[2]);
1986         if (!strcmp(argv[0], "split-view-height"))
1987                 return parse_step(&opt_scale_split_view, argv[2]);
1989         if (!strcmp(argv[0], "tab-size"))
1990                 return parse_int(&opt_tab_size, argv[2], 1, 1024);
1992         if (!strcmp(argv[0], "commit-encoding"))
1993                 return parse_string(opt_encoding, argv[2], sizeof(opt_encoding));
1995         config_msg = "Unknown variable name";
1996         return ERR;
1999 /* Wants: mode request key */
2000 static int
2001 option_bind_command(int argc, const char *argv[])
2003         enum request request;
2004         int keymap = -1;
2005         int key;
2007         if (argc < 3) {
2008                 config_msg = "Wrong number of arguments given to bind command";
2009                 return ERR;
2010         }
2012         if (!set_keymap(&keymap, argv[0])) {
2013                 config_msg = "Unknown key map";
2014                 return ERR;
2015         }
2017         key = get_key_value(argv[1]);
2018         if (key == ERR) {
2019                 config_msg = "Unknown key";
2020                 return ERR;
2021         }
2023         request = get_request(argv[2]);
2024         if (request == REQ_UNKNOWN) {
2025                 static const struct enum_map obsolete[] = {
2026                         ENUM_MAP("cherry-pick",         REQ_NONE),
2027                         ENUM_MAP("screen-resize",       REQ_NONE),
2028                         ENUM_MAP("tree-parent",         REQ_PARENT),
2029                 };
2030                 int alias;
2032                 if (map_enum(&alias, obsolete, argv[2])) {
2033                         if (alias != REQ_NONE)
2034                                 add_keybinding(keymap, alias, key);
2035                         config_msg = "Obsolete request name";
2036                         return ERR;
2037                 }
2038         }
2039         if (request == REQ_UNKNOWN && *argv[2]++ == '!')
2040                 request = add_run_request(keymap, key, argv + 2);
2041         if (request == REQ_UNKNOWN) {
2042                 config_msg = "Unknown request name";
2043                 return ERR;
2044         }
2046         add_keybinding(keymap, request, key);
2048         return OK;
2051 static int
2052 set_option(const char *opt, char *value)
2054         const char *argv[SIZEOF_ARG];
2055         int argc = 0;
2057         if (!argv_from_string(argv, &argc, value)) {
2058                 config_msg = "Too many option arguments";
2059                 return ERR;
2060         }
2062         if (!strcmp(opt, "color"))
2063                 return option_color_command(argc, argv);
2065         if (!strcmp(opt, "set"))
2066                 return option_set_command(argc, argv);
2068         if (!strcmp(opt, "bind"))
2069                 return option_bind_command(argc, argv);
2071         config_msg = "Unknown option command";
2072         return ERR;
2075 static int
2076 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
2078         int status = OK;
2080         config_lineno++;
2081         config_msg = "Internal error";
2083         /* Check for comment markers, since read_properties() will
2084          * only ensure opt and value are split at first " \t". */
2085         optlen = strcspn(opt, "#");
2086         if (optlen == 0)
2087                 return OK;
2089         if (opt[optlen] != 0) {
2090                 config_msg = "No option value";
2091                 status = ERR;
2093         }  else {
2094                 /* Look for comment endings in the value. */
2095                 size_t len = strcspn(value, "#");
2097                 if (len < valuelen) {
2098                         valuelen = len;
2099                         value[valuelen] = 0;
2100                 }
2102                 status = set_option(opt, value);
2103         }
2105         if (status == ERR) {
2106                 warn("Error on line %d, near '%.*s': %s",
2107                      config_lineno, (int) optlen, opt, config_msg);
2108                 config_errors = TRUE;
2109         }
2111         /* Always keep going if errors are encountered. */
2112         return OK;
2115 static void
2116 load_option_file(const char *path)
2118         struct io io;
2120         /* It's OK that the file doesn't exist. */
2121         if (!io_open(&io, "%s", path))
2122                 return;
2124         config_lineno = 0;
2125         config_errors = FALSE;
2127         if (io_load(&io, " \t", read_option) == ERR ||
2128             config_errors == TRUE)
2129                 warn("Errors while loading %s.", path);
2132 static int
2133 load_options(void)
2135         const char *home = getenv("HOME");
2136         const char *tigrc_user = getenv("TIGRC_USER");
2137         const char *tigrc_system = getenv("TIGRC_SYSTEM");
2138         char buf[SIZEOF_STR];
2140         if (!tigrc_system)
2141                 tigrc_system = SYSCONFDIR "/tigrc";
2142         load_option_file(tigrc_system);
2144         if (!tigrc_user) {
2145                 if (!home || !string_format(buf, "%s/.tigrc", home))
2146                         return ERR;
2147                 tigrc_user = buf;
2148         }
2149         load_option_file(tigrc_user);
2151         /* Add _after_ loading config files to avoid adding run requests
2152          * that conflict with keybindings. */
2153         add_builtin_run_requests();
2155         return OK;
2159 /*
2160  * The viewer
2161  */
2163 struct view;
2164 struct view_ops;
2166 /* The display array of active views and the index of the current view. */
2167 static struct view *display[2];
2168 static unsigned int current_view;
2170 #define foreach_displayed_view(view, i) \
2171         for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
2173 #define displayed_views()       (display[1] != NULL ? 2 : 1)
2175 /* Current head and commit ID */
2176 static char ref_blob[SIZEOF_REF]        = "";
2177 static char ref_commit[SIZEOF_REF]      = "HEAD";
2178 static char ref_head[SIZEOF_REF]        = "HEAD";
2179 static char ref_branch[SIZEOF_REF]      = "";
2181 enum view_type {
2182         VIEW_MAIN,
2183         VIEW_DIFF,
2184         VIEW_LOG,
2185         VIEW_TREE,
2186         VIEW_BLOB,
2187         VIEW_BLAME,
2188         VIEW_BRANCH,
2189         VIEW_HELP,
2190         VIEW_PAGER,
2191         VIEW_STATUS,
2192         VIEW_STAGE,
2193 };
2195 struct view {
2196         enum view_type type;    /* View type */
2197         const char *name;       /* View name */
2198         const char *cmd_env;    /* Command line set via environment */
2199         const char *id;         /* Points to either of ref_{head,commit,blob} */
2201         struct view_ops *ops;   /* View operations */
2203         enum keymap keymap;     /* What keymap does this view have */
2204         bool git_dir;           /* Whether the view requires a git directory. */
2206         char ref[SIZEOF_REF];   /* Hovered commit reference */
2207         char vid[SIZEOF_REF];   /* View ID. Set to id member when updating. */
2209         int height, width;      /* The width and height of the main window */
2210         WINDOW *win;            /* The main window */
2211         WINDOW *title;          /* The title window living below the main window */
2213         /* Navigation */
2214         unsigned long offset;   /* Offset of the window top */
2215         unsigned long yoffset;  /* Offset from the window side. */
2216         unsigned long lineno;   /* Current line number */
2217         unsigned long p_offset; /* Previous offset of the window top */
2218         unsigned long p_yoffset;/* Previous offset from the window side */
2219         unsigned long p_lineno; /* Previous current line number */
2220         bool p_restore;         /* Should the previous position be restored. */
2222         /* Searching */
2223         char grep[SIZEOF_STR];  /* Search string */
2224         regex_t *regex;         /* Pre-compiled regexp */
2226         /* If non-NULL, points to the view that opened this view. If this view
2227          * is closed tig will switch back to the parent view. */
2228         struct view *parent;
2229         struct view *prev;
2231         /* Buffering */
2232         size_t lines;           /* Total number of lines */
2233         struct line *line;      /* Line index */
2234         unsigned int digits;    /* Number of digits in the lines member. */
2236         /* Drawing */
2237         struct line *curline;   /* Line currently being drawn. */
2238         enum line_type curtype; /* Attribute currently used for drawing. */
2239         unsigned long col;      /* Column when drawing. */
2240         bool has_scrolled;      /* View was scrolled. */
2242         /* Loading */
2243         const char **argv;      /* Shell command arguments. */
2244         const char *dir;        /* Directory from which to execute. */
2245         struct io io;
2246         struct io *pipe;
2247         time_t start_time;
2248         time_t update_secs;
2249 };
2251 struct view_ops {
2252         /* What type of content being displayed. Used in the title bar. */
2253         const char *type;
2254         /* Default command arguments. */
2255         const char **argv;
2256         /* Open and reads in all view content. */
2257         bool (*open)(struct view *view);
2258         /* Read one line; updates view->line. */
2259         bool (*read)(struct view *view, char *data);
2260         /* Draw one line; @lineno must be < view->height. */
2261         bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
2262         /* Depending on view handle a special requests. */
2263         enum request (*request)(struct view *view, enum request request, struct line *line);
2264         /* Search for regexp in a line. */
2265         bool (*grep)(struct view *view, struct line *line);
2266         /* Select line */
2267         void (*select)(struct view *view, struct line *line);
2268         /* Prepare view for loading */
2269         bool (*prepare)(struct view *view);
2270 };
2272 static struct view_ops blame_ops;
2273 static struct view_ops blob_ops;
2274 static struct view_ops diff_ops;
2275 static struct view_ops help_ops;
2276 static struct view_ops log_ops;
2277 static struct view_ops main_ops;
2278 static struct view_ops pager_ops;
2279 static struct view_ops stage_ops;
2280 static struct view_ops status_ops;
2281 static struct view_ops tree_ops;
2282 static struct view_ops branch_ops;
2284 #define VIEW_STR(type, name, env, ref, ops, map, git) \
2285         { type, name, #env, ref, ops, map, git }
2287 #define VIEW_(id, name, ops, git, ref) \
2288         VIEW_STR(VIEW_##id, name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
2290 static struct view views[] = {
2291         VIEW_(MAIN,   "main",   &main_ops,   TRUE,  ref_head),
2292         VIEW_(DIFF,   "diff",   &diff_ops,   TRUE,  ref_commit),
2293         VIEW_(LOG,    "log",    &log_ops,    TRUE,  ref_head),
2294         VIEW_(TREE,   "tree",   &tree_ops,   TRUE,  ref_commit),
2295         VIEW_(BLOB,   "blob",   &blob_ops,   TRUE,  ref_blob),
2296         VIEW_(BLAME,  "blame",  &blame_ops,  TRUE,  ref_commit),
2297         VIEW_(BRANCH, "branch", &branch_ops, TRUE,  ref_head),
2298         VIEW_(HELP,   "help",   &help_ops,   FALSE, ""),
2299         VIEW_(PAGER,  "pager",  &pager_ops,  FALSE, "stdin"),
2300         VIEW_(STATUS, "status", &status_ops, TRUE,  ""),
2301         VIEW_(STAGE,  "stage",  &stage_ops,  TRUE,  ""),
2302 };
2304 #define VIEW(req)       (&views[(req) - REQ_OFFSET - 1])
2306 #define foreach_view(view, i) \
2307         for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
2309 #define view_is_displayed(view) \
2310         (view == display[0] || view == display[1])
2312 static enum request
2313 view_request(struct view *view, enum request request)
2315         if (!view || !view->lines)
2316                 return request;
2317         return view->ops->request(view, request, &view->line[view->lineno]);
2321 /*
2322  * View drawing.
2323  */
2325 static inline void
2326 set_view_attr(struct view *view, enum line_type type)
2328         if (!view->curline->selected && view->curtype != type) {
2329                 (void) wattrset(view->win, get_line_attr(type));
2330                 wchgat(view->win, -1, 0, type, NULL);
2331                 view->curtype = type;
2332         }
2335 static int
2336 draw_chars(struct view *view, enum line_type type, const char *string,
2337            int max_len, bool use_tilde)
2339         static char out_buffer[BUFSIZ * 2];
2340         int len = 0;
2341         int col = 0;
2342         int trimmed = FALSE;
2343         size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2345         if (max_len <= 0)
2346                 return 0;
2348         len = utf8_length(&string, skip, &col, max_len, &trimmed, use_tilde, opt_tab_size);
2350         set_view_attr(view, type);
2351         if (len > 0) {
2352                 if (opt_iconv_out != ICONV_NONE) {
2353                         ICONV_CONST char *inbuf = (ICONV_CONST char *) string;
2354                         size_t inlen = len + 1;
2356                         char *outbuf = out_buffer;
2357                         size_t outlen = sizeof(out_buffer);
2359                         size_t ret;
2361                         ret = iconv(opt_iconv_out, &inbuf, &inlen, &outbuf, &outlen);
2362                         if (ret != (size_t) -1) {
2363                                 string = out_buffer;
2364                                 len = sizeof(out_buffer) - outlen;
2365                         }
2366                 }
2368                 waddnstr(view->win, string, len);
2369         }
2370         if (trimmed && use_tilde) {
2371                 set_view_attr(view, LINE_DELIMITER);
2372                 waddch(view->win, '~');
2373                 col++;
2374         }
2376         return col;
2379 static int
2380 draw_space(struct view *view, enum line_type type, int max, int spaces)
2382         static char space[] = "                    ";
2383         int col = 0;
2385         spaces = MIN(max, spaces);
2387         while (spaces > 0) {
2388                 int len = MIN(spaces, sizeof(space) - 1);
2390                 col += draw_chars(view, type, space, len, FALSE);
2391                 spaces -= len;
2392         }
2394         return col;
2397 static bool
2398 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
2400         char text[SIZEOF_STR];
2402         do {
2403                 size_t pos = string_expand(text, sizeof(text), string, opt_tab_size);
2405                 view->col += draw_chars(view, type, text, view->width + view->yoffset - view->col, trim);
2406                 string += pos;
2407         } while (*string && view->width + view->yoffset > view->col);
2409         return view->width + view->yoffset <= view->col;
2412 static bool
2413 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
2415         size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2416         int max = view->width + view->yoffset - view->col;
2417         int i;
2419         if (max < size)
2420                 size = max;
2422         set_view_attr(view, type);
2423         /* Using waddch() instead of waddnstr() ensures that
2424          * they'll be rendered correctly for the cursor line. */
2425         for (i = skip; i < size; i++)
2426                 waddch(view->win, graphic[i]);
2428         view->col += size;
2429         if (size < max && skip <= size)
2430                 waddch(view->win, ' ');
2431         view->col++;
2433         return view->width + view->yoffset <= view->col;
2436 static bool
2437 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
2439         int max = MIN(view->width + view->yoffset - view->col, len);
2440         int col;
2442         if (text)
2443                 col = draw_chars(view, type, text, max - 1, trim);
2444         else
2445                 col = draw_space(view, type, max - 1, max - 1);
2447         view->col += col;
2448         view->col += draw_space(view, LINE_DEFAULT, max - col, max - col);
2449         return view->width + view->yoffset <= view->col;
2452 static bool
2453 draw_date(struct view *view, struct time *time)
2455         const char *date = mkdate(time, opt_date);
2456         int cols = opt_date == DATE_SHORT ? DATE_SHORT_COLS : DATE_COLS;
2458         return draw_field(view, LINE_DATE, date, cols, FALSE);
2461 static bool
2462 draw_author(struct view *view, const char *author)
2464         bool trim = opt_author_cols == 0 || opt_author_cols > 5;
2465         bool abbreviate = opt_author == AUTHOR_ABBREVIATED || !trim;
2467         if (abbreviate && author)
2468                 author = get_author_initials(author);
2470         return draw_field(view, LINE_AUTHOR, author, opt_author_cols, trim);
2473 static bool
2474 draw_mode(struct view *view, mode_t mode)
2476         const char *str;
2478         if (S_ISDIR(mode))
2479                 str = "drwxr-xr-x";
2480         else if (S_ISLNK(mode))
2481                 str = "lrwxrwxrwx";
2482         else if (S_ISGITLINK(mode))
2483                 str = "m---------";
2484         else if (S_ISREG(mode) && mode & S_IXUSR)
2485                 str = "-rwxr-xr-x";
2486         else if (S_ISREG(mode))
2487                 str = "-rw-r--r--";
2488         else
2489                 str = "----------";
2491         return draw_field(view, LINE_MODE, str, STRING_SIZE("-rw-r--r-- "), FALSE);
2494 static bool
2495 draw_lineno(struct view *view, unsigned int lineno)
2497         char number[10];
2498         int digits3 = view->digits < 3 ? 3 : view->digits;
2499         int max = MIN(view->width + view->yoffset - view->col, digits3);
2500         char *text = NULL;
2501         chtype separator = opt_line_graphics ? ACS_VLINE : '|';
2503         lineno += view->offset + 1;
2504         if (lineno == 1 || (lineno % opt_num_interval) == 0) {
2505                 static char fmt[] = "%1ld";
2507                 fmt[1] = '0' + (view->digits <= 9 ? digits3 : 1);
2508                 if (string_format(number, fmt, lineno))
2509                         text = number;
2510         }
2511         if (text)
2512                 view->col += draw_chars(view, LINE_LINE_NUMBER, text, max, TRUE);
2513         else
2514                 view->col += draw_space(view, LINE_LINE_NUMBER, max, digits3);
2515         return draw_graphic(view, LINE_DEFAULT, &separator, 1);
2518 static bool
2519 draw_view_line(struct view *view, unsigned int lineno)
2521         struct line *line;
2522         bool selected = (view->offset + lineno == view->lineno);
2524         assert(view_is_displayed(view));
2526         if (view->offset + lineno >= view->lines)
2527                 return FALSE;
2529         line = &view->line[view->offset + lineno];
2531         wmove(view->win, lineno, 0);
2532         if (line->cleareol)
2533                 wclrtoeol(view->win);
2534         view->col = 0;
2535         view->curline = line;
2536         view->curtype = LINE_NONE;
2537         line->selected = FALSE;
2538         line->dirty = line->cleareol = 0;
2540         if (selected) {
2541                 set_view_attr(view, LINE_CURSOR);
2542                 line->selected = TRUE;
2543                 view->ops->select(view, line);
2544         }
2546         return view->ops->draw(view, line, lineno);
2549 static void
2550 redraw_view_dirty(struct view *view)
2552         bool dirty = FALSE;
2553         int lineno;
2555         for (lineno = 0; lineno < view->height; lineno++) {
2556                 if (view->offset + lineno >= view->lines)
2557                         break;
2558                 if (!view->line[view->offset + lineno].dirty)
2559                         continue;
2560                 dirty = TRUE;
2561                 if (!draw_view_line(view, lineno))
2562                         break;
2563         }
2565         if (!dirty)
2566                 return;
2567         wnoutrefresh(view->win);
2570 static void
2571 redraw_view_from(struct view *view, int lineno)
2573         assert(0 <= lineno && lineno < view->height);
2575         for (; lineno < view->height; lineno++) {
2576                 if (!draw_view_line(view, lineno))
2577                         break;
2578         }
2580         wnoutrefresh(view->win);
2583 static void
2584 redraw_view(struct view *view)
2586         werase(view->win);
2587         redraw_view_from(view, 0);
2591 static void
2592 update_view_title(struct view *view)
2594         char buf[SIZEOF_STR];
2595         char state[SIZEOF_STR];
2596         size_t bufpos = 0, statelen = 0;
2598         assert(view_is_displayed(view));
2600         if (view->type != VIEW_STATUS && view->lines) {
2601                 unsigned int view_lines = view->offset + view->height;
2602                 unsigned int lines = view->lines
2603                                    ? MIN(view_lines, view->lines) * 100 / view->lines
2604                                    : 0;
2606                 string_format_from(state, &statelen, " - %s %d of %d (%d%%)",
2607                                    view->ops->type,
2608                                    view->lineno + 1,
2609                                    view->lines,
2610                                    lines);
2612         }
2614         if (view->pipe) {
2615                 time_t secs = time(NULL) - view->start_time;
2617                 /* Three git seconds are a long time ... */
2618                 if (secs > 2)
2619                         string_format_from(state, &statelen, " loading %lds", secs);
2620         }
2622         string_format_from(buf, &bufpos, "[%s]", view->name);
2623         if (*view->ref && bufpos < view->width) {
2624                 size_t refsize = strlen(view->ref);
2625                 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2627                 if (minsize < view->width)
2628                         refsize = view->width - minsize + 7;
2629                 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2630         }
2632         if (statelen && bufpos < view->width) {
2633                 string_format_from(buf, &bufpos, "%s", state);
2634         }
2636         if (view == display[current_view])
2637                 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2638         else
2639                 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2641         mvwaddnstr(view->title, 0, 0, buf, bufpos);
2642         wclrtoeol(view->title);
2643         wnoutrefresh(view->title);
2646 static int
2647 apply_step(double step, int value)
2649         if (step >= 1)
2650                 return (int) step;
2651         value *= step + 0.01;
2652         return value ? value : 1;
2655 static void
2656 resize_display(void)
2658         int offset, i;
2659         struct view *base = display[0];
2660         struct view *view = display[1] ? display[1] : display[0];
2662         /* Setup window dimensions */
2664         getmaxyx(stdscr, base->height, base->width);
2666         /* Make room for the status window. */
2667         base->height -= 1;
2669         if (view != base) {
2670                 /* Horizontal split. */
2671                 view->width   = base->width;
2672                 view->height  = apply_step(opt_scale_split_view, base->height);
2673                 view->height  = MAX(view->height, MIN_VIEW_HEIGHT);
2674                 view->height  = MIN(view->height, base->height - MIN_VIEW_HEIGHT);
2675                 base->height -= view->height;
2677                 /* Make room for the title bar. */
2678                 view->height -= 1;
2679         }
2681         /* Make room for the title bar. */
2682         base->height -= 1;
2684         offset = 0;
2686         foreach_displayed_view (view, i) {
2687                 if (!view->win) {
2688                         view->win = newwin(view->height, 0, offset, 0);
2689                         if (!view->win)
2690                                 die("Failed to create %s view", view->name);
2692                         scrollok(view->win, FALSE);
2694                         view->title = newwin(1, 0, offset + view->height, 0);
2695                         if (!view->title)
2696                                 die("Failed to create title window");
2698                 } else {
2699                         wresize(view->win, view->height, view->width);
2700                         mvwin(view->win,   offset, 0);
2701                         mvwin(view->title, offset + view->height, 0);
2702                 }
2704                 offset += view->height + 1;
2705         }
2708 static void
2709 redraw_display(bool clear)
2711         struct view *view;
2712         int i;
2714         foreach_displayed_view (view, i) {
2715                 if (clear)
2716                         wclear(view->win);
2717                 redraw_view(view);
2718                 update_view_title(view);
2719         }
2723 /*
2724  * Option management
2725  */
2727 static void
2728 toggle_enum_option_do(unsigned int *opt, const char *help,
2729                       const struct enum_map *map, size_t size)
2731         *opt = (*opt + 1) % size;
2732         redraw_display(FALSE);
2733         report("Displaying %s %s", enum_name(map[*opt]), help);
2736 #define toggle_enum_option(opt, help, map) \
2737         toggle_enum_option_do(opt, help, map, ARRAY_SIZE(map))
2739 #define toggle_date() toggle_enum_option(&opt_date, "dates", date_map)
2740 #define toggle_author() toggle_enum_option(&opt_author, "author names", author_map)
2742 static void
2743 toggle_view_option(bool *option, const char *help)
2745         *option = !*option;
2746         redraw_display(FALSE);
2747         report("%sabling %s", *option ? "En" : "Dis", help);
2750 static void
2751 open_option_menu(void)
2753         const struct menu_item menu[] = {
2754                 { '.', "line numbers", &opt_line_number },
2755                 { 'D', "date display", &opt_date },
2756                 { 'A', "author display", &opt_author },
2757                 { 'g', "revision graph display", &opt_rev_graph },
2758                 { 'F', "reference display", &opt_show_refs },
2759                 { 0 }
2760         };
2761         int selected = 0;
2763         if (prompt_menu("Toggle option", menu, &selected)) {
2764                 if (menu[selected].data == &opt_date)
2765                         toggle_date();
2766                 else if (menu[selected].data == &opt_author)
2767                         toggle_author();
2768                 else
2769                         toggle_view_option(menu[selected].data, menu[selected].text);
2770         }
2773 static void
2774 maximize_view(struct view *view)
2776         memset(display, 0, sizeof(display));
2777         current_view = 0;
2778         display[current_view] = view;
2779         resize_display();
2780         redraw_display(FALSE);
2781         report("");
2785 /*
2786  * Navigation
2787  */
2789 static bool
2790 goto_view_line(struct view *view, unsigned long offset, unsigned long lineno)
2792         if (lineno >= view->lines)
2793                 lineno = view->lines > 0 ? view->lines - 1 : 0;
2795         if (offset > lineno || offset + view->height <= lineno) {
2796                 unsigned long half = view->height / 2;
2798                 if (lineno > half)
2799                         offset = lineno - half;
2800                 else
2801                         offset = 0;
2802         }
2804         if (offset != view->offset || lineno != view->lineno) {
2805                 view->offset = offset;
2806                 view->lineno = lineno;
2807                 return TRUE;
2808         }
2810         return FALSE;
2813 /* Scrolling backend */
2814 static void
2815 do_scroll_view(struct view *view, int lines)
2817         bool redraw_current_line = FALSE;
2819         /* The rendering expects the new offset. */
2820         view->offset += lines;
2822         assert(0 <= view->offset && view->offset < view->lines);
2823         assert(lines);
2825         /* Move current line into the view. */
2826         if (view->lineno < view->offset) {
2827                 view->lineno = view->offset;
2828                 redraw_current_line = TRUE;
2829         } else if (view->lineno >= view->offset + view->height) {
2830                 view->lineno = view->offset + view->height - 1;
2831                 redraw_current_line = TRUE;
2832         }
2834         assert(view->offset <= view->lineno && view->lineno < view->lines);
2836         /* Redraw the whole screen if scrolling is pointless. */
2837         if (view->height < ABS(lines)) {
2838                 redraw_view(view);
2840         } else {
2841                 int line = lines > 0 ? view->height - lines : 0;
2842                 int end = line + ABS(lines);
2844                 scrollok(view->win, TRUE);
2845                 wscrl(view->win, lines);
2846                 scrollok(view->win, FALSE);
2848                 while (line < end && draw_view_line(view, line))
2849                         line++;
2851                 if (redraw_current_line)
2852                         draw_view_line(view, view->lineno - view->offset);
2853                 wnoutrefresh(view->win);
2854         }
2856         view->has_scrolled = TRUE;
2857         report("");
2860 /* Scroll frontend */
2861 static void
2862 scroll_view(struct view *view, enum request request)
2864         int lines = 1;
2866         assert(view_is_displayed(view));
2868         switch (request) {
2869         case REQ_SCROLL_LEFT:
2870                 if (view->yoffset == 0) {
2871                         report("Cannot scroll beyond the first column");
2872                         return;
2873                 }
2874                 if (view->yoffset <= apply_step(opt_hscroll, view->width))
2875                         view->yoffset = 0;
2876                 else
2877                         view->yoffset -= apply_step(opt_hscroll, view->width);
2878                 redraw_view_from(view, 0);
2879                 report("");
2880                 return;
2881         case REQ_SCROLL_RIGHT:
2882                 view->yoffset += apply_step(opt_hscroll, view->width);
2883                 redraw_view(view);
2884                 report("");
2885                 return;
2886         case REQ_SCROLL_PAGE_DOWN:
2887                 lines = view->height;
2888         case REQ_SCROLL_LINE_DOWN:
2889                 if (view->offset + lines > view->lines)
2890                         lines = view->lines - view->offset;
2892                 if (lines == 0 || view->offset + view->height >= view->lines) {
2893                         report("Cannot scroll beyond the last line");
2894                         return;
2895                 }
2896                 break;
2898         case REQ_SCROLL_PAGE_UP:
2899                 lines = view->height;
2900         case REQ_SCROLL_LINE_UP:
2901                 if (lines > view->offset)
2902                         lines = view->offset;
2904                 if (lines == 0) {
2905                         report("Cannot scroll beyond the first line");
2906                         return;
2907                 }
2909                 lines = -lines;
2910                 break;
2912         default:
2913                 die("request %d not handled in switch", request);
2914         }
2916         do_scroll_view(view, lines);
2919 /* Cursor moving */
2920 static void
2921 move_view(struct view *view, enum request request)
2923         int scroll_steps = 0;
2924         int steps;
2926         switch (request) {
2927         case REQ_MOVE_FIRST_LINE:
2928                 steps = -view->lineno;
2929                 break;
2931         case REQ_MOVE_LAST_LINE:
2932                 steps = view->lines - view->lineno - 1;
2933                 break;
2935         case REQ_MOVE_PAGE_UP:
2936                 steps = view->height > view->lineno
2937                       ? -view->lineno : -view->height;
2938                 break;
2940         case REQ_MOVE_PAGE_DOWN:
2941                 steps = view->lineno + view->height >= view->lines
2942                       ? view->lines - view->lineno - 1 : view->height;
2943                 break;
2945         case REQ_MOVE_UP:
2946                 steps = -1;
2947                 break;
2949         case REQ_MOVE_DOWN:
2950                 steps = 1;
2951                 break;
2953         default:
2954                 die("request %d not handled in switch", request);
2955         }
2957         if (steps <= 0 && view->lineno == 0) {
2958                 report("Cannot move beyond the first line");
2959                 return;
2961         } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2962                 report("Cannot move beyond the last line");
2963                 return;
2964         }
2966         /* Move the current line */
2967         view->lineno += steps;
2968         assert(0 <= view->lineno && view->lineno < view->lines);
2970         /* Check whether the view needs to be scrolled */
2971         if (view->lineno < view->offset ||
2972             view->lineno >= view->offset + view->height) {
2973                 scroll_steps = steps;
2974                 if (steps < 0 && -steps > view->offset) {
2975                         scroll_steps = -view->offset;
2977                 } else if (steps > 0) {
2978                         if (view->lineno == view->lines - 1 &&
2979                             view->lines > view->height) {
2980                                 scroll_steps = view->lines - view->offset - 1;
2981                                 if (scroll_steps >= view->height)
2982                                         scroll_steps -= view->height - 1;
2983                         }
2984                 }
2985         }
2987         if (!view_is_displayed(view)) {
2988                 view->offset += scroll_steps;
2989                 assert(0 <= view->offset && view->offset < view->lines);
2990                 view->ops->select(view, &view->line[view->lineno]);
2991                 return;
2992         }
2994         /* Repaint the old "current" line if we be scrolling */
2995         if (ABS(steps) < view->height)
2996                 draw_view_line(view, view->lineno - steps - view->offset);
2998         if (scroll_steps) {
2999                 do_scroll_view(view, scroll_steps);
3000                 return;
3001         }
3003         /* Draw the current line */
3004         draw_view_line(view, view->lineno - view->offset);
3006         wnoutrefresh(view->win);
3007         report("");
3011 /*
3012  * Searching
3013  */
3015 static void search_view(struct view *view, enum request request);
3017 static bool
3018 grep_text(struct view *view, const char *text[])
3020         regmatch_t pmatch;
3021         size_t i;
3023         for (i = 0; text[i]; i++)
3024                 if (*text[i] &&
3025                     regexec(view->regex, text[i], 1, &pmatch, 0) != REG_NOMATCH)
3026                         return TRUE;
3027         return FALSE;
3030 static void
3031 select_view_line(struct view *view, unsigned long lineno)
3033         unsigned long old_lineno = view->lineno;
3034         unsigned long old_offset = view->offset;
3036         if (goto_view_line(view, view->offset, lineno)) {
3037                 if (view_is_displayed(view)) {
3038                         if (old_offset != view->offset) {
3039                                 redraw_view(view);
3040                         } else {
3041                                 draw_view_line(view, old_lineno - view->offset);
3042                                 draw_view_line(view, view->lineno - view->offset);
3043                                 wnoutrefresh(view->win);
3044                         }
3045                 } else {
3046                         view->ops->select(view, &view->line[view->lineno]);
3047                 }
3048         }
3051 static void
3052 find_next(struct view *view, enum request request)
3054         unsigned long lineno = view->lineno;
3055         int direction;
3057         if (!*view->grep) {
3058                 if (!*opt_search)
3059                         report("No previous search");
3060                 else
3061                         search_view(view, request);
3062                 return;
3063         }
3065         switch (request) {
3066         case REQ_SEARCH:
3067         case REQ_FIND_NEXT:
3068                 direction = 1;
3069                 break;
3071         case REQ_SEARCH_BACK:
3072         case REQ_FIND_PREV:
3073                 direction = -1;
3074                 break;
3076         default:
3077                 return;
3078         }
3080         if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
3081                 lineno += direction;
3083         /* Note, lineno is unsigned long so will wrap around in which case it
3084          * will become bigger than view->lines. */
3085         for (; lineno < view->lines; lineno += direction) {
3086                 if (view->ops->grep(view, &view->line[lineno])) {
3087                         select_view_line(view, lineno);
3088                         report("Line %ld matches '%s'", lineno + 1, view->grep);
3089                         return;
3090                 }
3091         }
3093         report("No match found for '%s'", view->grep);
3096 static void
3097 search_view(struct view *view, enum request request)
3099         int regex_err;
3101         if (view->regex) {
3102                 regfree(view->regex);
3103                 *view->grep = 0;
3104         } else {
3105                 view->regex = calloc(1, sizeof(*view->regex));
3106                 if (!view->regex)
3107                         return;
3108         }
3110         regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
3111         if (regex_err != 0) {
3112                 char buf[SIZEOF_STR] = "unknown error";
3114                 regerror(regex_err, view->regex, buf, sizeof(buf));
3115                 report("Search failed: %s", buf);
3116                 return;
3117         }
3119         string_copy(view->grep, opt_search);
3121         find_next(view, request);
3124 /*
3125  * Incremental updating
3126  */
3128 static void
3129 reset_view(struct view *view)
3131         int i;
3133         for (i = 0; i < view->lines; i++)
3134                 free(view->line[i].data);
3135         free(view->line);
3137         view->p_offset = view->offset;
3138         view->p_yoffset = view->yoffset;
3139         view->p_lineno = view->lineno;
3141         view->line = NULL;
3142         view->offset = 0;
3143         view->yoffset = 0;
3144         view->lines  = 0;
3145         view->lineno = 0;
3146         view->vid[0] = 0;
3147         view->update_secs = 0;
3150 static const char *
3151 format_arg(const char *name)
3153         static struct {
3154                 const char *name;
3155                 size_t namelen;
3156                 const char *value;
3157                 const char *value_if_empty;
3158         } vars[] = {
3159 #define FORMAT_VAR(name, value, value_if_empty) \
3160         { name, STRING_SIZE(name), value, value_if_empty }
3161                 FORMAT_VAR("%(directory)",      opt_path,       ""),
3162                 FORMAT_VAR("%(file)",           opt_file,       ""),
3163                 FORMAT_VAR("%(ref)",            opt_ref,        "HEAD"),
3164                 FORMAT_VAR("%(head)",           ref_head,       ""),
3165                 FORMAT_VAR("%(commit)",         ref_commit,     ""),
3166                 FORMAT_VAR("%(blob)",           ref_blob,       ""),
3167                 FORMAT_VAR("%(branch)",         ref_branch,     ""),
3168         };
3169         int i;
3171         for (i = 0; i < ARRAY_SIZE(vars); i++)
3172                 if (!strncmp(name, vars[i].name, vars[i].namelen))
3173                         return *vars[i].value ? vars[i].value : vars[i].value_if_empty;
3175         report("Unknown replacement: `%s`", name);
3176         return NULL;
3179 static bool
3180 format_argv(const char ***dst_argv, const char *src_argv[], bool replace)
3182         char buf[SIZEOF_STR];
3183         int argc;
3185         argv_free(*dst_argv);
3187         for (argc = 0; src_argv[argc]; argc++) {
3188                 const char *arg = src_argv[argc];
3189                 size_t bufpos = 0;
3191                 if (!strcmp(arg, "%(file-args)")) {
3192                         if (!argv_append_array(dst_argv, opt_file_args))
3193                                 break;
3194                         continue;
3196                 } else if (!strcmp(arg, "%(diff-args)")) {
3197                         if (!argv_append_array(dst_argv, opt_diff_args))
3198                                 break;
3199                         continue;
3201                 } else if (!strcmp(arg, "%(rev-args)")) {
3202                         if (!argv_append_array(dst_argv, opt_rev_args))
3203                                 break;
3204                         continue;
3205                 }
3207                 while (arg) {
3208                         char *next = strstr(arg, "%(");
3209                         int len = next - arg;
3210                         const char *value;
3212                         if (!next || !replace) {
3213                                 len = strlen(arg);
3214                                 value = "";
3216                         } else {
3217                                 value = format_arg(next);
3219                                 if (!value) {
3220                                         return FALSE;
3221                                 }
3222                         }
3224                         if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
3225                                 return FALSE;
3227                         arg = next && replace ? strchr(next, ')') + 1 : NULL;
3228                 }
3230                 if (!argv_append(dst_argv, buf))
3231                         break;
3232         }
3234         return src_argv[argc] == NULL;
3237 static bool
3238 restore_view_position(struct view *view)
3240         if (!view->p_restore || (view->pipe && view->lines <= view->p_lineno))
3241                 return FALSE;
3243         /* Changing the view position cancels the restoring. */
3244         /* FIXME: Changing back to the first line is not detected. */
3245         if (view->offset != 0 || view->lineno != 0) {
3246                 view->p_restore = FALSE;
3247                 return FALSE;
3248         }
3250         if (goto_view_line(view, view->p_offset, view->p_lineno) &&
3251             view_is_displayed(view))
3252                 werase(view->win);
3254         view->yoffset = view->p_yoffset;
3255         view->p_restore = FALSE;
3257         return TRUE;
3260 static void
3261 end_update(struct view *view, bool force)
3263         if (!view->pipe)
3264                 return;
3265         while (!view->ops->read(view, NULL))
3266                 if (!force)
3267                         return;
3268         if (force)
3269                 io_kill(view->pipe);
3270         io_done(view->pipe);
3271         view->pipe = NULL;
3274 static void
3275 setup_update(struct view *view, const char *vid)
3277         reset_view(view);
3278         string_copy_rev(view->vid, vid);
3279         view->pipe = &view->io;
3280         view->start_time = time(NULL);
3283 static bool
3284 prepare_io(struct view *view, const char *dir, const char *argv[], bool replace)
3286         view->dir = dir;
3287         return format_argv(&view->argv, argv, replace);
3290 static bool
3291 prepare_update(struct view *view, const char *argv[], const char *dir)
3293         if (view->pipe)
3294                 end_update(view, TRUE);
3295         return prepare_io(view, dir, argv, FALSE);
3298 static bool
3299 start_update(struct view *view, const char **argv, const char *dir)
3301         if (view->pipe)
3302                 io_done(view->pipe);
3303         return prepare_io(view, dir, argv, FALSE) &&
3304                io_run(&view->io, IO_RD, dir, view->argv);
3307 static bool
3308 prepare_update_file(struct view *view, const char *name)
3310         if (view->pipe)
3311                 end_update(view, TRUE);
3312         argv_free(view->argv);
3313         return io_open(&view->io, "%s/%s", opt_cdup[0] ? opt_cdup : ".", name);
3316 static bool
3317 begin_update(struct view *view, bool refresh)
3319         if (view->pipe)
3320                 end_update(view, TRUE);
3322         if (!refresh) {
3323                 if (view->ops->prepare) {
3324                         if (!view->ops->prepare(view))
3325                                 return FALSE;
3326                 } else if (!prepare_io(view, NULL, view->ops->argv, TRUE)) {
3327                         return FALSE;
3328                 }
3330                 /* Put the current ref_* value to the view title ref
3331                  * member. This is needed by the blob view. Most other
3332                  * views sets it automatically after loading because the
3333                  * first line is a commit line. */
3334                 string_copy_rev(view->ref, view->id);
3335         }
3337         if (view->argv && view->argv[0] &&
3338             !io_run(&view->io, IO_RD, view->dir, view->argv))
3339                 return FALSE;
3341         setup_update(view, view->id);
3343         return TRUE;
3346 static bool
3347 update_view(struct view *view)
3349         char out_buffer[BUFSIZ * 2];
3350         char *line;
3351         /* Clear the view and redraw everything since the tree sorting
3352          * might have rearranged things. */
3353         bool redraw = view->lines == 0;
3354         bool can_read = TRUE;
3356         if (!view->pipe)
3357                 return TRUE;
3359         if (!io_can_read(view->pipe)) {
3360                 if (view->lines == 0 && view_is_displayed(view)) {
3361                         time_t secs = time(NULL) - view->start_time;
3363                         if (secs > 1 && secs > view->update_secs) {
3364                                 if (view->update_secs == 0)
3365                                         redraw_view(view);
3366                                 update_view_title(view);
3367                                 view->update_secs = secs;
3368                         }
3369                 }
3370                 return TRUE;
3371         }
3373         for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
3374                 if (opt_iconv_in != ICONV_NONE) {
3375                         ICONV_CONST char *inbuf = line;
3376                         size_t inlen = strlen(line) + 1;
3378                         char *outbuf = out_buffer;
3379                         size_t outlen = sizeof(out_buffer);
3381                         size_t ret;
3383                         ret = iconv(opt_iconv_in, &inbuf, &inlen, &outbuf, &outlen);
3384                         if (ret != (size_t) -1)
3385                                 line = out_buffer;
3386                 }
3388                 if (!view->ops->read(view, line)) {
3389                         report("Allocation failure");
3390                         end_update(view, TRUE);
3391                         return FALSE;
3392                 }
3393         }
3395         {
3396                 unsigned long lines = view->lines;
3397                 int digits;
3399                 for (digits = 0; lines; digits++)
3400                         lines /= 10;
3402                 /* Keep the displayed view in sync with line number scaling. */
3403                 if (digits != view->digits) {
3404                         view->digits = digits;
3405                         if (opt_line_number || view->type == VIEW_BLAME)
3406                                 redraw = TRUE;
3407                 }
3408         }
3410         if (io_error(view->pipe)) {
3411                 report("Failed to read: %s", io_strerror(view->pipe));
3412                 end_update(view, TRUE);
3414         } else if (io_eof(view->pipe)) {
3415                 if (view_is_displayed(view))
3416                         report("");
3417                 end_update(view, FALSE);
3418         }
3420         if (restore_view_position(view))
3421                 redraw = TRUE;
3423         if (!view_is_displayed(view))
3424                 return TRUE;
3426         if (redraw)
3427                 redraw_view_from(view, 0);
3428         else
3429                 redraw_view_dirty(view);
3431         /* Update the title _after_ the redraw so that if the redraw picks up a
3432          * commit reference in view->ref it'll be available here. */
3433         update_view_title(view);
3434         return TRUE;
3437 DEFINE_ALLOCATOR(realloc_lines, struct line, 256)
3439 static struct line *
3440 add_line_data(struct view *view, void *data, enum line_type type)
3442         struct line *line;
3444         if (!realloc_lines(&view->line, view->lines, 1))
3445                 return NULL;
3447         line = &view->line[view->lines++];
3448         memset(line, 0, sizeof(*line));
3449         line->type = type;
3450         line->data = data;
3451         line->dirty = 1;
3453         return line;
3456 static struct line *
3457 add_line_text(struct view *view, const char *text, enum line_type type)
3459         char *data = text ? strdup(text) : NULL;
3461         return data ? add_line_data(view, data, type) : NULL;
3464 static struct line *
3465 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
3467         char buf[SIZEOF_STR];
3468         va_list args;
3470         va_start(args, fmt);
3471         if (vsnprintf(buf, sizeof(buf), fmt, args) >= sizeof(buf))
3472                 buf[0] = 0;
3473         va_end(args);
3475         return buf[0] ? add_line_text(view, buf, type) : NULL;
3478 /*
3479  * View opening
3480  */
3482 enum open_flags {
3483         OPEN_DEFAULT = 0,       /* Use default view switching. */
3484         OPEN_SPLIT = 1,         /* Split current view. */
3485         OPEN_RELOAD = 4,        /* Reload view even if it is the current. */
3486         OPEN_REFRESH = 16,      /* Refresh view using previous command. */
3487         OPEN_PREPARED = 32,     /* Open already prepared command. */
3488 };
3490 static void
3491 open_view(struct view *prev, enum request request, enum open_flags flags)
3493         bool split = !!(flags & OPEN_SPLIT);
3494         bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
3495         bool nomaximize = !!(flags & OPEN_REFRESH);
3496         struct view *view = VIEW(request);
3497         int nviews = displayed_views();
3498         struct view *base_view = display[0];
3500         if (view == prev && nviews == 1 && !reload) {
3501                 report("Already in %s view", view->name);
3502                 return;
3503         }
3505         if (view->git_dir && !opt_git_dir[0]) {
3506                 report("The %s view is disabled in pager view", view->name);
3507                 return;
3508         }
3510         if (split) {
3511                 display[1] = view;
3512                 current_view = 1;
3513                 view->parent = prev;
3514         } else if (!nomaximize) {
3515                 /* Maximize the current view. */
3516                 memset(display, 0, sizeof(display));
3517                 current_view = 0;
3518                 display[current_view] = view;
3519         }
3521         /* No prev signals that this is the first loaded view. */
3522         if (prev && view != prev) {
3523                 view->prev = prev;
3524         }
3526         /* Resize the view when switching between split- and full-screen,
3527          * or when switching between two different full-screen views. */
3528         if (nviews != displayed_views() ||
3529             (nviews == 1 && base_view != display[0]))
3530                 resize_display();
3532         if (view->ops->open) {
3533                 if (view->pipe)
3534                         end_update(view, TRUE);
3535                 if (!view->ops->open(view)) {
3536                         report("Failed to load %s view", view->name);
3537                         return;
3538                 }
3539                 restore_view_position(view);
3541         } else if ((reload || strcmp(view->vid, view->id)) &&
3542                    !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
3543                 report("Failed to load %s view", view->name);
3544                 return;
3545         }
3547         if (split && prev->lineno - prev->offset >= prev->height) {
3548                 /* Take the title line into account. */
3549                 int lines = prev->lineno - prev->offset - prev->height + 1;
3551                 /* Scroll the view that was split if the current line is
3552                  * outside the new limited view. */
3553                 do_scroll_view(prev, lines);
3554         }
3556         if (prev && view != prev && split && view_is_displayed(prev)) {
3557                 /* "Blur" the previous view. */
3558                 update_view_title(prev);
3559         }
3561         if (view->pipe && view->lines == 0) {
3562                 /* Clear the old view and let the incremental updating refill
3563                  * the screen. */
3564                 werase(view->win);
3565                 view->p_restore = flags & (OPEN_RELOAD | OPEN_REFRESH);
3566                 report("");
3567         } else if (view_is_displayed(view)) {
3568                 redraw_view(view);
3569                 report("");
3570         }
3573 static void
3574 open_external_viewer(const char *argv[], const char *dir)
3576         def_prog_mode();           /* save current tty modes */
3577         endwin();                  /* restore original tty modes */
3578         io_run_fg(argv, dir);
3579         fprintf(stderr, "Press Enter to continue");
3580         getc(opt_tty);
3581         reset_prog_mode();
3582         redraw_display(TRUE);
3585 static void
3586 open_mergetool(const char *file)
3588         const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
3590         open_external_viewer(mergetool_argv, opt_cdup);
3593 static void
3594 open_editor(const char *file)
3596         const char *editor_argv[] = { "vi", file, NULL };
3597         const char *editor;
3599         editor = getenv("GIT_EDITOR");
3600         if (!editor && *opt_editor)
3601                 editor = opt_editor;
3602         if (!editor)
3603                 editor = getenv("VISUAL");
3604         if (!editor)
3605                 editor = getenv("EDITOR");
3606         if (!editor)
3607                 editor = "vi";
3609         editor_argv[0] = editor;
3610         open_external_viewer(editor_argv, opt_cdup);
3613 static void
3614 open_run_request(enum request request)
3616         struct run_request *req = get_run_request(request);
3617         const char **argv = NULL;
3619         if (!req) {
3620                 report("Unknown run request");
3621                 return;
3622         }
3624         if (format_argv(&argv, req->argv, TRUE))
3625                 open_external_viewer(argv, NULL);
3626         if (argv)
3627                 argv_free(argv);
3628         free(argv);
3631 /*
3632  * User request switch noodle
3633  */
3635 static int
3636 view_driver(struct view *view, enum request request)
3638         int i;
3640         if (request == REQ_NONE)
3641                 return TRUE;
3643         if (request > REQ_NONE) {
3644                 open_run_request(request);
3645                 view_request(view, REQ_REFRESH);
3646                 return TRUE;
3647         }
3649         request = view_request(view, request);
3650         if (request == REQ_NONE)
3651                 return TRUE;
3653         switch (request) {
3654         case REQ_MOVE_UP:
3655         case REQ_MOVE_DOWN:
3656         case REQ_MOVE_PAGE_UP:
3657         case REQ_MOVE_PAGE_DOWN:
3658         case REQ_MOVE_FIRST_LINE:
3659         case REQ_MOVE_LAST_LINE:
3660                 move_view(view, request);
3661                 break;
3663         case REQ_SCROLL_LEFT:
3664         case REQ_SCROLL_RIGHT:
3665         case REQ_SCROLL_LINE_DOWN:
3666         case REQ_SCROLL_LINE_UP:
3667         case REQ_SCROLL_PAGE_DOWN:
3668         case REQ_SCROLL_PAGE_UP:
3669                 scroll_view(view, request);
3670                 break;
3672         case REQ_VIEW_BLAME:
3673                 if (!opt_file[0]) {
3674                         report("No file chosen, press %s to open tree view",
3675                                get_key(view->keymap, REQ_VIEW_TREE));
3676                         break;
3677                 }
3678                 open_view(view, request, OPEN_DEFAULT);
3679                 break;
3681         case REQ_VIEW_BLOB:
3682                 if (!ref_blob[0]) {
3683                         report("No file chosen, press %s to open tree view",
3684                                get_key(view->keymap, REQ_VIEW_TREE));
3685                         break;
3686                 }
3687                 open_view(view, request, OPEN_DEFAULT);
3688                 break;
3690         case REQ_VIEW_PAGER:
3691                 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3692                         report("No pager content, press %s to run command from prompt",
3693                                get_key(view->keymap, REQ_PROMPT));
3694                         break;
3695                 }
3696                 open_view(view, request, OPEN_DEFAULT);
3697                 break;
3699         case REQ_VIEW_STAGE:
3700                 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3701                         report("No stage content, press %s to open the status view and choose file",
3702                                get_key(view->keymap, REQ_VIEW_STATUS));
3703                         break;
3704                 }
3705                 open_view(view, request, OPEN_DEFAULT);
3706                 break;
3708         case REQ_VIEW_STATUS:
3709                 if (opt_is_inside_work_tree == FALSE) {
3710                         report("The status view requires a working tree");
3711                         break;
3712                 }
3713                 open_view(view, request, OPEN_DEFAULT);
3714                 break;
3716         case REQ_VIEW_MAIN:
3717         case REQ_VIEW_DIFF:
3718         case REQ_VIEW_LOG:
3719         case REQ_VIEW_TREE:
3720         case REQ_VIEW_HELP:
3721         case REQ_VIEW_BRANCH:
3722                 open_view(view, request, OPEN_DEFAULT);
3723                 break;
3725         case REQ_NEXT:
3726         case REQ_PREVIOUS:
3727                 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3729                 if (view->parent) {
3730                         int line;
3732                         view = view->parent;
3733                         line = view->lineno;
3734                         move_view(view, request);
3735                         if (view_is_displayed(view))
3736                                 update_view_title(view);
3737                         if (line != view->lineno)
3738                                 view_request(view, REQ_ENTER);
3739                 } else {
3740                         move_view(view, request);
3741                 }
3742                 break;
3744         case REQ_VIEW_NEXT:
3745         {
3746                 int nviews = displayed_views();
3747                 int next_view = (current_view + 1) % nviews;
3749                 if (next_view == current_view) {
3750                         report("Only one view is displayed");
3751                         break;
3752                 }
3754                 current_view = next_view;
3755                 /* Blur out the title of the previous view. */
3756                 update_view_title(view);
3757                 report("");
3758                 break;
3759         }
3760         case REQ_REFRESH:
3761                 report("Refreshing is not yet supported for the %s view", view->name);
3762                 break;
3764         case REQ_MAXIMIZE:
3765                 if (displayed_views() == 2)
3766                         maximize_view(view);
3767                 break;
3769         case REQ_OPTIONS:
3770                 open_option_menu();
3771                 break;
3773         case REQ_TOGGLE_LINENO:
3774                 toggle_view_option(&opt_line_number, "line numbers");
3775                 break;
3777         case REQ_TOGGLE_DATE:
3778                 toggle_date();
3779                 break;
3781         case REQ_TOGGLE_AUTHOR:
3782                 toggle_author();
3783                 break;
3785         case REQ_TOGGLE_REV_GRAPH:
3786                 toggle_view_option(&opt_rev_graph, "revision graph display");
3787                 break;
3789         case REQ_TOGGLE_REFS:
3790                 toggle_view_option(&opt_show_refs, "reference display");
3791                 break;
3793         case REQ_TOGGLE_SORT_FIELD:
3794         case REQ_TOGGLE_SORT_ORDER:
3795                 report("Sorting is not yet supported for the %s view", view->name);
3796                 break;
3798         case REQ_SEARCH:
3799         case REQ_SEARCH_BACK:
3800                 search_view(view, request);
3801                 break;
3803         case REQ_FIND_NEXT:
3804         case REQ_FIND_PREV:
3805                 find_next(view, request);
3806                 break;
3808         case REQ_STOP_LOADING:
3809                 foreach_view(view, i) {
3810                         if (view->pipe)
3811                                 report("Stopped loading the %s view", view->name),
3812                         end_update(view, TRUE);
3813                 }
3814                 break;
3816         case REQ_SHOW_VERSION:
3817                 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3818                 return TRUE;
3820         case REQ_SCREEN_REDRAW:
3821                 redraw_display(TRUE);
3822                 break;
3824         case REQ_EDIT:
3825                 report("Nothing to edit");
3826                 break;
3828         case REQ_ENTER:
3829                 report("Nothing to enter");
3830                 break;
3832         case REQ_VIEW_CLOSE:
3833                 /* XXX: Mark closed views by letting view->prev point to the
3834                  * view itself. Parents to closed view should never be
3835                  * followed. */
3836                 if (view->prev && view->prev != view) {
3837                         maximize_view(view->prev);
3838                         view->prev = view;
3839                         break;
3840                 }
3841                 /* Fall-through */
3842         case REQ_QUIT:
3843                 return FALSE;
3845         default:
3846                 report("Unknown key, press %s for help",
3847                        get_key(view->keymap, REQ_VIEW_HELP));
3848                 return TRUE;
3849         }
3851         return TRUE;
3855 /*
3856  * View backend utilities
3857  */
3859 enum sort_field {
3860         ORDERBY_NAME,
3861         ORDERBY_DATE,
3862         ORDERBY_AUTHOR,
3863 };
3865 struct sort_state {
3866         const enum sort_field *fields;
3867         size_t size, current;
3868         bool reverse;
3869 };
3871 #define SORT_STATE(fields) { fields, ARRAY_SIZE(fields), 0 }
3872 #define get_sort_field(state) ((state).fields[(state).current])
3873 #define sort_order(state, result) ((state).reverse ? -(result) : (result))
3875 static void
3876 sort_view(struct view *view, enum request request, struct sort_state *state,
3877           int (*compare)(const void *, const void *))
3879         switch (request) {
3880         case REQ_TOGGLE_SORT_FIELD:
3881                 state->current = (state->current + 1) % state->size;
3882                 break;
3884         case REQ_TOGGLE_SORT_ORDER:
3885                 state->reverse = !state->reverse;
3886                 break;
3887         default:
3888                 die("Not a sort request");
3889         }
3891         qsort(view->line, view->lines, sizeof(*view->line), compare);
3892         redraw_view(view);
3895 DEFINE_ALLOCATOR(realloc_authors, const char *, 256)
3897 /* Small author cache to reduce memory consumption. It uses binary
3898  * search to lookup or find place to position new entries. No entries
3899  * are ever freed. */
3900 static const char *
3901 get_author(const char *name)
3903         static const char **authors;
3904         static size_t authors_size;
3905         int from = 0, to = authors_size - 1;
3907         while (from <= to) {
3908                 size_t pos = (to + from) / 2;
3909                 int cmp = strcmp(name, authors[pos]);
3911                 if (!cmp)
3912                         return authors[pos];
3914                 if (cmp < 0)
3915                         to = pos - 1;
3916                 else
3917                         from = pos + 1;
3918         }
3920         if (!realloc_authors(&authors, authors_size, 1))
3921                 return NULL;
3922         name = strdup(name);
3923         if (!name)
3924                 return NULL;
3926         memmove(authors + from + 1, authors + from, (authors_size - from) * sizeof(*authors));
3927         authors[from] = name;
3928         authors_size++;
3930         return name;
3933 static void
3934 parse_timesec(struct time *time, const char *sec)
3936         time->sec = (time_t) atol(sec);
3939 static void
3940 parse_timezone(struct time *time, const char *zone)
3942         long tz;
3944         tz  = ('0' - zone[1]) * 60 * 60 * 10;
3945         tz += ('0' - zone[2]) * 60 * 60;
3946         tz += ('0' - zone[3]) * 60 * 10;
3947         tz += ('0' - zone[4]) * 60;
3949         if (zone[0] == '-')
3950                 tz = -tz;
3952         time->tz = tz;
3953         time->sec -= tz;
3956 /* Parse author lines where the name may be empty:
3957  *      author  <email@address.tld> 1138474660 +0100
3958  */
3959 static void
3960 parse_author_line(char *ident, const char **author, struct time *time)
3962         char *nameend = strchr(ident, '<');
3963         char *emailend = strchr(ident, '>');
3965         if (nameend && emailend)
3966                 *nameend = *emailend = 0;
3967         ident = chomp_string(ident);
3968         if (!*ident) {
3969                 if (nameend)
3970                         ident = chomp_string(nameend + 1);
3971                 if (!*ident)
3972                         ident = "Unknown";
3973         }
3975         *author = get_author(ident);
3977         /* Parse epoch and timezone */
3978         if (emailend && emailend[1] == ' ') {
3979                 char *secs = emailend + 2;
3980                 char *zone = strchr(secs, ' ');
3982                 parse_timesec(time, secs);
3984                 if (zone && strlen(zone) == STRING_SIZE(" +0700"))
3985                         parse_timezone(time, zone + 1);
3986         }
3989 /*
3990  * Pager backend
3991  */
3993 static bool
3994 pager_draw(struct view *view, struct line *line, unsigned int lineno)
3996         if (opt_line_number && draw_lineno(view, lineno))
3997                 return TRUE;
3999         draw_text(view, line->type, line->data, TRUE);
4000         return TRUE;
4003 static bool
4004 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
4006         const char *describe_argv[] = { "git", "describe", commit_id, NULL };
4007         char ref[SIZEOF_STR];
4009         if (!io_run_buf(describe_argv, ref, sizeof(ref)) || !*ref)
4010                 return TRUE;
4012         /* This is the only fatal call, since it can "corrupt" the buffer. */
4013         if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
4014                 return FALSE;
4016         return TRUE;
4019 static void
4020 add_pager_refs(struct view *view, struct line *line)
4022         char buf[SIZEOF_STR];
4023         char *commit_id = (char *)line->data + STRING_SIZE("commit ");
4024         struct ref_list *list;
4025         size_t bufpos = 0, i;
4026         const char *sep = "Refs: ";
4027         bool is_tag = FALSE;
4029         assert(line->type == LINE_COMMIT);
4031         list = get_ref_list(commit_id);
4032         if (!list) {
4033                 if (view->type == VIEW_DIFF)
4034                         goto try_add_describe_ref;
4035                 return;
4036         }
4038         for (i = 0; i < list->size; i++) {
4039                 struct ref *ref = list->refs[i];
4040                 const char *fmt = ref->tag    ? "%s[%s]" :
4041                                   ref->remote ? "%s<%s>" : "%s%s";
4043                 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
4044                         return;
4045                 sep = ", ";
4046                 if (ref->tag)
4047                         is_tag = TRUE;
4048         }
4050         if (!is_tag && view->type == VIEW_DIFF) {
4051 try_add_describe_ref:
4052                 /* Add <tag>-g<commit_id> "fake" reference. */
4053                 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
4054                         return;
4055         }
4057         if (bufpos == 0)
4058                 return;
4060         add_line_text(view, buf, LINE_PP_REFS);
4063 static bool
4064 pager_read(struct view *view, char *data)
4066         struct line *line;
4068         if (!data)
4069                 return TRUE;
4071         line = add_line_text(view, data, get_line_type(data));
4072         if (!line)
4073                 return FALSE;
4075         if (line->type == LINE_COMMIT &&
4076             (view->type == VIEW_DIFF ||
4077              view->type == VIEW_LOG))
4078                 add_pager_refs(view, line);
4080         return TRUE;
4083 static enum request
4084 pager_request(struct view *view, enum request request, struct line *line)
4086         int split = 0;
4088         if (request != REQ_ENTER)
4089                 return request;
4091         if (line->type == LINE_COMMIT &&
4092            (view->type == VIEW_LOG ||
4093             view->type == VIEW_PAGER)) {
4094                 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
4095                 split = 1;
4096         }
4098         /* Always scroll the view even if it was split. That way
4099          * you can use Enter to scroll through the log view and
4100          * split open each commit diff. */
4101         scroll_view(view, REQ_SCROLL_LINE_DOWN);
4103         /* FIXME: A minor workaround. Scrolling the view will call report("")
4104          * but if we are scrolling a non-current view this won't properly
4105          * update the view title. */
4106         if (split)
4107                 update_view_title(view);
4109         return REQ_NONE;
4112 static bool
4113 pager_grep(struct view *view, struct line *line)
4115         const char *text[] = { line->data, NULL };
4117         return grep_text(view, text);
4120 static void
4121 pager_select(struct view *view, struct line *line)
4123         if (line->type == LINE_COMMIT) {
4124                 char *text = (char *)line->data + STRING_SIZE("commit ");
4126                 if (view->type != VIEW_PAGER)
4127                         string_copy_rev(view->ref, text);
4128                 string_copy_rev(ref_commit, text);
4129         }
4132 static struct view_ops pager_ops = {
4133         "line",
4134         NULL,
4135         NULL,
4136         pager_read,
4137         pager_draw,
4138         pager_request,
4139         pager_grep,
4140         pager_select,
4141 };
4143 static const char *log_argv[SIZEOF_ARG] = {
4144         "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
4145 };
4147 static enum request
4148 log_request(struct view *view, enum request request, struct line *line)
4150         switch (request) {
4151         case REQ_REFRESH:
4152                 load_refs();
4153                 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
4154                 return REQ_NONE;
4155         default:
4156                 return pager_request(view, request, line);
4157         }
4160 static struct view_ops log_ops = {
4161         "line",
4162         log_argv,
4163         NULL,
4164         pager_read,
4165         pager_draw,
4166         log_request,
4167         pager_grep,
4168         pager_select,
4169 };
4171 static const char *diff_argv[SIZEOF_ARG] = {
4172         "git", "show", "--pretty=fuller", "--no-color", "--root",
4173                 "--patch-with-stat", "--find-copies-harder", "-C",
4174                 "%(diff-args)", "%(commit)", "--", "%(file-args)", NULL
4175 };
4177 static struct view_ops diff_ops = {
4178         "line",
4179         diff_argv,
4180         NULL,
4181         pager_read,
4182         pager_draw,
4183         pager_request,
4184         pager_grep,
4185         pager_select,
4186 };
4188 /*
4189  * Help backend
4190  */
4192 static bool help_keymap_hidden[ARRAY_SIZE(keymap_table)];
4194 static bool
4195 help_open_keymap_title(struct view *view, enum keymap keymap)
4197         struct line *line;
4199         line = add_line_format(view, LINE_HELP_KEYMAP, "[%c] %s bindings",
4200                                help_keymap_hidden[keymap] ? '+' : '-',
4201                                enum_name(keymap_table[keymap]));
4202         if (line)
4203                 line->other = keymap;
4205         return help_keymap_hidden[keymap];
4208 static void
4209 help_open_keymap(struct view *view, enum keymap keymap)
4211         const char *group = NULL;
4212         char buf[SIZEOF_STR];
4213         size_t bufpos;
4214         bool add_title = TRUE;
4215         int i;
4217         for (i = 0; i < ARRAY_SIZE(req_info); i++) {
4218                 const char *key = NULL;
4220                 if (req_info[i].request == REQ_NONE)
4221                         continue;
4223                 if (!req_info[i].request) {
4224                         group = req_info[i].help;
4225                         continue;
4226                 }
4228                 key = get_keys(keymap, req_info[i].request, TRUE);
4229                 if (!key || !*key)
4230                         continue;
4232                 if (add_title && help_open_keymap_title(view, keymap))
4233                         return;
4234                 add_title = FALSE;
4236                 if (group) {
4237                         add_line_text(view, group, LINE_HELP_GROUP);
4238                         group = NULL;
4239                 }
4241                 add_line_format(view, LINE_DEFAULT, "    %-25s %-20s %s", key,
4242                                 enum_name(req_info[i]), req_info[i].help);
4243         }
4245         group = "External commands:";
4247         for (i = 0; i < run_requests; i++) {
4248                 struct run_request *req = get_run_request(REQ_NONE + i + 1);
4249                 const char *key;
4250                 int argc;
4252                 if (!req || req->keymap != keymap)
4253                         continue;
4255                 key = get_key_name(req->key);
4256                 if (!*key)
4257                         key = "(no key defined)";
4259                 if (add_title && help_open_keymap_title(view, keymap))
4260                         return;
4261                 if (group) {
4262                         add_line_text(view, group, LINE_HELP_GROUP);
4263                         group = NULL;
4264                 }
4266                 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
4267                         if (!string_format_from(buf, &bufpos, "%s%s",
4268                                                 argc ? " " : "", req->argv[argc]))
4269                                 return;
4271                 add_line_format(view, LINE_DEFAULT, "    %-25s `%s`", key, buf);
4272         }
4275 static bool
4276 help_open(struct view *view)
4278         enum keymap keymap;
4280         reset_view(view);
4281         add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
4282         add_line_text(view, "", LINE_DEFAULT);
4284         for (keymap = 0; keymap < ARRAY_SIZE(keymap_table); keymap++)
4285                 help_open_keymap(view, keymap);
4287         return TRUE;
4290 static enum request
4291 help_request(struct view *view, enum request request, struct line *line)
4293         switch (request) {
4294         case REQ_ENTER:
4295                 if (line->type == LINE_HELP_KEYMAP) {
4296                         help_keymap_hidden[line->other] =
4297                                 !help_keymap_hidden[line->other];
4298                         view->p_restore = TRUE;
4299                         open_view(view, REQ_VIEW_HELP, OPEN_REFRESH);
4300                 }
4302                 return REQ_NONE;
4303         default:
4304                 return pager_request(view, request, line);
4305         }
4308 static struct view_ops help_ops = {
4309         "line",
4310         NULL,
4311         help_open,
4312         NULL,
4313         pager_draw,
4314         help_request,
4315         pager_grep,
4316         pager_select,
4317 };
4320 /*
4321  * Tree backend
4322  */
4324 struct tree_stack_entry {
4325         struct tree_stack_entry *prev;  /* Entry below this in the stack */
4326         unsigned long lineno;           /* Line number to restore */
4327         char *name;                     /* Position of name in opt_path */
4328 };
4330 /* The top of the path stack. */
4331 static struct tree_stack_entry *tree_stack = NULL;
4332 unsigned long tree_lineno = 0;
4334 static void
4335 pop_tree_stack_entry(void)
4337         struct tree_stack_entry *entry = tree_stack;
4339         tree_lineno = entry->lineno;
4340         entry->name[0] = 0;
4341         tree_stack = entry->prev;
4342         free(entry);
4345 static void
4346 push_tree_stack_entry(const char *name, unsigned long lineno)
4348         struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
4349         size_t pathlen = strlen(opt_path);
4351         if (!entry)
4352                 return;
4354         entry->prev = tree_stack;
4355         entry->name = opt_path + pathlen;
4356         tree_stack = entry;
4358         if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
4359                 pop_tree_stack_entry();
4360                 return;
4361         }
4363         /* Move the current line to the first tree entry. */
4364         tree_lineno = 1;
4365         entry->lineno = lineno;
4368 /* Parse output from git-ls-tree(1):
4369  *
4370  * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
4371  */
4373 #define SIZEOF_TREE_ATTR \
4374         STRING_SIZE("100644 blob f931e1d229c3e185caad4449bf5b66ed72462657\t")
4376 #define SIZEOF_TREE_MODE \
4377         STRING_SIZE("100644 ")
4379 #define TREE_ID_OFFSET \
4380         STRING_SIZE("100644 blob ")
4382 struct tree_entry {
4383         char id[SIZEOF_REV];
4384         mode_t mode;
4385         struct time time;               /* Date from the author ident. */
4386         const char *author;             /* Author of the commit. */
4387         char name[1];
4388 };
4390 static const char *
4391 tree_path(const struct line *line)
4393         return ((struct tree_entry *) line->data)->name;
4396 static int
4397 tree_compare_entry(const struct line *line1, const struct line *line2)
4399         if (line1->type != line2->type)
4400                 return line1->type == LINE_TREE_DIR ? -1 : 1;
4401         return strcmp(tree_path(line1), tree_path(line2));
4404 static const enum sort_field tree_sort_fields[] = {
4405         ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
4406 };
4407 static struct sort_state tree_sort_state = SORT_STATE(tree_sort_fields);
4409 static int
4410 tree_compare(const void *l1, const void *l2)
4412         const struct line *line1 = (const struct line *) l1;
4413         const struct line *line2 = (const struct line *) l2;
4414         const struct tree_entry *entry1 = ((const struct line *) l1)->data;
4415         const struct tree_entry *entry2 = ((const struct line *) l2)->data;
4417         if (line1->type == LINE_TREE_HEAD)
4418                 return -1;
4419         if (line2->type == LINE_TREE_HEAD)
4420                 return 1;
4422         switch (get_sort_field(tree_sort_state)) {
4423         case ORDERBY_DATE:
4424                 return sort_order(tree_sort_state, timecmp(&entry1->time, &entry2->time));
4426         case ORDERBY_AUTHOR:
4427                 return sort_order(tree_sort_state, strcmp(entry1->author, entry2->author));
4429         case ORDERBY_NAME:
4430         default:
4431                 return sort_order(tree_sort_state, tree_compare_entry(line1, line2));
4432         }
4436 static struct line *
4437 tree_entry(struct view *view, enum line_type type, const char *path,
4438            const char *mode, const char *id)
4440         struct tree_entry *entry = calloc(1, sizeof(*entry) + strlen(path));
4441         struct line *line = entry ? add_line_data(view, entry, type) : NULL;
4443         if (!entry || !line) {
4444                 free(entry);
4445                 return NULL;
4446         }
4448         strncpy(entry->name, path, strlen(path));
4449         if (mode)
4450                 entry->mode = strtoul(mode, NULL, 8);
4451         if (id)
4452                 string_copy_rev(entry->id, id);
4454         return line;
4457 static bool
4458 tree_read_date(struct view *view, char *text, bool *read_date)
4460         static const char *author_name;
4461         static struct time author_time;
4463         if (!text && *read_date) {
4464                 *read_date = FALSE;
4465                 return TRUE;
4467         } else if (!text) {
4468                 char *path = *opt_path ? opt_path : ".";
4469                 /* Find next entry to process */
4470                 const char *log_file[] = {
4471                         "git", "log", "--no-color", "--pretty=raw",
4472                                 "--cc", "--raw", view->id, "--", path, NULL
4473                 };
4475                 if (!view->lines) {
4476                         tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL);
4477                         report("Tree is empty");
4478                         return TRUE;
4479                 }
4481                 if (!start_update(view, log_file, opt_cdup)) {
4482                         report("Failed to load tree data");
4483                         return TRUE;
4484                 }
4486                 *read_date = TRUE;
4487                 return FALSE;
4489         } else if (*text == 'a' && get_line_type(text) == LINE_AUTHOR) {
4490                 parse_author_line(text + STRING_SIZE("author "),
4491                                   &author_name, &author_time);
4493         } else if (*text == ':') {
4494                 char *pos;
4495                 size_t annotated = 1;
4496                 size_t i;
4498                 pos = strchr(text, '\t');
4499                 if (!pos)
4500                         return TRUE;
4501                 text = pos + 1;
4502                 if (*opt_path && !strncmp(text, opt_path, strlen(opt_path)))
4503                         text += strlen(opt_path);
4504                 pos = strchr(text, '/');
4505                 if (pos)
4506                         *pos = 0;
4508                 for (i = 1; i < view->lines; i++) {
4509                         struct line *line = &view->line[i];
4510                         struct tree_entry *entry = line->data;
4512                         annotated += !!entry->author;
4513                         if (entry->author || strcmp(entry->name, text))
4514                                 continue;
4516                         entry->author = author_name;
4517                         entry->time = author_time;
4518                         line->dirty = 1;
4519                         break;
4520                 }
4522                 if (annotated == view->lines)
4523                         io_kill(view->pipe);
4524         }
4525         return TRUE;
4528 static bool
4529 tree_read(struct view *view, char *text)
4531         static bool read_date = FALSE;
4532         struct tree_entry *data;
4533         struct line *entry, *line;
4534         enum line_type type;
4535         size_t textlen = text ? strlen(text) : 0;
4536         char *path = text + SIZEOF_TREE_ATTR;
4538         if (read_date || !text)
4539                 return tree_read_date(view, text, &read_date);
4541         if (textlen <= SIZEOF_TREE_ATTR)
4542                 return FALSE;
4543         if (view->lines == 0 &&
4544             !tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL))
4545                 return FALSE;
4547         /* Strip the path part ... */
4548         if (*opt_path) {
4549                 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
4550                 size_t striplen = strlen(opt_path);
4552                 if (pathlen > striplen)
4553                         memmove(path, path + striplen,
4554                                 pathlen - striplen + 1);
4556                 /* Insert "link" to parent directory. */
4557                 if (view->lines == 1 &&
4558                     !tree_entry(view, LINE_TREE_DIR, "..", "040000", view->ref))
4559                         return FALSE;
4560         }
4562         type = text[SIZEOF_TREE_MODE] == 't' ? LINE_TREE_DIR : LINE_TREE_FILE;
4563         entry = tree_entry(view, type, path, text, text + TREE_ID_OFFSET);
4564         if (!entry)
4565                 return FALSE;
4566         data = entry->data;
4568         /* Skip "Directory ..." and ".." line. */
4569         for (line = &view->line[1 + !!*opt_path]; line < entry; line++) {
4570                 if (tree_compare_entry(line, entry) <= 0)
4571                         continue;
4573                 memmove(line + 1, line, (entry - line) * sizeof(*entry));
4575                 line->data = data;
4576                 line->type = type;
4577                 for (; line <= entry; line++)
4578                         line->dirty = line->cleareol = 1;
4579                 return TRUE;
4580         }
4582         if (tree_lineno > view->lineno) {
4583                 view->lineno = tree_lineno;
4584                 tree_lineno = 0;
4585         }
4587         return TRUE;
4590 static bool
4591 tree_draw(struct view *view, struct line *line, unsigned int lineno)
4593         struct tree_entry *entry = line->data;
4595         if (line->type == LINE_TREE_HEAD) {
4596                 if (draw_text(view, line->type, "Directory path /", TRUE))
4597                         return TRUE;
4598         } else {
4599                 if (draw_mode(view, entry->mode))
4600                         return TRUE;
4602                 if (opt_author && draw_author(view, entry->author))
4603                         return TRUE;
4605                 if (opt_date && draw_date(view, &entry->time))
4606                         return TRUE;
4607         }
4608         if (draw_text(view, line->type, entry->name, TRUE))
4609                 return TRUE;
4610         return TRUE;
4613 static void
4614 open_blob_editor(const char *id)
4616         const char *blob_argv[] = { "git", "cat-file", "blob", id, NULL };
4617         char file[SIZEOF_STR] = "/tmp/tigblob.XXXXXX";
4618         int fd = mkstemp(file);
4620         if (fd == -1)
4621                 report("Failed to create temporary file");
4622         else if (!io_run_append(blob_argv, fd))
4623                 report("Failed to save blob data to file");
4624         else
4625                 open_editor(file);
4626         if (fd != -1)
4627                 unlink(file);
4630 static enum request
4631 tree_request(struct view *view, enum request request, struct line *line)
4633         enum open_flags flags;
4634         struct tree_entry *entry = line->data;
4636         switch (request) {
4637         case REQ_VIEW_BLAME:
4638                 if (line->type != LINE_TREE_FILE) {
4639                         report("Blame only supported for files");
4640                         return REQ_NONE;
4641                 }
4643                 string_copy(opt_ref, view->vid);
4644                 return request;
4646         case REQ_EDIT:
4647                 if (line->type != LINE_TREE_FILE) {
4648                         report("Edit only supported for files");
4649                 } else if (!is_head_commit(view->vid)) {
4650                         open_blob_editor(entry->id);
4651                 } else {
4652                         open_editor(opt_file);
4653                 }
4654                 return REQ_NONE;
4656         case REQ_TOGGLE_SORT_FIELD:
4657         case REQ_TOGGLE_SORT_ORDER:
4658                 sort_view(view, request, &tree_sort_state, tree_compare);
4659                 return REQ_NONE;
4661         case REQ_PARENT:
4662                 if (!*opt_path) {
4663                         /* quit view if at top of tree */
4664                         return REQ_VIEW_CLOSE;
4665                 }
4666                 /* fake 'cd  ..' */
4667                 line = &view->line[1];
4668                 break;
4670         case REQ_ENTER:
4671                 break;
4673         default:
4674                 return request;
4675         }
4677         /* Cleanup the stack if the tree view is at a different tree. */
4678         while (!*opt_path && tree_stack)
4679                 pop_tree_stack_entry();
4681         switch (line->type) {
4682         case LINE_TREE_DIR:
4683                 /* Depending on whether it is a subdirectory or parent link
4684                  * mangle the path buffer. */
4685                 if (line == &view->line[1] && *opt_path) {
4686                         pop_tree_stack_entry();
4688                 } else {
4689                         const char *basename = tree_path(line);
4691                         push_tree_stack_entry(basename, view->lineno);
4692                 }
4694                 /* Trees and subtrees share the same ID, so they are not not
4695                  * unique like blobs. */
4696                 flags = OPEN_RELOAD;
4697                 request = REQ_VIEW_TREE;
4698                 break;
4700         case LINE_TREE_FILE:
4701                 flags = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
4702                 request = REQ_VIEW_BLOB;
4703                 break;
4705         default:
4706                 return REQ_NONE;
4707         }
4709         open_view(view, request, flags);
4710         if (request == REQ_VIEW_TREE)
4711                 view->lineno = tree_lineno;
4713         return REQ_NONE;
4716 static bool
4717 tree_grep(struct view *view, struct line *line)
4719         struct tree_entry *entry = line->data;
4720         const char *text[] = {
4721                 entry->name,
4722                 opt_author ? entry->author : "",
4723                 mkdate(&entry->time, opt_date),
4724                 NULL
4725         };
4727         return grep_text(view, text);
4730 static void
4731 tree_select(struct view *view, struct line *line)
4733         struct tree_entry *entry = line->data;
4735         if (line->type == LINE_TREE_FILE) {
4736                 string_copy_rev(ref_blob, entry->id);
4737                 string_format(opt_file, "%s%s", opt_path, tree_path(line));
4739         } else if (line->type != LINE_TREE_DIR) {
4740                 return;
4741         }
4743         string_copy_rev(view->ref, entry->id);
4746 static bool
4747 tree_prepare(struct view *view)
4749         if (view->lines == 0 && opt_prefix[0]) {
4750                 char *pos = opt_prefix;
4752                 while (pos && *pos) {
4753                         char *end = strchr(pos, '/');
4755                         if (end)
4756                                 *end = 0;
4757                         push_tree_stack_entry(pos, 0);
4758                         pos = end;
4759                         if (end) {
4760                                 *end = '/';
4761                                 pos++;
4762                         }
4763                 }
4765         } else if (strcmp(view->vid, view->id)) {
4766                 opt_path[0] = 0;
4767         }
4769         return prepare_io(view, opt_cdup, view->ops->argv, TRUE);
4772 static const char *tree_argv[SIZEOF_ARG] = {
4773         "git", "ls-tree", "%(commit)", "%(directory)", NULL
4774 };
4776 static struct view_ops tree_ops = {
4777         "file",
4778         tree_argv,
4779         NULL,
4780         tree_read,
4781         tree_draw,
4782         tree_request,
4783         tree_grep,
4784         tree_select,
4785         tree_prepare,
4786 };
4788 static bool
4789 blob_read(struct view *view, char *line)
4791         if (!line)
4792                 return TRUE;
4793         return add_line_text(view, line, LINE_DEFAULT) != NULL;
4796 static enum request
4797 blob_request(struct view *view, enum request request, struct line *line)
4799         switch (request) {
4800         case REQ_EDIT:
4801                 open_blob_editor(view->vid);
4802                 return REQ_NONE;
4803         default:
4804                 return pager_request(view, request, line);
4805         }
4808 static const char *blob_argv[SIZEOF_ARG] = {
4809         "git", "cat-file", "blob", "%(blob)", NULL
4810 };
4812 static struct view_ops blob_ops = {
4813         "line",
4814         blob_argv,
4815         NULL,
4816         blob_read,
4817         pager_draw,
4818         blob_request,
4819         pager_grep,
4820         pager_select,
4821 };
4823 /*
4824  * Blame backend
4825  *
4826  * Loading the blame view is a two phase job:
4827  *
4828  *  1. File content is read either using opt_file from the
4829  *     filesystem or using git-cat-file.
4830  *  2. Then blame information is incrementally added by
4831  *     reading output from git-blame.
4832  */
4834 struct blame_commit {
4835         char id[SIZEOF_REV];            /* SHA1 ID. */
4836         char title[128];                /* First line of the commit message. */
4837         const char *author;             /* Author of the commit. */
4838         struct time time;               /* Date from the author ident. */
4839         char filename[128];             /* Name of file. */
4840         char parent_id[SIZEOF_REV];     /* Parent/previous SHA1 ID. */
4841         char parent_filename[128];      /* Parent/previous name of file. */
4842 };
4844 struct blame {
4845         struct blame_commit *commit;
4846         unsigned long lineno;
4847         char text[1];
4848 };
4850 static bool
4851 blame_open(struct view *view)
4853         char path[SIZEOF_STR];
4854         size_t i;
4856         if (!view->prev && *opt_prefix) {
4857                 string_copy(path, opt_file);
4858                 if (!string_format(opt_file, "%s%s", opt_prefix, path))
4859                         return FALSE;
4860         }
4862         if (*opt_ref || !io_open(&view->io, "%s%s", opt_cdup, opt_file)) {
4863                 const char *blame_cat_file_argv[] = {
4864                         "git", "cat-file", "blob", path, NULL
4865                 };
4867                 if (!string_format(path, "%s:%s", opt_ref, opt_file) ||
4868                     !start_update(view, blame_cat_file_argv, opt_cdup))
4869                         return FALSE;
4870         }
4872         /* First pass: remove multiple references to the same commit. */
4873         for (i = 0; i < view->lines; i++) {
4874                 struct blame *blame = view->line[i].data;
4876                 if (blame->commit && blame->commit->id[0])
4877                         blame->commit->id[0] = 0;
4878                 else
4879                         blame->commit = NULL;
4880         }
4882         /* Second pass: free existing references. */
4883         for (i = 0; i < view->lines; i++) {
4884                 struct blame *blame = view->line[i].data;
4886                 if (blame->commit)
4887                         free(blame->commit);
4888         }
4890         setup_update(view, opt_file);
4891         string_format(view->ref, "%s ...", opt_file);
4893         return TRUE;
4896 static struct blame_commit *
4897 get_blame_commit(struct view *view, const char *id)
4899         size_t i;
4901         for (i = 0; i < view->lines; i++) {
4902                 struct blame *blame = view->line[i].data;
4904                 if (!blame->commit)
4905                         continue;
4907                 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
4908                         return blame->commit;
4909         }
4911         {
4912                 struct blame_commit *commit = calloc(1, sizeof(*commit));
4914                 if (commit)
4915                         string_ncopy(commit->id, id, SIZEOF_REV);
4916                 return commit;
4917         }
4920 static bool
4921 parse_number(const char **posref, size_t *number, size_t min, size_t max)
4923         const char *pos = *posref;
4925         *posref = NULL;
4926         pos = strchr(pos + 1, ' ');
4927         if (!pos || !isdigit(pos[1]))
4928                 return FALSE;
4929         *number = atoi(pos + 1);
4930         if (*number < min || *number > max)
4931                 return FALSE;
4933         *posref = pos;
4934         return TRUE;
4937 static struct blame_commit *
4938 parse_blame_commit(struct view *view, const char *text, int *blamed)
4940         struct blame_commit *commit;
4941         struct blame *blame;
4942         const char *pos = text + SIZEOF_REV - 2;
4943         size_t orig_lineno = 0;
4944         size_t lineno;
4945         size_t group;
4947         if (strlen(text) <= SIZEOF_REV || pos[1] != ' ')
4948                 return NULL;
4950         if (!parse_number(&pos, &orig_lineno, 1, 9999999) ||
4951             !parse_number(&pos, &lineno, 1, view->lines) ||
4952             !parse_number(&pos, &group, 1, view->lines - lineno + 1))
4953                 return NULL;
4955         commit = get_blame_commit(view, text);
4956         if (!commit)
4957                 return NULL;
4959         *blamed += group;
4960         while (group--) {
4961                 struct line *line = &view->line[lineno + group - 1];
4963                 blame = line->data;
4964                 blame->commit = commit;
4965                 blame->lineno = orig_lineno + group - 1;
4966                 line->dirty = 1;
4967         }
4969         return commit;
4972 static bool
4973 blame_read_file(struct view *view, const char *line, bool *read_file)
4975         if (!line) {
4976                 const char *blame_argv[] = {
4977                         "git", "blame", "--incremental",
4978                                 *opt_ref ? opt_ref : "--incremental", "--", opt_file, NULL
4979                 };
4981                 if (view->lines == 0 && !view->prev)
4982                         die("No blame exist for %s", view->vid);
4984                 if (view->lines == 0 || !start_update(view, blame_argv, opt_cdup)) {
4985                         report("Failed to load blame data");
4986                         return TRUE;
4987                 }
4989                 *read_file = FALSE;
4990                 return FALSE;
4992         } else {
4993                 size_t linelen = strlen(line);
4994                 struct blame *blame = malloc(sizeof(*blame) + linelen);
4996                 if (!blame)
4997                         return FALSE;
4999                 blame->commit = NULL;
5000                 strncpy(blame->text, line, linelen);
5001                 blame->text[linelen] = 0;
5002                 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
5003         }
5006 static bool
5007 match_blame_header(const char *name, char **line)
5009         size_t namelen = strlen(name);
5010         bool matched = !strncmp(name, *line, namelen);
5012         if (matched)
5013                 *line += namelen;
5015         return matched;
5018 static bool
5019 blame_read(struct view *view, char *line)
5021         static struct blame_commit *commit = NULL;
5022         static int blamed = 0;
5023         static bool read_file = TRUE;
5025         if (read_file)
5026                 return blame_read_file(view, line, &read_file);
5028         if (!line) {
5029                 /* Reset all! */
5030                 commit = NULL;
5031                 blamed = 0;
5032                 read_file = TRUE;
5033                 string_format(view->ref, "%s", view->vid);
5034                 if (view_is_displayed(view)) {
5035                         update_view_title(view);
5036                         redraw_view_from(view, 0);
5037                 }
5038                 return TRUE;
5039         }
5041         if (!commit) {
5042                 commit = parse_blame_commit(view, line, &blamed);
5043                 string_format(view->ref, "%s %2d%%", view->vid,
5044                               view->lines ? blamed * 100 / view->lines : 0);
5046         } else if (match_blame_header("author ", &line)) {
5047                 commit->author = get_author(line);
5049         } else if (match_blame_header("author-time ", &line)) {
5050                 parse_timesec(&commit->time, line);
5052         } else if (match_blame_header("author-tz ", &line)) {
5053                 parse_timezone(&commit->time, line);
5055         } else if (match_blame_header("summary ", &line)) {
5056                 string_ncopy(commit->title, line, strlen(line));
5058         } else if (match_blame_header("previous ", &line)) {
5059                 if (strlen(line) <= SIZEOF_REV)
5060                         return FALSE;
5061                 string_copy_rev(commit->parent_id, line);
5062                 line += SIZEOF_REV;
5063                 string_ncopy(commit->parent_filename, line, strlen(line));
5065         } else if (match_blame_header("filename ", &line)) {
5066                 string_ncopy(commit->filename, line, strlen(line));
5067                 commit = NULL;
5068         }
5070         return TRUE;
5073 static bool
5074 blame_draw(struct view *view, struct line *line, unsigned int lineno)
5076         struct blame *blame = line->data;
5077         struct time *time = NULL;
5078         const char *id = NULL, *author = NULL;
5080         if (blame->commit && *blame->commit->filename) {
5081                 id = blame->commit->id;
5082                 author = blame->commit->author;
5083                 time = &blame->commit->time;
5084         }
5086         if (opt_date && draw_date(view, time))
5087                 return TRUE;
5089         if (opt_author && draw_author(view, author))
5090                 return TRUE;
5092         if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
5093                 return TRUE;
5095         if (draw_lineno(view, lineno))
5096                 return TRUE;
5098         draw_text(view, LINE_DEFAULT, blame->text, TRUE);
5099         return TRUE;
5102 static bool
5103 check_blame_commit(struct blame *blame, bool check_null_id)
5105         if (!blame->commit)
5106                 report("Commit data not loaded yet");
5107         else if (check_null_id && !strcmp(blame->commit->id, NULL_ID))
5108                 report("No commit exist for the selected line");
5109         else
5110                 return TRUE;
5111         return FALSE;
5114 static void
5115 setup_blame_parent_line(struct view *view, struct blame *blame)
5117         char from[SIZEOF_REF + SIZEOF_STR];
5118         char to[SIZEOF_REF + SIZEOF_STR];
5119         const char *diff_tree_argv[] = {
5120                 "git", "diff", "--no-textconv", "--no-extdiff", "--no-color",
5121                         "-U0", from, to, "--", NULL
5122         };
5123         struct io io;
5124         int parent_lineno = -1;
5125         int blamed_lineno = -1;
5126         char *line;
5128         if (!string_format(from, "%s:%s", opt_ref, opt_file) ||
5129             !string_format(to, "%s:%s", blame->commit->id, blame->commit->filename) ||
5130             !io_run(&io, IO_RD, NULL, diff_tree_argv))
5131                 return;
5133         while ((line = io_get(&io, '\n', TRUE))) {
5134                 if (*line == '@') {
5135                         char *pos = strchr(line, '+');
5137                         parent_lineno = atoi(line + 4);
5138                         if (pos)
5139                                 blamed_lineno = atoi(pos + 1);
5141                 } else if (*line == '+' && parent_lineno != -1) {
5142                         if (blame->lineno == blamed_lineno - 1 &&
5143                             !strcmp(blame->text, line + 1)) {
5144                                 view->lineno = parent_lineno ? parent_lineno - 1 : 0;
5145                                 break;
5146                         }
5147                         blamed_lineno++;
5148                 }
5149         }
5151         io_done(&io);
5154 static enum request
5155 blame_request(struct view *view, enum request request, struct line *line)
5157         enum open_flags flags = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
5158         struct blame *blame = line->data;
5160         switch (request) {
5161         case REQ_VIEW_BLAME:
5162                 if (check_blame_commit(blame, TRUE)) {
5163                         string_copy(opt_ref, blame->commit->id);
5164                         string_copy(opt_file, blame->commit->filename);
5165                         if (blame->lineno)
5166                                 view->lineno = blame->lineno;
5167                         open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
5168                 }
5169                 break;
5171         case REQ_PARENT:
5172                 if (!check_blame_commit(blame, TRUE))
5173                         break;
5174                 if (!*blame->commit->parent_id) {
5175                         report("The selected commit has no parents");
5176                 } else {
5177                         string_copy_rev(opt_ref, blame->commit->parent_id);
5178                         string_copy(opt_file, blame->commit->parent_filename);
5179                         setup_blame_parent_line(view, blame);
5180                         open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
5181                 }
5182                 break;
5184         case REQ_ENTER:
5185                 if (!check_blame_commit(blame, FALSE))
5186                         break;
5188                 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
5189                     !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
5190                         break;
5192                 if (!strcmp(blame->commit->id, NULL_ID)) {
5193                         struct view *diff = VIEW(REQ_VIEW_DIFF);
5194                         const char *diff_index_argv[] = {
5195                                 "git", "diff-index", "--root", "--patch-with-stat",
5196                                         "-C", "-M", "HEAD", "--", view->vid, NULL
5197                         };
5199                         if (!*blame->commit->parent_id) {
5200                                 diff_index_argv[1] = "diff";
5201                                 diff_index_argv[2] = "--no-color";
5202                                 diff_index_argv[6] = "--";
5203                                 diff_index_argv[7] = "/dev/null";
5204                         }
5206                         if (!prepare_update(diff, diff_index_argv, NULL)) {
5207                                 report("Failed to allocate diff command");
5208                                 break;
5209                         }
5210                         flags |= OPEN_PREPARED;
5211                 }
5213                 open_view(view, REQ_VIEW_DIFF, flags);
5214                 if (VIEW(REQ_VIEW_DIFF)->pipe && !strcmp(blame->commit->id, NULL_ID))
5215                         string_copy_rev(VIEW(REQ_VIEW_DIFF)->ref, NULL_ID);
5216                 break;
5218         default:
5219                 return request;
5220         }
5222         return REQ_NONE;
5225 static bool
5226 blame_grep(struct view *view, struct line *line)
5228         struct blame *blame = line->data;
5229         struct blame_commit *commit = blame->commit;
5230         const char *text[] = {
5231                 blame->text,
5232                 commit ? commit->title : "",
5233                 commit ? commit->id : "",
5234                 commit && opt_author ? commit->author : "",
5235                 commit ? mkdate(&commit->time, opt_date) : "",
5236                 NULL
5237         };
5239         return grep_text(view, text);
5242 static void
5243 blame_select(struct view *view, struct line *line)
5245         struct blame *blame = line->data;
5246         struct blame_commit *commit = blame->commit;
5248         if (!commit)
5249                 return;
5251         if (!strcmp(commit->id, NULL_ID))
5252                 string_ncopy(ref_commit, "HEAD", 4);
5253         else
5254                 string_copy_rev(ref_commit, commit->id);
5257 static struct view_ops blame_ops = {
5258         "line",
5259         NULL,
5260         blame_open,
5261         blame_read,
5262         blame_draw,
5263         blame_request,
5264         blame_grep,
5265         blame_select,
5266 };
5268 /*
5269  * Branch backend
5270  */
5272 struct branch {
5273         const char *author;             /* Author of the last commit. */
5274         struct time time;               /* Date of the last activity. */
5275         const struct ref *ref;          /* Name and commit ID information. */
5276 };
5278 static const struct ref branch_all;
5280 static const enum sort_field branch_sort_fields[] = {
5281         ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
5282 };
5283 static struct sort_state branch_sort_state = SORT_STATE(branch_sort_fields);
5285 static int
5286 branch_compare(const void *l1, const void *l2)
5288         const struct branch *branch1 = ((const struct line *) l1)->data;
5289         const struct branch *branch2 = ((const struct line *) l2)->data;
5291         switch (get_sort_field(branch_sort_state)) {
5292         case ORDERBY_DATE:
5293                 return sort_order(branch_sort_state, timecmp(&branch1->time, &branch2->time));
5295         case ORDERBY_AUTHOR:
5296                 return sort_order(branch_sort_state, strcmp(branch1->author, branch2->author));
5298         case ORDERBY_NAME:
5299         default:
5300                 return sort_order(branch_sort_state, strcmp(branch1->ref->name, branch2->ref->name));
5301         }
5304 static bool
5305 branch_draw(struct view *view, struct line *line, unsigned int lineno)
5307         struct branch *branch = line->data;
5308         enum line_type type = branch->ref->head ? LINE_MAIN_HEAD : LINE_DEFAULT;
5310         if (opt_date && draw_date(view, &branch->time))
5311                 return TRUE;
5313         if (opt_author && draw_author(view, branch->author))
5314                 return TRUE;
5316         draw_text(view, type, branch->ref == &branch_all ? "All branches" : branch->ref->name, TRUE);
5317         return TRUE;
5320 static enum request
5321 branch_request(struct view *view, enum request request, struct line *line)
5323         struct branch *branch = line->data;
5325         switch (request) {
5326         case REQ_REFRESH:
5327                 load_refs();
5328                 open_view(view, REQ_VIEW_BRANCH, OPEN_REFRESH);
5329                 return REQ_NONE;
5331         case REQ_TOGGLE_SORT_FIELD:
5332         case REQ_TOGGLE_SORT_ORDER:
5333                 sort_view(view, request, &branch_sort_state, branch_compare);
5334                 return REQ_NONE;
5336         case REQ_ENTER:
5337         {
5338                 const struct ref *ref = branch->ref;
5339                 const char *all_branches_argv[] = {
5340                         "git", "log", "--no-color", "--pretty=raw", "--parents",
5341                               "--topo-order",
5342                               ref == &branch_all ? "--all" : ref->name, NULL
5343                 };
5344                 struct view *main_view = VIEW(REQ_VIEW_MAIN);
5346                 if (!prepare_update(main_view, all_branches_argv, NULL))
5347                         report("Failed to load view of all branches");
5348                 else
5349                         open_view(view, REQ_VIEW_MAIN, OPEN_PREPARED | OPEN_SPLIT);
5350                 return REQ_NONE;
5351         }
5352         default:
5353                 return request;
5354         }
5357 static bool
5358 branch_read(struct view *view, char *line)
5360         static char id[SIZEOF_REV];
5361         struct branch *reference;
5362         size_t i;
5364         if (!line)
5365                 return TRUE;
5367         switch (get_line_type(line)) {
5368         case LINE_COMMIT:
5369                 string_copy_rev(id, line + STRING_SIZE("commit "));
5370                 return TRUE;
5372         case LINE_AUTHOR:
5373                 for (i = 0, reference = NULL; i < view->lines; i++) {
5374                         struct branch *branch = view->line[i].data;
5376                         if (strcmp(branch->ref->id, id))
5377                                 continue;
5379                         view->line[i].dirty = TRUE;
5380                         if (reference) {
5381                                 branch->author = reference->author;
5382                                 branch->time = reference->time;
5383                                 continue;
5384                         }
5386                         parse_author_line(line + STRING_SIZE("author "),
5387                                           &branch->author, &branch->time);
5388                         reference = branch;
5389                 }
5390                 return TRUE;
5392         default:
5393                 return TRUE;
5394         }
5398 static bool
5399 branch_open_visitor(void *data, const struct ref *ref)
5401         struct view *view = data;
5402         struct branch *branch;
5404         if (ref->tag || ref->ltag || ref->remote)
5405                 return TRUE;
5407         branch = calloc(1, sizeof(*branch));
5408         if (!branch)
5409                 return FALSE;
5411         branch->ref = ref;
5412         return !!add_line_data(view, branch, LINE_DEFAULT);
5415 static bool
5416 branch_open(struct view *view)
5418         const char *branch_log[] = {
5419                 "git", "log", "--no-color", "--pretty=raw",
5420                         "--simplify-by-decoration", "--all", NULL
5421         };
5423         if (!start_update(view, branch_log, NULL)) {
5424                 report("Failed to load branch data");
5425                 return TRUE;
5426         }
5428         setup_update(view, view->id);
5429         branch_open_visitor(view, &branch_all);
5430         foreach_ref(branch_open_visitor, view);
5431         view->p_restore = TRUE;
5433         return TRUE;
5436 static bool
5437 branch_grep(struct view *view, struct line *line)
5439         struct branch *branch = line->data;
5440         const char *text[] = {
5441                 branch->ref->name,
5442                 branch->author,
5443                 NULL
5444         };
5446         return grep_text(view, text);
5449 static void
5450 branch_select(struct view *view, struct line *line)
5452         struct branch *branch = line->data;
5454         string_copy_rev(view->ref, branch->ref->id);
5455         string_copy_rev(ref_commit, branch->ref->id);
5456         string_copy_rev(ref_head, branch->ref->id);
5457         string_copy_rev(ref_branch, branch->ref->name);
5460 static struct view_ops branch_ops = {
5461         "branch",
5462         NULL,
5463         branch_open,
5464         branch_read,
5465         branch_draw,
5466         branch_request,
5467         branch_grep,
5468         branch_select,
5469 };
5471 /*
5472  * Status backend
5473  */
5475 struct status {
5476         char status;
5477         struct {
5478                 mode_t mode;
5479                 char rev[SIZEOF_REV];
5480                 char name[SIZEOF_STR];
5481         } old;
5482         struct {
5483                 mode_t mode;
5484                 char rev[SIZEOF_REV];
5485                 char name[SIZEOF_STR];
5486         } new;
5487 };
5489 static char status_onbranch[SIZEOF_STR];
5490 static struct status stage_status;
5491 static enum line_type stage_line_type;
5492 static size_t stage_chunks;
5493 static int *stage_chunk;
5495 DEFINE_ALLOCATOR(realloc_ints, int, 32)
5497 /* This should work even for the "On branch" line. */
5498 static inline bool
5499 status_has_none(struct view *view, struct line *line)
5501         return line < view->line + view->lines && !line[1].data;
5504 /* Get fields from the diff line:
5505  * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
5506  */
5507 static inline bool
5508 status_get_diff(struct status *file, const char *buf, size_t bufsize)
5510         const char *old_mode = buf +  1;
5511         const char *new_mode = buf +  8;
5512         const char *old_rev  = buf + 15;
5513         const char *new_rev  = buf + 56;
5514         const char *status   = buf + 97;
5516         if (bufsize < 98 ||
5517             old_mode[-1] != ':' ||
5518             new_mode[-1] != ' ' ||
5519             old_rev[-1]  != ' ' ||
5520             new_rev[-1]  != ' ' ||
5521             status[-1]   != ' ')
5522                 return FALSE;
5524         file->status = *status;
5526         string_copy_rev(file->old.rev, old_rev);
5527         string_copy_rev(file->new.rev, new_rev);
5529         file->old.mode = strtoul(old_mode, NULL, 8);
5530         file->new.mode = strtoul(new_mode, NULL, 8);
5532         file->old.name[0] = file->new.name[0] = 0;
5534         return TRUE;
5537 static bool
5538 status_run(struct view *view, const char *argv[], char status, enum line_type type)
5540         struct status *unmerged = NULL;
5541         char *buf;
5542         struct io io;
5544         if (!io_run(&io, IO_RD, opt_cdup, argv))
5545                 return FALSE;
5547         add_line_data(view, NULL, type);
5549         while ((buf = io_get(&io, 0, TRUE))) {
5550                 struct status *file = unmerged;
5552                 if (!file) {
5553                         file = calloc(1, sizeof(*file));
5554                         if (!file || !add_line_data(view, file, type))
5555                                 goto error_out;
5556                 }
5558                 /* Parse diff info part. */
5559                 if (status) {
5560                         file->status = status;
5561                         if (status == 'A')
5562                                 string_copy(file->old.rev, NULL_ID);
5564                 } else if (!file->status || file == unmerged) {
5565                         if (!status_get_diff(file, buf, strlen(buf)))
5566                                 goto error_out;
5568                         buf = io_get(&io, 0, TRUE);
5569                         if (!buf)
5570                                 break;
5572                         /* Collapse all modified entries that follow an
5573                          * associated unmerged entry. */
5574                         if (unmerged == file) {
5575                                 unmerged->status = 'U';
5576                                 unmerged = NULL;
5577                         } else if (file->status == 'U') {
5578                                 unmerged = file;
5579                         }
5580                 }
5582                 /* Grab the old name for rename/copy. */
5583                 if (!*file->old.name &&
5584                     (file->status == 'R' || file->status == 'C')) {
5585                         string_ncopy(file->old.name, buf, strlen(buf));
5587                         buf = io_get(&io, 0, TRUE);
5588                         if (!buf)
5589                                 break;
5590                 }
5592                 /* git-ls-files just delivers a NUL separated list of
5593                  * file names similar to the second half of the
5594                  * git-diff-* output. */
5595                 string_ncopy(file->new.name, buf, strlen(buf));
5596                 if (!*file->old.name)
5597                         string_copy(file->old.name, file->new.name);
5598                 file = NULL;
5599         }
5601         if (io_error(&io)) {
5602 error_out:
5603                 io_done(&io);
5604                 return FALSE;
5605         }
5607         if (!view->line[view->lines - 1].data)
5608                 add_line_data(view, NULL, LINE_STAT_NONE);
5610         io_done(&io);
5611         return TRUE;
5614 /* Don't show unmerged entries in the staged section. */
5615 static const char *status_diff_index_argv[] = {
5616         "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
5617                              "--cached", "-M", "HEAD", NULL
5618 };
5620 static const char *status_diff_files_argv[] = {
5621         "git", "diff-files", "-z", NULL
5622 };
5624 static const char *status_list_other_argv[] = {
5625         "git", "ls-files", "-z", "--others", "--exclude-standard", opt_prefix, NULL
5626 };
5628 static const char *status_list_no_head_argv[] = {
5629         "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
5630 };
5632 static const char *update_index_argv[] = {
5633         "git", "update-index", "-q", "--unmerged", "--refresh", NULL
5634 };
5636 /* Restore the previous line number to stay in the context or select a
5637  * line with something that can be updated. */
5638 static void
5639 status_restore(struct view *view)
5641         if (view->p_lineno >= view->lines)
5642                 view->p_lineno = view->lines - 1;
5643         while (view->p_lineno < view->lines && !view->line[view->p_lineno].data)
5644                 view->p_lineno++;
5645         while (view->p_lineno > 0 && !view->line[view->p_lineno].data)
5646                 view->p_lineno--;
5648         /* If the above fails, always skip the "On branch" line. */
5649         if (view->p_lineno < view->lines)
5650                 view->lineno = view->p_lineno;
5651         else
5652                 view->lineno = 1;
5654         if (view->lineno < view->offset)
5655                 view->offset = view->lineno;
5656         else if (view->offset + view->height <= view->lineno)
5657                 view->offset = view->lineno - view->height + 1;
5659         view->p_restore = FALSE;
5662 static void
5663 status_update_onbranch(void)
5665         static const char *paths[][2] = {
5666                 { "rebase-apply/rebasing",      "Rebasing" },
5667                 { "rebase-apply/applying",      "Applying mailbox" },
5668                 { "rebase-apply/",              "Rebasing mailbox" },
5669                 { "rebase-merge/interactive",   "Interactive rebase" },
5670                 { "rebase-merge/",              "Rebase merge" },
5671                 { "MERGE_HEAD",                 "Merging" },
5672                 { "BISECT_LOG",                 "Bisecting" },
5673                 { "HEAD",                       "On branch" },
5674         };
5675         char buf[SIZEOF_STR];
5676         struct stat stat;
5677         int i;
5679         if (is_initial_commit()) {
5680                 string_copy(status_onbranch, "Initial commit");
5681                 return;
5682         }
5684         for (i = 0; i < ARRAY_SIZE(paths); i++) {
5685                 char *head = opt_head;
5687                 if (!string_format(buf, "%s/%s", opt_git_dir, paths[i][0]) ||
5688                     lstat(buf, &stat) < 0)
5689                         continue;
5691                 if (!*opt_head) {
5692                         struct io io;
5694                         if (io_open(&io, "%s/rebase-merge/head-name", opt_git_dir) &&
5695                             io_read_buf(&io, buf, sizeof(buf))) {
5696                                 head = buf;
5697                                 if (!prefixcmp(head, "refs/heads/"))
5698                                         head += STRING_SIZE("refs/heads/");
5699                         }
5700                 }
5702                 if (!string_format(status_onbranch, "%s %s", paths[i][1], head))
5703                         string_copy(status_onbranch, opt_head);
5704                 return;
5705         }
5707         string_copy(status_onbranch, "Not currently on any branch");
5710 /* First parse staged info using git-diff-index(1), then parse unstaged
5711  * info using git-diff-files(1), and finally untracked files using
5712  * git-ls-files(1). */
5713 static bool
5714 status_open(struct view *view)
5716         reset_view(view);
5718         add_line_data(view, NULL, LINE_STAT_HEAD);
5719         status_update_onbranch();
5721         io_run_bg(update_index_argv);
5723         if (is_initial_commit()) {
5724                 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
5725                         return FALSE;
5726         } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
5727                 return FALSE;
5728         }
5730         if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
5731             !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
5732                 return FALSE;
5734         /* Restore the exact position or use the specialized restore
5735          * mode? */
5736         if (!view->p_restore)
5737                 status_restore(view);
5738         return TRUE;
5741 static bool
5742 status_draw(struct view *view, struct line *line, unsigned int lineno)
5744         struct status *status = line->data;
5745         enum line_type type;
5746         const char *text;
5748         if (!status) {
5749                 switch (line->type) {
5750                 case LINE_STAT_STAGED:
5751                         type = LINE_STAT_SECTION;
5752                         text = "Changes to be committed:";
5753                         break;
5755                 case LINE_STAT_UNSTAGED:
5756                         type = LINE_STAT_SECTION;
5757                         text = "Changed but not updated:";
5758                         break;
5760                 case LINE_STAT_UNTRACKED:
5761                         type = LINE_STAT_SECTION;
5762                         text = "Untracked files:";
5763                         break;
5765                 case LINE_STAT_NONE:
5766                         type = LINE_DEFAULT;
5767                         text = "  (no files)";
5768                         break;
5770                 case LINE_STAT_HEAD:
5771                         type = LINE_STAT_HEAD;
5772                         text = status_onbranch;
5773                         break;
5775                 default:
5776                         return FALSE;
5777                 }
5778         } else {
5779                 static char buf[] = { '?', ' ', ' ', ' ', 0 };
5781                 buf[0] = status->status;
5782                 if (draw_text(view, line->type, buf, TRUE))
5783                         return TRUE;
5784                 type = LINE_DEFAULT;
5785                 text = status->new.name;
5786         }
5788         draw_text(view, type, text, TRUE);
5789         return TRUE;
5792 static enum request
5793 status_load_error(struct view *view, struct view *stage, const char *path)
5795         if (displayed_views() == 2 || display[current_view] != view)
5796                 maximize_view(view);
5797         report("Failed to load '%s': %s", path, io_strerror(&stage->io));
5798         return REQ_NONE;
5801 static enum request
5802 status_enter(struct view *view, struct line *line)
5804         struct status *status = line->data;
5805         const char *oldpath = status ? status->old.name : NULL;
5806         /* Diffs for unmerged entries are empty when passing the new
5807          * path, so leave it empty. */
5808         const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
5809         const char *info;
5810         enum open_flags split;
5811         struct view *stage = VIEW(REQ_VIEW_STAGE);
5813         if (line->type == LINE_STAT_NONE ||
5814             (!status && line[1].type == LINE_STAT_NONE)) {
5815                 report("No file to diff");
5816                 return REQ_NONE;
5817         }
5819         switch (line->type) {
5820         case LINE_STAT_STAGED:
5821                 if (is_initial_commit()) {
5822                         const char *no_head_diff_argv[] = {
5823                                 "git", "diff", "--no-color", "--patch-with-stat",
5824                                         "--", "/dev/null", newpath, NULL
5825                         };
5827                         if (!prepare_update(stage, no_head_diff_argv, opt_cdup))
5828                                 return status_load_error(view, stage, newpath);
5829                 } else {
5830                         const char *index_show_argv[] = {
5831                                 "git", "diff-index", "--root", "--patch-with-stat",
5832                                         "-C", "-M", "--cached", "HEAD", "--",
5833                                         oldpath, newpath, NULL
5834                         };
5836                         if (!prepare_update(stage, index_show_argv, opt_cdup))
5837                                 return status_load_error(view, stage, newpath);
5838                 }
5840                 if (status)
5841                         info = "Staged changes to %s";
5842                 else
5843                         info = "Staged changes";
5844                 break;
5846         case LINE_STAT_UNSTAGED:
5847         {
5848                 const char *files_show_argv[] = {
5849                         "git", "diff-files", "--root", "--patch-with-stat",
5850                                 "-C", "-M", "--", oldpath, newpath, NULL
5851                 };
5853                 if (!prepare_update(stage, files_show_argv, opt_cdup))
5854                         return status_load_error(view, stage, newpath);
5855                 if (status)
5856                         info = "Unstaged changes to %s";
5857                 else
5858                         info = "Unstaged changes";
5859                 break;
5860         }
5861         case LINE_STAT_UNTRACKED:
5862                 if (!newpath) {
5863                         report("No file to show");
5864                         return REQ_NONE;
5865                 }
5867                 if (!suffixcmp(status->new.name, -1, "/")) {
5868                         report("Cannot display a directory");
5869                         return REQ_NONE;
5870                 }
5872                 if (!prepare_update_file(stage, newpath))
5873                         return status_load_error(view, stage, newpath);
5874                 info = "Untracked file %s";
5875                 break;
5877         case LINE_STAT_HEAD:
5878                 return REQ_NONE;
5880         default:
5881                 die("line type %d not handled in switch", line->type);
5882         }
5884         split = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
5885         open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split);
5886         if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
5887                 if (status) {
5888                         stage_status = *status;
5889                 } else {
5890                         memset(&stage_status, 0, sizeof(stage_status));
5891                 }
5893                 stage_line_type = line->type;
5894                 stage_chunks = 0;
5895                 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
5896         }
5898         return REQ_NONE;
5901 static bool
5902 status_exists(struct status *status, enum line_type type)
5904         struct view *view = VIEW(REQ_VIEW_STATUS);
5905         unsigned long lineno;
5907         for (lineno = 0; lineno < view->lines; lineno++) {
5908                 struct line *line = &view->line[lineno];
5909                 struct status *pos = line->data;
5911                 if (line->type != type)
5912                         continue;
5913                 if (!pos && (!status || !status->status) && line[1].data) {
5914                         select_view_line(view, lineno);
5915                         return TRUE;
5916                 }
5917                 if (pos && !strcmp(status->new.name, pos->new.name)) {
5918                         select_view_line(view, lineno);
5919                         return TRUE;
5920                 }
5921         }
5923         return FALSE;
5927 static bool
5928 status_update_prepare(struct io *io, enum line_type type)
5930         const char *staged_argv[] = {
5931                 "git", "update-index", "-z", "--index-info", NULL
5932         };
5933         const char *others_argv[] = {
5934                 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
5935         };
5937         switch (type) {
5938         case LINE_STAT_STAGED:
5939                 return io_run(io, IO_WR, opt_cdup, staged_argv);
5941         case LINE_STAT_UNSTAGED:
5942         case LINE_STAT_UNTRACKED:
5943                 return io_run(io, IO_WR, opt_cdup, others_argv);
5945         default:
5946                 die("line type %d not handled in switch", type);
5947                 return FALSE;
5948         }
5951 static bool
5952 status_update_write(struct io *io, struct status *status, enum line_type type)
5954         char buf[SIZEOF_STR];
5955         size_t bufsize = 0;
5957         switch (type) {
5958         case LINE_STAT_STAGED:
5959                 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
5960                                         status->old.mode,
5961                                         status->old.rev,
5962                                         status->old.name, 0))
5963                         return FALSE;
5964                 break;
5966         case LINE_STAT_UNSTAGED:
5967         case LINE_STAT_UNTRACKED:
5968                 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
5969                         return FALSE;
5970                 break;
5972         default:
5973                 die("line type %d not handled in switch", type);
5974         }
5976         return io_write(io, buf, bufsize);
5979 static bool
5980 status_update_file(struct status *status, enum line_type type)
5982         struct io io;
5983         bool result;
5985         if (!status_update_prepare(&io, type))
5986                 return FALSE;
5988         result = status_update_write(&io, status, type);
5989         return io_done(&io) && result;
5992 static bool
5993 status_update_files(struct view *view, struct line *line)
5995         char buf[sizeof(view->ref)];
5996         struct io io;
5997         bool result = TRUE;
5998         struct line *pos = view->line + view->lines;
5999         int files = 0;
6000         int file, done;
6001         int cursor_y = -1, cursor_x = -1;
6003         if (!status_update_prepare(&io, line->type))
6004                 return FALSE;
6006         for (pos = line; pos < view->line + view->lines && pos->data; pos++)
6007                 files++;
6009         string_copy(buf, view->ref);
6010         getsyx(cursor_y, cursor_x);
6011         for (file = 0, done = 5; result && file < files; line++, file++) {
6012                 int almost_done = file * 100 / files;
6014                 if (almost_done > done) {
6015                         done = almost_done;
6016                         string_format(view->ref, "updating file %u of %u (%d%% done)",
6017                                       file, files, done);
6018                         update_view_title(view);
6019                         setsyx(cursor_y, cursor_x);
6020                         doupdate();
6021                 }
6022                 result = status_update_write(&io, line->data, line->type);
6023         }
6024         string_copy(view->ref, buf);
6026         return io_done(&io) && result;
6029 static bool
6030 status_update(struct view *view)
6032         struct line *line = &view->line[view->lineno];
6034         assert(view->lines);
6036         if (!line->data) {
6037                 /* This should work even for the "On branch" line. */
6038                 if (line < view->line + view->lines && !line[1].data) {
6039                         report("Nothing to update");
6040                         return FALSE;
6041                 }
6043                 if (!status_update_files(view, line + 1)) {
6044                         report("Failed to update file status");
6045                         return FALSE;
6046                 }
6048         } else if (!status_update_file(line->data, line->type)) {
6049                 report("Failed to update file status");
6050                 return FALSE;
6051         }
6053         return TRUE;
6056 static bool
6057 status_revert(struct status *status, enum line_type type, bool has_none)
6059         if (!status || type != LINE_STAT_UNSTAGED) {
6060                 if (type == LINE_STAT_STAGED) {
6061                         report("Cannot revert changes to staged files");
6062                 } else if (type == LINE_STAT_UNTRACKED) {
6063                         report("Cannot revert changes to untracked files");
6064                 } else if (has_none) {
6065                         report("Nothing to revert");
6066                 } else {
6067                         report("Cannot revert changes to multiple files");
6068                 }
6070         } else if (prompt_yesno("Are you sure you want to revert changes?")) {
6071                 char mode[10] = "100644";
6072                 const char *reset_argv[] = {
6073                         "git", "update-index", "--cacheinfo", mode,
6074                                 status->old.rev, status->old.name, NULL
6075                 };
6076                 const char *checkout_argv[] = {
6077                         "git", "checkout", "--", status->old.name, NULL
6078                 };
6080                 if (status->status == 'U') {
6081                         string_format(mode, "%5o", status->old.mode);
6083                         if (status->old.mode == 0 && status->new.mode == 0) {
6084                                 reset_argv[2] = "--force-remove";
6085                                 reset_argv[3] = status->old.name;
6086                                 reset_argv[4] = NULL;
6087                         }
6089                         if (!io_run_fg(reset_argv, opt_cdup))
6090                                 return FALSE;
6091                         if (status->old.mode == 0 && status->new.mode == 0)
6092                                 return TRUE;
6093                 }
6095                 return io_run_fg(checkout_argv, opt_cdup);
6096         }
6098         return FALSE;
6101 static enum request
6102 status_request(struct view *view, enum request request, struct line *line)
6104         struct status *status = line->data;
6106         switch (request) {
6107         case REQ_STATUS_UPDATE:
6108                 if (!status_update(view))
6109                         return REQ_NONE;
6110                 break;
6112         case REQ_STATUS_REVERT:
6113                 if (!status_revert(status, line->type, status_has_none(view, line)))
6114                         return REQ_NONE;
6115                 break;
6117         case REQ_STATUS_MERGE:
6118                 if (!status || status->status != 'U') {
6119                         report("Merging only possible for files with unmerged status ('U').");
6120                         return REQ_NONE;
6121                 }
6122                 open_mergetool(status->new.name);
6123                 break;
6125         case REQ_EDIT:
6126                 if (!status)
6127                         return request;
6128                 if (status->status == 'D') {
6129                         report("File has been deleted.");
6130                         return REQ_NONE;
6131                 }
6133                 open_editor(status->new.name);
6134                 break;
6136         case REQ_VIEW_BLAME:
6137                 if (status)
6138                         opt_ref[0] = 0;
6139                 return request;
6141         case REQ_ENTER:
6142                 /* After returning the status view has been split to
6143                  * show the stage view. No further reloading is
6144                  * necessary. */
6145                 return status_enter(view, line);
6147         case REQ_REFRESH:
6148                 /* Simply reload the view. */
6149                 break;
6151         default:
6152                 return request;
6153         }
6155         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
6157         return REQ_NONE;
6160 static void
6161 status_select(struct view *view, struct line *line)
6163         struct status *status = line->data;
6164         char file[SIZEOF_STR] = "all files";
6165         const char *text;
6166         const char *key;
6168         if (status && !string_format(file, "'%s'", status->new.name))
6169                 return;
6171         if (!status && line[1].type == LINE_STAT_NONE)
6172                 line++;
6174         switch (line->type) {
6175         case LINE_STAT_STAGED:
6176                 text = "Press %s to unstage %s for commit";
6177                 break;
6179         case LINE_STAT_UNSTAGED:
6180                 text = "Press %s to stage %s for commit";
6181                 break;
6183         case LINE_STAT_UNTRACKED:
6184                 text = "Press %s to stage %s for addition";
6185                 break;
6187         case LINE_STAT_HEAD:
6188         case LINE_STAT_NONE:
6189                 text = "Nothing to update";
6190                 break;
6192         default:
6193                 die("line type %d not handled in switch", line->type);
6194         }
6196         if (status && status->status == 'U') {
6197                 text = "Press %s to resolve conflict in %s";
6198                 key = get_key(KEYMAP_STATUS, REQ_STATUS_MERGE);
6200         } else {
6201                 key = get_key(KEYMAP_STATUS, REQ_STATUS_UPDATE);
6202         }
6204         string_format(view->ref, text, key, file);
6205         if (status)
6206                 string_copy(opt_file, status->new.name);
6209 static bool
6210 status_grep(struct view *view, struct line *line)
6212         struct status *status = line->data;
6214         if (status) {
6215                 const char buf[2] = { status->status, 0 };
6216                 const char *text[] = { status->new.name, buf, NULL };
6218                 return grep_text(view, text);
6219         }
6221         return FALSE;
6224 static struct view_ops status_ops = {
6225         "file",
6226         NULL,
6227         status_open,
6228         NULL,
6229         status_draw,
6230         status_request,
6231         status_grep,
6232         status_select,
6233 };
6236 static bool
6237 stage_diff_write(struct io *io, struct line *line, struct line *end)
6239         while (line < end) {
6240                 if (!io_write(io, line->data, strlen(line->data)) ||
6241                     !io_write(io, "\n", 1))
6242                         return FALSE;
6243                 line++;
6244                 if (line->type == LINE_DIFF_CHUNK ||
6245                     line->type == LINE_DIFF_HEADER)
6246                         break;
6247         }
6249         return TRUE;
6252 static struct line *
6253 stage_diff_find(struct view *view, struct line *line, enum line_type type)
6255         for (; view->line < line; line--)
6256                 if (line->type == type)
6257                         return line;
6259         return NULL;
6262 static bool
6263 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
6265         const char *apply_argv[SIZEOF_ARG] = {
6266                 "git", "apply", "--whitespace=nowarn", NULL
6267         };
6268         struct line *diff_hdr;
6269         struct io io;
6270         int argc = 3;
6272         diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
6273         if (!diff_hdr)
6274                 return FALSE;
6276         if (!revert)
6277                 apply_argv[argc++] = "--cached";
6278         if (revert || stage_line_type == LINE_STAT_STAGED)
6279                 apply_argv[argc++] = "-R";
6280         apply_argv[argc++] = "-";
6281         apply_argv[argc++] = NULL;
6282         if (!io_run(&io, IO_WR, opt_cdup, apply_argv))
6283                 return FALSE;
6285         if (!stage_diff_write(&io, diff_hdr, chunk) ||
6286             !stage_diff_write(&io, chunk, view->line + view->lines))
6287                 chunk = NULL;
6289         io_done(&io);
6290         io_run_bg(update_index_argv);
6292         return chunk ? TRUE : FALSE;
6295 static bool
6296 stage_update(struct view *view, struct line *line)
6298         struct line *chunk = NULL;
6300         if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
6301                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6303         if (chunk) {
6304                 if (!stage_apply_chunk(view, chunk, FALSE)) {
6305                         report("Failed to apply chunk");
6306                         return FALSE;
6307                 }
6309         } else if (!stage_status.status) {
6310                 view = VIEW(REQ_VIEW_STATUS);
6312                 for (line = view->line; line < view->line + view->lines; line++)
6313                         if (line->type == stage_line_type)
6314                                 break;
6316                 if (!status_update_files(view, line + 1)) {
6317                         report("Failed to update files");
6318                         return FALSE;
6319                 }
6321         } else if (!status_update_file(&stage_status, stage_line_type)) {
6322                 report("Failed to update file");
6323                 return FALSE;
6324         }
6326         return TRUE;
6329 static bool
6330 stage_revert(struct view *view, struct line *line)
6332         struct line *chunk = NULL;
6334         if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
6335                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6337         if (chunk) {
6338                 if (!prompt_yesno("Are you sure you want to revert changes?"))
6339                         return FALSE;
6341                 if (!stage_apply_chunk(view, chunk, TRUE)) {
6342                         report("Failed to revert chunk");
6343                         return FALSE;
6344                 }
6345                 return TRUE;
6347         } else {
6348                 return status_revert(stage_status.status ? &stage_status : NULL,
6349                                      stage_line_type, FALSE);
6350         }
6354 static void
6355 stage_next(struct view *view, struct line *line)
6357         int i;
6359         if (!stage_chunks) {
6360                 for (line = view->line; line < view->line + view->lines; line++) {
6361                         if (line->type != LINE_DIFF_CHUNK)
6362                                 continue;
6364                         if (!realloc_ints(&stage_chunk, stage_chunks, 1)) {
6365                                 report("Allocation failure");
6366                                 return;
6367                         }
6369                         stage_chunk[stage_chunks++] = line - view->line;
6370                 }
6371         }
6373         for (i = 0; i < stage_chunks; i++) {
6374                 if (stage_chunk[i] > view->lineno) {
6375                         do_scroll_view(view, stage_chunk[i] - view->lineno);
6376                         report("Chunk %d of %d", i + 1, stage_chunks);
6377                         return;
6378                 }
6379         }
6381         report("No next chunk found");
6384 static enum request
6385 stage_request(struct view *view, enum request request, struct line *line)
6387         switch (request) {
6388         case REQ_STATUS_UPDATE:
6389                 if (!stage_update(view, line))
6390                         return REQ_NONE;
6391                 break;
6393         case REQ_STATUS_REVERT:
6394                 if (!stage_revert(view, line))
6395                         return REQ_NONE;
6396                 break;
6398         case REQ_STAGE_NEXT:
6399                 if (stage_line_type == LINE_STAT_UNTRACKED) {
6400                         report("File is untracked; press %s to add",
6401                                get_key(KEYMAP_STAGE, REQ_STATUS_UPDATE));
6402                         return REQ_NONE;
6403                 }
6404                 stage_next(view, line);
6405                 return REQ_NONE;
6407         case REQ_EDIT:
6408                 if (!stage_status.new.name[0])
6409                         return request;
6410                 if (stage_status.status == 'D') {
6411                         report("File has been deleted.");
6412                         return REQ_NONE;
6413                 }
6415                 open_editor(stage_status.new.name);
6416                 break;
6418         case REQ_REFRESH:
6419                 /* Reload everything ... */
6420                 break;
6422         case REQ_VIEW_BLAME:
6423                 if (stage_status.new.name[0]) {
6424                         string_copy(opt_file, stage_status.new.name);
6425                         opt_ref[0] = 0;
6426                 }
6427                 return request;
6429         case REQ_ENTER:
6430                 return pager_request(view, request, line);
6432         default:
6433                 return request;
6434         }
6436         VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
6437         open_view(view, REQ_VIEW_STATUS, OPEN_REFRESH);
6439         /* Check whether the staged entry still exists, and close the
6440          * stage view if it doesn't. */
6441         if (!status_exists(&stage_status, stage_line_type)) {
6442                 status_restore(VIEW(REQ_VIEW_STATUS));
6443                 return REQ_VIEW_CLOSE;
6444         }
6446         if (stage_line_type == LINE_STAT_UNTRACKED) {
6447                 if (!suffixcmp(stage_status.new.name, -1, "/")) {
6448                         report("Cannot display a directory");
6449                         return REQ_NONE;
6450                 }
6452                 if (!prepare_update_file(view, stage_status.new.name)) {
6453                         report("Failed to open file: %s", strerror(errno));
6454                         return REQ_NONE;
6455                 }
6456         }
6457         open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
6459         return REQ_NONE;
6462 static struct view_ops stage_ops = {
6463         "line",
6464         NULL,
6465         NULL,
6466         pager_read,
6467         pager_draw,
6468         stage_request,
6469         pager_grep,
6470         pager_select,
6471 };
6474 /*
6475  * Revision graph
6476  */
6478 struct commit {
6479         char id[SIZEOF_REV];            /* SHA1 ID. */
6480         char title[128];                /* First line of the commit message. */
6481         const char *author;             /* Author of the commit. */
6482         struct time time;               /* Date from the author ident. */
6483         struct ref_list *refs;          /* Repository references. */
6484         chtype graph[SIZEOF_REVGRAPH];  /* Ancestry chain graphics. */
6485         size_t graph_size;              /* The width of the graph array. */
6486         bool has_parents;               /* Rewritten --parents seen. */
6487 };
6489 /* Size of rev graph with no  "padding" columns */
6490 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
6492 struct rev_graph {
6493         struct rev_graph *prev, *next, *parents;
6494         char rev[SIZEOF_REVITEMS][SIZEOF_REV];
6495         size_t size;
6496         struct commit *commit;
6497         size_t pos;
6498         unsigned int boundary:1;
6499 };
6501 /* Parents of the commit being visualized. */
6502 static struct rev_graph graph_parents[4];
6504 /* The current stack of revisions on the graph. */
6505 static struct rev_graph graph_stacks[4] = {
6506         { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
6507         { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
6508         { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
6509         { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
6510 };
6512 static inline bool
6513 graph_parent_is_merge(struct rev_graph *graph)
6515         return graph->parents->size > 1;
6518 static inline void
6519 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
6521         struct commit *commit = graph->commit;
6523         if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
6524                 commit->graph[commit->graph_size++] = symbol;
6527 static void
6528 clear_rev_graph(struct rev_graph *graph)
6530         graph->boundary = 0;
6531         graph->size = graph->pos = 0;
6532         graph->commit = NULL;
6533         memset(graph->parents, 0, sizeof(*graph->parents));
6536 static void
6537 done_rev_graph(struct rev_graph *graph)
6539         if (graph_parent_is_merge(graph) &&
6540             graph->pos < graph->size - 1 &&
6541             graph->next->size == graph->size + graph->parents->size - 1) {
6542                 size_t i = graph->pos + graph->parents->size - 1;
6544                 graph->commit->graph_size = i * 2;
6545                 while (i < graph->next->size - 1) {
6546                         append_to_rev_graph(graph, ' ');
6547                         append_to_rev_graph(graph, '\\');
6548                         i++;
6549                 }
6550         }
6552         clear_rev_graph(graph);
6555 static void
6556 push_rev_graph(struct rev_graph *graph, const char *parent)
6558         int i;
6560         /* "Collapse" duplicate parents lines.
6561          *
6562          * FIXME: This needs to also update update the drawn graph but
6563          * for now it just serves as a method for pruning graph lines. */
6564         for (i = 0; i < graph->size; i++)
6565                 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
6566                         return;
6568         if (graph->size < SIZEOF_REVITEMS) {
6569                 string_copy_rev(graph->rev[graph->size++], parent);
6570         }
6573 static chtype
6574 get_rev_graph_symbol(struct rev_graph *graph)
6576         chtype symbol;
6578         if (graph->boundary)
6579                 symbol = REVGRAPH_BOUND;
6580         else if (graph->parents->size == 0)
6581                 symbol = REVGRAPH_INIT;
6582         else if (graph_parent_is_merge(graph))
6583                 symbol = REVGRAPH_MERGE;
6584         else if (graph->pos >= graph->size)
6585                 symbol = REVGRAPH_BRANCH;
6586         else
6587                 symbol = REVGRAPH_COMMIT;
6589         return symbol;
6592 static void
6593 draw_rev_graph(struct rev_graph *graph)
6595         struct rev_filler {
6596                 chtype separator, line;
6597         };
6598         enum { DEFAULT, RSHARP, RDIAG, LDIAG };
6599         static struct rev_filler fillers[] = {
6600                 { ' ',  '|' },
6601                 { '`',  '.' },
6602                 { '\'', ' ' },
6603                 { '/',  ' ' },
6604         };
6605         chtype symbol = get_rev_graph_symbol(graph);
6606         struct rev_filler *filler;
6607         size_t i;
6609         fillers[DEFAULT].line = opt_line_graphics ? ACS_VLINE : '|';
6610         filler = &fillers[DEFAULT];
6612         for (i = 0; i < graph->pos; i++) {
6613                 append_to_rev_graph(graph, filler->line);
6614                 if (graph_parent_is_merge(graph->prev) &&
6615                     graph->prev->pos == i)
6616                         filler = &fillers[RSHARP];
6618                 append_to_rev_graph(graph, filler->separator);
6619         }
6621         /* Place the symbol for this revision. */
6622         append_to_rev_graph(graph, symbol);
6624         if (graph->prev->size > graph->size)
6625                 filler = &fillers[RDIAG];
6626         else
6627                 filler = &fillers[DEFAULT];
6629         i++;
6631         for (; i < graph->size; i++) {
6632                 append_to_rev_graph(graph, filler->separator);
6633                 append_to_rev_graph(graph, filler->line);
6634                 if (graph_parent_is_merge(graph->prev) &&
6635                     i < graph->prev->pos + graph->parents->size)
6636                         filler = &fillers[RSHARP];
6637                 if (graph->prev->size > graph->size)
6638                         filler = &fillers[LDIAG];
6639         }
6641         if (graph->prev->size > graph->size) {
6642                 append_to_rev_graph(graph, filler->separator);
6643                 if (filler->line != ' ')
6644                         append_to_rev_graph(graph, filler->line);
6645         }
6648 /* Prepare the next rev graph */
6649 static void
6650 prepare_rev_graph(struct rev_graph *graph)
6652         size_t i;
6654         /* First, traverse all lines of revisions up to the active one. */
6655         for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
6656                 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
6657                         break;
6659                 push_rev_graph(graph->next, graph->rev[graph->pos]);
6660         }
6662         /* Interleave the new revision parent(s). */
6663         for (i = 0; !graph->boundary && i < graph->parents->size; i++)
6664                 push_rev_graph(graph->next, graph->parents->rev[i]);
6666         /* Lastly, put any remaining revisions. */
6667         for (i = graph->pos + 1; i < graph->size; i++)
6668                 push_rev_graph(graph->next, graph->rev[i]);
6671 static void
6672 update_rev_graph(struct view *view, struct rev_graph *graph)
6674         /* If this is the finalizing update ... */
6675         if (graph->commit)
6676                 prepare_rev_graph(graph);
6678         /* Graph visualization needs a one rev look-ahead,
6679          * so the first update doesn't visualize anything. */
6680         if (!graph->prev->commit)
6681                 return;
6683         if (view->lines > 2)
6684                 view->line[view->lines - 3].dirty = 1;
6685         if (view->lines > 1)
6686                 view->line[view->lines - 2].dirty = 1;
6687         draw_rev_graph(graph->prev);
6688         done_rev_graph(graph->prev->prev);
6692 /*
6693  * Main view backend
6694  */
6696 static const char *main_argv[SIZEOF_ARG] = {
6697         "git", "log", "--no-color", "--pretty=raw", "--parents",
6698                 "--topo-order", "%(diff-args)", "%(rev-args)",
6699                 "--", "%(file-args)", NULL
6700 };
6702 static bool
6703 main_draw(struct view *view, struct line *line, unsigned int lineno)
6705         struct commit *commit = line->data;
6707         if (!commit->author)
6708                 return FALSE;
6710         if (opt_date && draw_date(view, &commit->time))
6711                 return TRUE;
6713         if (opt_author && draw_author(view, commit->author))
6714                 return TRUE;
6716         if (opt_rev_graph && commit->graph_size &&
6717             draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
6718                 return TRUE;
6720         if (opt_show_refs && commit->refs) {
6721                 size_t i;
6723                 for (i = 0; i < commit->refs->size; i++) {
6724                         struct ref *ref = commit->refs->refs[i];
6725                         enum line_type type;
6727                         if (ref->head)
6728                                 type = LINE_MAIN_HEAD;
6729                         else if (ref->ltag)
6730                                 type = LINE_MAIN_LOCAL_TAG;
6731                         else if (ref->tag)
6732                                 type = LINE_MAIN_TAG;
6733                         else if (ref->tracked)
6734                                 type = LINE_MAIN_TRACKED;
6735                         else if (ref->remote)
6736                                 type = LINE_MAIN_REMOTE;
6737                         else
6738                                 type = LINE_MAIN_REF;
6740                         if (draw_text(view, type, "[", TRUE) ||
6741                             draw_text(view, type, ref->name, TRUE) ||
6742                             draw_text(view, type, "]", TRUE))
6743                                 return TRUE;
6745                         if (draw_text(view, LINE_DEFAULT, " ", TRUE))
6746                                 return TRUE;
6747                 }
6748         }
6750         draw_text(view, LINE_DEFAULT, commit->title, TRUE);
6751         return TRUE;
6754 /* Reads git log --pretty=raw output and parses it into the commit struct. */
6755 static bool
6756 main_read(struct view *view, char *line)
6758         static struct rev_graph *graph = graph_stacks;
6759         enum line_type type;
6760         struct commit *commit;
6762         if (!line) {
6763                 int i;
6765                 if (!view->lines && !view->prev)
6766                         die("No revisions match the given arguments.");
6767                 if (view->lines > 0) {
6768                         commit = view->line[view->lines - 1].data;
6769                         view->line[view->lines - 1].dirty = 1;
6770                         if (!commit->author) {
6771                                 view->lines--;
6772                                 free(commit);
6773                                 graph->commit = NULL;
6774                         }
6775                 }
6776                 update_rev_graph(view, graph);
6778                 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
6779                         clear_rev_graph(&graph_stacks[i]);
6780                 return TRUE;
6781         }
6783         type = get_line_type(line);
6784         if (type == LINE_COMMIT) {
6785                 commit = calloc(1, sizeof(struct commit));
6786                 if (!commit)
6787                         return FALSE;
6789                 line += STRING_SIZE("commit ");
6790                 if (*line == '-') {
6791                         graph->boundary = 1;
6792                         line++;
6793                 }
6795                 string_copy_rev(commit->id, line);
6796                 commit->refs = get_ref_list(commit->id);
6797                 graph->commit = commit;
6798                 add_line_data(view, commit, LINE_MAIN_COMMIT);
6800                 while ((line = strchr(line, ' '))) {
6801                         line++;
6802                         push_rev_graph(graph->parents, line);
6803                         commit->has_parents = TRUE;
6804                 }
6805                 return TRUE;
6806         }
6808         if (!view->lines)
6809                 return TRUE;
6810         commit = view->line[view->lines - 1].data;
6812         switch (type) {
6813         case LINE_PARENT:
6814                 if (commit->has_parents)
6815                         break;
6816                 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
6817                 break;
6819         case LINE_AUTHOR:
6820                 parse_author_line(line + STRING_SIZE("author "),
6821                                   &commit->author, &commit->time);
6822                 update_rev_graph(view, graph);
6823                 graph = graph->next;
6824                 break;
6826         default:
6827                 /* Fill in the commit title if it has not already been set. */
6828                 if (commit->title[0])
6829                         break;
6831                 /* Require titles to start with a non-space character at the
6832                  * offset used by git log. */
6833                 if (strncmp(line, "    ", 4))
6834                         break;
6835                 line += 4;
6836                 /* Well, if the title starts with a whitespace character,
6837                  * try to be forgiving.  Otherwise we end up with no title. */
6838                 while (isspace(*line))
6839                         line++;
6840                 if (*line == '\0')
6841                         break;
6842                 /* FIXME: More graceful handling of titles; append "..." to
6843                  * shortened titles, etc. */
6845                 string_expand(commit->title, sizeof(commit->title), line, 1);
6846                 view->line[view->lines - 1].dirty = 1;
6847         }
6849         return TRUE;
6852 static enum request
6853 main_request(struct view *view, enum request request, struct line *line)
6855         enum open_flags flags = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
6857         switch (request) {
6858         case REQ_ENTER:
6859                 open_view(view, REQ_VIEW_DIFF, flags);
6860                 break;
6861         case REQ_REFRESH:
6862                 load_refs();
6863                 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
6864                 break;
6865         default:
6866                 return request;
6867         }
6869         return REQ_NONE;
6872 static bool
6873 grep_refs(struct ref_list *list, regex_t *regex)
6875         regmatch_t pmatch;
6876         size_t i;
6878         if (!opt_show_refs || !list)
6879                 return FALSE;
6881         for (i = 0; i < list->size; i++) {
6882                 if (regexec(regex, list->refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
6883                         return TRUE;
6884         }
6886         return FALSE;
6889 static bool
6890 main_grep(struct view *view, struct line *line)
6892         struct commit *commit = line->data;
6893         const char *text[] = {
6894                 commit->title,
6895                 opt_author ? commit->author : "",
6896                 mkdate(&commit->time, opt_date),
6897                 NULL
6898         };
6900         return grep_text(view, text) || grep_refs(commit->refs, view->regex);
6903 static void
6904 main_select(struct view *view, struct line *line)
6906         struct commit *commit = line->data;
6908         string_copy_rev(view->ref, commit->id);
6909         string_copy_rev(ref_commit, view->ref);
6912 static struct view_ops main_ops = {
6913         "commit",
6914         main_argv,
6915         NULL,
6916         main_read,
6917         main_draw,
6918         main_request,
6919         main_grep,
6920         main_select,
6921 };
6924 /*
6925  * Status management
6926  */
6928 /* Whether or not the curses interface has been initialized. */
6929 static bool cursed = FALSE;
6931 /* Terminal hacks and workarounds. */
6932 static bool use_scroll_redrawwin;
6933 static bool use_scroll_status_wclear;
6935 /* The status window is used for polling keystrokes. */
6936 static WINDOW *status_win;
6938 /* Reading from the prompt? */
6939 static bool input_mode = FALSE;
6941 static bool status_empty = FALSE;
6943 /* Update status and title window. */
6944 static void
6945 report(const char *msg, ...)
6947         struct view *view = display[current_view];
6949         if (input_mode)
6950                 return;
6952         if (!view) {
6953                 char buf[SIZEOF_STR];
6954                 va_list args;
6956                 va_start(args, msg);
6957                 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
6958                         buf[sizeof(buf) - 1] = 0;
6959                         buf[sizeof(buf) - 2] = '.';
6960                         buf[sizeof(buf) - 3] = '.';
6961                         buf[sizeof(buf) - 4] = '.';
6962                 }
6963                 va_end(args);
6964                 die("%s", buf);
6965         }
6967         if (!status_empty || *msg) {
6968                 va_list args;
6970                 va_start(args, msg);
6972                 wmove(status_win, 0, 0);
6973                 if (view->has_scrolled && use_scroll_status_wclear)
6974                         wclear(status_win);
6975                 if (*msg) {
6976                         vwprintw(status_win, msg, args);
6977                         status_empty = FALSE;
6978                 } else {
6979                         status_empty = TRUE;
6980                 }
6981                 wclrtoeol(status_win);
6982                 wnoutrefresh(status_win);
6984                 va_end(args);
6985         }
6987         update_view_title(view);
6990 static void
6991 init_display(void)
6993         const char *term;
6994         int x, y;
6996         /* Initialize the curses library */
6997         if (isatty(STDIN_FILENO)) {
6998                 cursed = !!initscr();
6999                 opt_tty = stdin;
7000         } else {
7001                 /* Leave stdin and stdout alone when acting as a pager. */
7002                 opt_tty = fopen("/dev/tty", "r+");
7003                 if (!opt_tty)
7004                         die("Failed to open /dev/tty");
7005                 cursed = !!newterm(NULL, opt_tty, opt_tty);
7006         }
7008         if (!cursed)
7009                 die("Failed to initialize curses");
7011         nonl();         /* Disable conversion and detect newlines from input. */
7012         cbreak();       /* Take input chars one at a time, no wait for \n */
7013         noecho();       /* Don't echo input */
7014         leaveok(stdscr, FALSE);
7016         if (has_colors())
7017                 init_colors();
7019         getmaxyx(stdscr, y, x);
7020         status_win = newwin(1, 0, y - 1, 0);
7021         if (!status_win)
7022                 die("Failed to create status window");
7024         /* Enable keyboard mapping */
7025         keypad(status_win, TRUE);
7026         wbkgdset(status_win, get_line_attr(LINE_STATUS));
7028         TABSIZE = opt_tab_size;
7030         term = getenv("XTERM_VERSION") ? NULL : getenv("COLORTERM");
7031         if (term && !strcmp(term, "gnome-terminal")) {
7032                 /* In the gnome-terminal-emulator, the message from
7033                  * scrolling up one line when impossible followed by
7034                  * scrolling down one line causes corruption of the
7035                  * status line. This is fixed by calling wclear. */
7036                 use_scroll_status_wclear = TRUE;
7037                 use_scroll_redrawwin = FALSE;
7039         } else if (term && !strcmp(term, "xrvt-xpm")) {
7040                 /* No problems with full optimizations in xrvt-(unicode)
7041                  * and aterm. */
7042                 use_scroll_status_wclear = use_scroll_redrawwin = FALSE;
7044         } else {
7045                 /* When scrolling in (u)xterm the last line in the
7046                  * scrolling direction will update slowly. */
7047                 use_scroll_redrawwin = TRUE;
7048                 use_scroll_status_wclear = FALSE;
7049         }
7052 static int
7053 get_input(int prompt_position)
7055         struct view *view;
7056         int i, key, cursor_y, cursor_x;
7058         if (prompt_position)
7059                 input_mode = TRUE;
7061         while (TRUE) {
7062                 bool loading = FALSE;
7064                 foreach_view (view, i) {
7065                         update_view(view);
7066                         if (view_is_displayed(view) && view->has_scrolled &&
7067                             use_scroll_redrawwin)
7068                                 redrawwin(view->win);
7069                         view->has_scrolled = FALSE;
7070                         if (view->pipe)
7071                                 loading = TRUE;
7072                 }
7074                 /* Update the cursor position. */
7075                 if (prompt_position) {
7076                         getbegyx(status_win, cursor_y, cursor_x);
7077                         cursor_x = prompt_position;
7078                 } else {
7079                         view = display[current_view];
7080                         getbegyx(view->win, cursor_y, cursor_x);
7081                         cursor_x = view->width - 1;
7082                         cursor_y += view->lineno - view->offset;
7083                 }
7084                 setsyx(cursor_y, cursor_x);
7086                 /* Refresh, accept single keystroke of input */
7087                 doupdate();
7088                 nodelay(status_win, loading);
7089                 key = wgetch(status_win);
7091                 /* wgetch() with nodelay() enabled returns ERR when
7092                  * there's no input. */
7093                 if (key == ERR) {
7095                 } else if (key == KEY_RESIZE) {
7096                         int height, width;
7098                         getmaxyx(stdscr, height, width);
7100                         wresize(status_win, 1, width);
7101                         mvwin(status_win, height - 1, 0);
7102                         wnoutrefresh(status_win);
7103                         resize_display();
7104                         redraw_display(TRUE);
7106                 } else {
7107                         input_mode = FALSE;
7108                         return key;
7109                 }
7110         }
7113 static char *
7114 prompt_input(const char *prompt, input_handler handler, void *data)
7116         enum input_status status = INPUT_OK;
7117         static char buf[SIZEOF_STR];
7118         size_t pos = 0;
7120         buf[pos] = 0;
7122         while (status == INPUT_OK || status == INPUT_SKIP) {
7123                 int key;
7125                 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
7126                 wclrtoeol(status_win);
7128                 key = get_input(pos + 1);
7129                 switch (key) {
7130                 case KEY_RETURN:
7131                 case KEY_ENTER:
7132                 case '\n':
7133                         status = pos ? INPUT_STOP : INPUT_CANCEL;
7134                         break;
7136                 case KEY_BACKSPACE:
7137                         if (pos > 0)
7138                                 buf[--pos] = 0;
7139                         else
7140                                 status = INPUT_CANCEL;
7141                         break;
7143                 case KEY_ESC:
7144                         status = INPUT_CANCEL;
7145                         break;
7147                 default:
7148                         if (pos >= sizeof(buf)) {
7149                                 report("Input string too long");
7150                                 return NULL;
7151                         }
7153                         status = handler(data, buf, key);
7154                         if (status == INPUT_OK)
7155                                 buf[pos++] = (char) key;
7156                 }
7157         }
7159         /* Clear the status window */
7160         status_empty = FALSE;
7161         report("");
7163         if (status == INPUT_CANCEL)
7164                 return NULL;
7166         buf[pos++] = 0;
7168         return buf;
7171 static enum input_status
7172 prompt_yesno_handler(void *data, char *buf, int c)
7174         if (c == 'y' || c == 'Y')
7175                 return INPUT_STOP;
7176         if (c == 'n' || c == 'N')
7177                 return INPUT_CANCEL;
7178         return INPUT_SKIP;
7181 static bool
7182 prompt_yesno(const char *prompt)
7184         char prompt2[SIZEOF_STR];
7186         if (!string_format(prompt2, "%s [Yy/Nn]", prompt))
7187                 return FALSE;
7189         return !!prompt_input(prompt2, prompt_yesno_handler, NULL);
7192 static enum input_status
7193 read_prompt_handler(void *data, char *buf, int c)
7195         return isprint(c) ? INPUT_OK : INPUT_SKIP;
7198 static char *
7199 read_prompt(const char *prompt)
7201         return prompt_input(prompt, read_prompt_handler, NULL);
7204 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected)
7206         enum input_status status = INPUT_OK;
7207         int size = 0;
7209         while (items[size].text)
7210                 size++;
7212         while (status == INPUT_OK) {
7213                 const struct menu_item *item = &items[*selected];
7214                 int key;
7215                 int i;
7217                 mvwprintw(status_win, 0, 0, "%s (%d of %d) ",
7218                           prompt, *selected + 1, size);
7219                 if (item->hotkey)
7220                         wprintw(status_win, "[%c] ", (char) item->hotkey);
7221                 wprintw(status_win, "%s", item->text);
7222                 wclrtoeol(status_win);
7224                 key = get_input(COLS - 1);
7225                 switch (key) {
7226                 case KEY_RETURN:
7227                 case KEY_ENTER:
7228                 case '\n':
7229                         status = INPUT_STOP;
7230                         break;
7232                 case KEY_LEFT:
7233                 case KEY_UP:
7234                         *selected = *selected - 1;
7235                         if (*selected < 0)
7236                                 *selected = size - 1;
7237                         break;
7239                 case KEY_RIGHT:
7240                 case KEY_DOWN:
7241                         *selected = (*selected + 1) % size;
7242                         break;
7244                 case KEY_ESC:
7245                         status = INPUT_CANCEL;
7246                         break;
7248                 default:
7249                         for (i = 0; items[i].text; i++)
7250                                 if (items[i].hotkey == key) {
7251                                         *selected = i;
7252                                         status = INPUT_STOP;
7253                                         break;
7254                                 }
7255                 }
7256         }
7258         /* Clear the status window */
7259         status_empty = FALSE;
7260         report("");
7262         return status != INPUT_CANCEL;
7265 /*
7266  * Repository properties
7267  */
7269 static struct ref **refs = NULL;
7270 static size_t refs_size = 0;
7271 static struct ref *refs_head = NULL;
7273 static struct ref_list **ref_lists = NULL;
7274 static size_t ref_lists_size = 0;
7276 DEFINE_ALLOCATOR(realloc_refs, struct ref *, 256)
7277 DEFINE_ALLOCATOR(realloc_refs_list, struct ref *, 8)
7278 DEFINE_ALLOCATOR(realloc_ref_lists, struct ref_list *, 8)
7280 static int
7281 compare_refs(const void *ref1_, const void *ref2_)
7283         const struct ref *ref1 = *(const struct ref **)ref1_;
7284         const struct ref *ref2 = *(const struct ref **)ref2_;
7286         if (ref1->tag != ref2->tag)
7287                 return ref2->tag - ref1->tag;
7288         if (ref1->ltag != ref2->ltag)
7289                 return ref2->ltag - ref2->ltag;
7290         if (ref1->head != ref2->head)
7291                 return ref2->head - ref1->head;
7292         if (ref1->tracked != ref2->tracked)
7293                 return ref2->tracked - ref1->tracked;
7294         if (ref1->remote != ref2->remote)
7295                 return ref2->remote - ref1->remote;
7296         return strcmp(ref1->name, ref2->name);
7299 static void
7300 foreach_ref(bool (*visitor)(void *data, const struct ref *ref), void *data)
7302         size_t i;
7304         for (i = 0; i < refs_size; i++)
7305                 if (!visitor(data, refs[i]))
7306                         break;
7309 static struct ref *
7310 get_ref_head()
7312         return refs_head;
7315 static struct ref_list *
7316 get_ref_list(const char *id)
7318         struct ref_list *list;
7319         size_t i;
7321         for (i = 0; i < ref_lists_size; i++)
7322                 if (!strcmp(id, ref_lists[i]->id))
7323                         return ref_lists[i];
7325         if (!realloc_ref_lists(&ref_lists, ref_lists_size, 1))
7326                 return NULL;
7327         list = calloc(1, sizeof(*list));
7328         if (!list)
7329                 return NULL;
7331         for (i = 0; i < refs_size; i++) {
7332                 if (!strcmp(id, refs[i]->id) &&
7333                     realloc_refs_list(&list->refs, list->size, 1))
7334                         list->refs[list->size++] = refs[i];
7335         }
7337         if (!list->refs) {
7338                 free(list);
7339                 return NULL;
7340         }
7342         qsort(list->refs, list->size, sizeof(*list->refs), compare_refs);
7343         ref_lists[ref_lists_size++] = list;
7344         return list;
7347 static int
7348 read_ref(char *id, size_t idlen, char *name, size_t namelen)
7350         struct ref *ref = NULL;
7351         bool tag = FALSE;
7352         bool ltag = FALSE;
7353         bool remote = FALSE;
7354         bool tracked = FALSE;
7355         bool head = FALSE;
7356         int from = 0, to = refs_size - 1;
7358         if (!prefixcmp(name, "refs/tags/")) {
7359                 if (!suffixcmp(name, namelen, "^{}")) {
7360                         namelen -= 3;
7361                         name[namelen] = 0;
7362                 } else {
7363                         ltag = TRUE;
7364                 }
7366                 tag = TRUE;
7367                 namelen -= STRING_SIZE("refs/tags/");
7368                 name    += STRING_SIZE("refs/tags/");
7370         } else if (!prefixcmp(name, "refs/remotes/")) {
7371                 remote = TRUE;
7372                 namelen -= STRING_SIZE("refs/remotes/");
7373                 name    += STRING_SIZE("refs/remotes/");
7374                 tracked  = !strcmp(opt_remote, name);
7376         } else if (!prefixcmp(name, "refs/heads/")) {
7377                 namelen -= STRING_SIZE("refs/heads/");
7378                 name    += STRING_SIZE("refs/heads/");
7379                 if (!strncmp(opt_head, name, namelen))
7380                         return OK;
7382         } else if (!strcmp(name, "HEAD")) {
7383                 head     = TRUE;
7384                 if (*opt_head) {
7385                         namelen  = strlen(opt_head);
7386                         name     = opt_head;
7387                 }
7388         }
7390         /* If we are reloading or it's an annotated tag, replace the
7391          * previous SHA1 with the resolved commit id; relies on the fact
7392          * git-ls-remote lists the commit id of an annotated tag right
7393          * before the commit id it points to. */
7394         while (from <= to) {
7395                 size_t pos = (to + from) / 2;
7396                 int cmp = strcmp(name, refs[pos]->name);
7398                 if (!cmp) {
7399                         ref = refs[pos];
7400                         break;
7401                 }
7403                 if (cmp < 0)
7404                         to = pos - 1;
7405                 else
7406                         from = pos + 1;
7407         }
7409         if (!ref) {
7410                 if (!realloc_refs(&refs, refs_size, 1))
7411                         return ERR;
7412                 ref = calloc(1, sizeof(*ref) + namelen);
7413                 if (!ref)
7414                         return ERR;
7415                 memmove(refs + from + 1, refs + from,
7416                         (refs_size - from) * sizeof(*refs));
7417                 refs[from] = ref;
7418                 strncpy(ref->name, name, namelen);
7419                 refs_size++;
7420         }
7422         ref->head = head;
7423         ref->tag = tag;
7424         ref->ltag = ltag;
7425         ref->remote = remote;
7426         ref->tracked = tracked;
7427         string_copy_rev(ref->id, id);
7429         if (head)
7430                 refs_head = ref;
7431         return OK;
7434 static int
7435 load_refs(void)
7437         const char *head_argv[] = {
7438                 "git", "symbolic-ref", "HEAD", NULL
7439         };
7440         static const char *ls_remote_argv[SIZEOF_ARG] = {
7441                 "git", "ls-remote", opt_git_dir, NULL
7442         };
7443         static bool init = FALSE;
7444         size_t i;
7446         if (!init) {
7447                 if (!argv_from_env(ls_remote_argv, "TIG_LS_REMOTE"))
7448                         die("TIG_LS_REMOTE contains too many arguments");
7449                 init = TRUE;
7450         }
7452         if (!*opt_git_dir)
7453                 return OK;
7455         if (io_run_buf(head_argv, opt_head, sizeof(opt_head)) &&
7456             !prefixcmp(opt_head, "refs/heads/")) {
7457                 char *offset = opt_head + STRING_SIZE("refs/heads/");
7459                 memmove(opt_head, offset, strlen(offset) + 1);
7460         }
7462         refs_head = NULL;
7463         for (i = 0; i < refs_size; i++)
7464                 refs[i]->id[0] = 0;
7466         if (io_run_load(ls_remote_argv, "\t", read_ref) == ERR)
7467                 return ERR;
7469         /* Update the ref lists to reflect changes. */
7470         for (i = 0; i < ref_lists_size; i++) {
7471                 struct ref_list *list = ref_lists[i];
7472                 size_t old, new;
7474                 for (old = new = 0; old < list->size; old++)
7475                         if (!strcmp(list->id, list->refs[old]->id))
7476                                 list->refs[new++] = list->refs[old];
7477                 list->size = new;
7478         }
7480         return OK;
7483 static void
7484 set_remote_branch(const char *name, const char *value, size_t valuelen)
7486         if (!strcmp(name, ".remote")) {
7487                 string_ncopy(opt_remote, value, valuelen);
7489         } else if (*opt_remote && !strcmp(name, ".merge")) {
7490                 size_t from = strlen(opt_remote);
7492                 if (!prefixcmp(value, "refs/heads/"))
7493                         value += STRING_SIZE("refs/heads/");
7495                 if (!string_format_from(opt_remote, &from, "/%s", value))
7496                         opt_remote[0] = 0;
7497         }
7500 static void
7501 set_repo_config_option(char *name, char *value, int (*cmd)(int, const char **))
7503         const char *argv[SIZEOF_ARG] = { name, "=" };
7504         int argc = 1 + (cmd == option_set_command);
7505         int error = ERR;
7507         if (!argv_from_string(argv, &argc, value))
7508                 config_msg = "Too many option arguments";
7509         else
7510                 error = cmd(argc, argv);
7512         if (error == ERR)
7513                 warn("Option 'tig.%s': %s", name, config_msg);
7516 static bool
7517 set_environment_variable(const char *name, const char *value)
7519         size_t len = strlen(name) + 1 + strlen(value) + 1;
7520         char *env = malloc(len);
7522         if (env &&
7523             string_nformat(env, len, NULL, "%s=%s", name, value) &&
7524             putenv(env) == 0)
7525                 return TRUE;
7526         free(env);
7527         return FALSE;
7530 static void
7531 set_work_tree(const char *value)
7533         char cwd[SIZEOF_STR];
7535         if (!getcwd(cwd, sizeof(cwd)))
7536                 die("Failed to get cwd path: %s", strerror(errno));
7537         if (chdir(opt_git_dir) < 0)
7538                 die("Failed to chdir(%s): %s", strerror(errno));
7539         if (!getcwd(opt_git_dir, sizeof(opt_git_dir)))
7540                 die("Failed to get git path: %s", strerror(errno));
7541         if (chdir(cwd) < 0)
7542                 die("Failed to chdir(%s): %s", cwd, strerror(errno));
7543         if (chdir(value) < 0)
7544                 die("Failed to chdir(%s): %s", value, strerror(errno));
7545         if (!getcwd(cwd, sizeof(cwd)))
7546                 die("Failed to get cwd path: %s", strerror(errno));
7547         if (!set_environment_variable("GIT_WORK_TREE", cwd))
7548                 die("Failed to set GIT_WORK_TREE to '%s'", cwd);
7549         if (!set_environment_variable("GIT_DIR", opt_git_dir))
7550                 die("Failed to set GIT_DIR to '%s'", opt_git_dir);
7551         opt_is_inside_work_tree = TRUE;
7554 static int
7555 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
7557         if (!strcmp(name, "i18n.commitencoding"))
7558                 string_ncopy(opt_encoding, value, valuelen);
7560         else if (!strcmp(name, "core.editor"))
7561                 string_ncopy(opt_editor, value, valuelen);
7563         else if (!strcmp(name, "core.worktree"))
7564                 set_work_tree(value);
7566         else if (!prefixcmp(name, "tig.color."))
7567                 set_repo_config_option(name + 10, value, option_color_command);
7569         else if (!prefixcmp(name, "tig.bind."))
7570                 set_repo_config_option(name + 9, value, option_bind_command);
7572         else if (!prefixcmp(name, "tig."))
7573                 set_repo_config_option(name + 4, value, option_set_command);
7575         else if (*opt_head && !prefixcmp(name, "branch.") &&
7576                  !strncmp(name + 7, opt_head, strlen(opt_head)))
7577                 set_remote_branch(name + 7 + strlen(opt_head), value, valuelen);
7579         return OK;
7582 static int
7583 load_git_config(void)
7585         const char *config_list_argv[] = { "git", "config", "--list", NULL };
7587         return io_run_load(config_list_argv, "=", read_repo_config_option);
7590 static int
7591 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
7593         if (!opt_git_dir[0]) {
7594                 string_ncopy(opt_git_dir, name, namelen);
7596         } else if (opt_is_inside_work_tree == -1) {
7597                 /* This can be 3 different values depending on the
7598                  * version of git being used. If git-rev-parse does not
7599                  * understand --is-inside-work-tree it will simply echo
7600                  * the option else either "true" or "false" is printed.
7601                  * Default to true for the unknown case. */
7602                 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
7604         } else if (*name == '.') {
7605                 string_ncopy(opt_cdup, name, namelen);
7607         } else {
7608                 string_ncopy(opt_prefix, name, namelen);
7609         }
7611         return OK;
7614 static int
7615 load_repo_info(void)
7617         const char *rev_parse_argv[] = {
7618                 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
7619                         "--show-cdup", "--show-prefix", NULL
7620         };
7622         return io_run_load(rev_parse_argv, "=", read_repo_info);
7626 /*
7627  * Main
7628  */
7630 static const char usage[] =
7631 "tig " TIG_VERSION " (" __DATE__ ")\n"
7632 "\n"
7633 "Usage: tig        [options] [revs] [--] [paths]\n"
7634 "   or: tig show   [options] [revs] [--] [paths]\n"
7635 "   or: tig blame  [rev] path\n"
7636 "   or: tig status\n"
7637 "   or: tig <      [git command output]\n"
7638 "\n"
7639 "Options:\n"
7640 "  -v, --version   Show version and exit\n"
7641 "  -h, --help      Show help message and exit";
7643 static void __NORETURN
7644 quit(int sig)
7646         /* XXX: Restore tty modes and let the OS cleanup the rest! */
7647         if (cursed)
7648                 endwin();
7649         exit(0);
7652 static void __NORETURN
7653 die(const char *err, ...)
7655         va_list args;
7657         endwin();
7659         va_start(args, err);
7660         fputs("tig: ", stderr);
7661         vfprintf(stderr, err, args);
7662         fputs("\n", stderr);
7663         va_end(args);
7665         exit(1);
7668 static void
7669 warn(const char *msg, ...)
7671         va_list args;
7673         va_start(args, msg);
7674         fputs("tig warning: ", stderr);
7675         vfprintf(stderr, msg, args);
7676         fputs("\n", stderr);
7677         va_end(args);
7680 static const char ***filter_args;
7682 static int
7683 read_filter_args(char *name, size_t namelen, char *value, size_t valuelen)
7685         return argv_append(filter_args, name) ? OK : ERR;
7688 static void
7689 filter_rev_parse(const char ***args, const char *arg1, const char *arg2, const char *argv[])
7691         const char *rev_parse_argv[SIZEOF_ARG] = { "git", "rev-parse", arg1, arg2 };
7692         const char **all_argv = NULL;
7694         filter_args = args;
7695         if (!argv_append_array(&all_argv, rev_parse_argv) ||
7696             !argv_append_array(&all_argv, argv) ||
7697             !io_run_load(all_argv, "\n", read_filter_args) == ERR)
7698                 die("Failed to split arguments");
7699         argv_free(all_argv);
7700         free(all_argv);
7703 static void
7704 filter_options(const char *argv[])
7706         filter_rev_parse(&opt_file_args, "--no-revs", "--no-flags", argv);
7707         filter_rev_parse(&opt_diff_args, "--no-revs", "--flags", argv);
7708         filter_rev_parse(&opt_rev_args, "--symbolic", "--revs-only", argv);
7711 static enum request
7712 parse_options(int argc, const char *argv[])
7714         enum request request = REQ_VIEW_MAIN;
7715         const char *subcommand;
7716         bool seen_dashdash = FALSE;
7717         const char **filter_argv = NULL;
7718         int i;
7720         if (!isatty(STDIN_FILENO)) {
7721                 io_open(&VIEW(REQ_VIEW_PAGER)->io, "");
7722                 return REQ_VIEW_PAGER;
7723         }
7725         if (argc <= 1)
7726                 return REQ_VIEW_MAIN;
7728         subcommand = argv[1];
7729         if (!strcmp(subcommand, "status")) {
7730                 if (argc > 2)
7731                         warn("ignoring arguments after `%s'", subcommand);
7732                 return REQ_VIEW_STATUS;
7734         } else if (!strcmp(subcommand, "blame")) {
7735                 if (argc <= 2 || argc > 4)
7736                         die("invalid number of options to blame\n\n%s", usage);
7738                 i = 2;
7739                 if (argc == 4) {
7740                         string_ncopy(opt_ref, argv[i], strlen(argv[i]));
7741                         i++;
7742                 }
7744                 string_ncopy(opt_file, argv[i], strlen(argv[i]));
7745                 return REQ_VIEW_BLAME;
7747         } else if (!strcmp(subcommand, "show")) {
7748                 request = REQ_VIEW_DIFF;
7750         } else {
7751                 subcommand = NULL;
7752         }
7754         for (i = 1 + !!subcommand; i < argc; i++) {
7755                 const char *opt = argv[i];
7757                 if (seen_dashdash) {
7758                         argv_append(&opt_file_args, opt);
7759                         continue;
7761                 } else if (!strcmp(opt, "--")) {
7762                         seen_dashdash = TRUE;
7763                         continue;
7765                 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
7766                         printf("tig version %s\n", TIG_VERSION);
7767                         quit(0);
7769                 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
7770                         printf("%s\n", usage);
7771                         quit(0);
7773                 } else if (!strcmp(opt, "--all")) {
7774                         argv_append(&opt_rev_args, opt);
7775                         continue;
7776                 }
7778                 if (!argv_append(&filter_argv, opt))
7779                         die("command too long");
7780         }
7782         if (filter_argv)
7783                 filter_options(filter_argv);
7785         return request;
7788 int
7789 main(int argc, const char *argv[])
7791         const char *codeset = "UTF-8";
7792         enum request request = parse_options(argc, argv);
7793         struct view *view;
7794         size_t i;
7796         signal(SIGINT, quit);
7797         signal(SIGPIPE, SIG_IGN);
7799         if (setlocale(LC_ALL, "")) {
7800                 codeset = nl_langinfo(CODESET);
7801         }
7803         if (load_repo_info() == ERR)
7804                 die("Failed to load repo info.");
7806         if (load_options() == ERR)
7807                 die("Failed to load user config.");
7809         if (load_git_config() == ERR)
7810                 die("Failed to load repo config.");
7812         /* Require a git repository unless when running in pager mode. */
7813         if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
7814                 die("Not a git repository");
7816         if (*opt_encoding && strcmp(codeset, "UTF-8")) {
7817                 opt_iconv_in = iconv_open("UTF-8", opt_encoding);
7818                 if (opt_iconv_in == ICONV_NONE)
7819                         die("Failed to initialize character set conversion");
7820         }
7822         if (codeset && strcmp(codeset, "UTF-8")) {
7823                 opt_iconv_out = iconv_open(codeset, "UTF-8");
7824                 if (opt_iconv_out == ICONV_NONE)
7825                         die("Failed to initialize character set conversion");
7826         }
7828         if (load_refs() == ERR)
7829                 die("Failed to load refs.");
7831         foreach_view (view, i) {
7832                 if (getenv(view->cmd_env))
7833                         warn("Use of the %s environment variable is deprecated,"
7834                              " use options or TIG_DIFF_ARGS instead",
7835                              view->cmd_env);
7836                 if (!argv_from_env(view->ops->argv, view->cmd_env))
7837                         die("Too many arguments in the `%s` environment variable",
7838                             view->cmd_env);
7839         }
7841         init_display();
7843         while (view_driver(display[current_view], request)) {
7844                 int key = get_input(0);
7846                 view = display[current_view];
7847                 request = get_keybinding(view->keymap, key);
7849                 /* Some low-level request handling. This keeps access to
7850                  * status_win restricted. */
7851                 switch (request) {
7852                 case REQ_NONE:
7853                         report("Unknown key, press %s for help",
7854                                get_key(view->keymap, REQ_VIEW_HELP));
7855                         break;
7856                 case REQ_PROMPT:
7857                 {
7858                         char *cmd = read_prompt(":");
7860                         if (cmd && isdigit(*cmd)) {
7861                                 int lineno = view->lineno + 1;
7863                                 if (parse_int(&lineno, cmd, 1, view->lines + 1) == OK) {
7864                                         select_view_line(view, lineno - 1);
7865                                         report("");
7866                                 } else {
7867                                         report("Unable to parse '%s' as a line number", cmd);
7868                                 }
7870                         } else if (cmd) {
7871                                 struct view *next = VIEW(REQ_VIEW_PAGER);
7872                                 const char *argv[SIZEOF_ARG] = { "git" };
7873                                 int argc = 1;
7875                                 /* When running random commands, initially show the
7876                                  * command in the title. However, it maybe later be
7877                                  * overwritten if a commit line is selected. */
7878                                 string_ncopy(next->ref, cmd, strlen(cmd));
7880                                 if (!argv_from_string(argv, &argc, cmd)) {
7881                                         report("Too many arguments");
7882                                 } else if (!prepare_update(next, argv, NULL)) {
7883                                         report("Failed to format command");
7884                                 } else {
7885                                         open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
7886                                 }
7887                         }
7889                         request = REQ_NONE;
7890                         break;
7891                 }
7892                 case REQ_SEARCH:
7893                 case REQ_SEARCH_BACK:
7894                 {
7895                         const char *prompt = request == REQ_SEARCH ? "/" : "?";
7896                         char *search = read_prompt(prompt);
7898                         if (search)
7899                                 string_ncopy(opt_search, search, strlen(search));
7900                         else if (*opt_search)
7901                                 request = request == REQ_SEARCH ?
7902                                         REQ_FIND_NEXT :
7903                                         REQ_FIND_PREV;
7904                         else
7905                                 request = REQ_NONE;
7906                         break;
7907                 }
7908                 default:
7909                         break;
7910                 }
7911         }
7913         quit(0);
7915         return 0;