Code

Cleanup IO struct initialization
[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 void
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;
233 static char *
234 chomp_string(char *name)
236         int namelen;
238         while (isspace(*name))
239                 name++;
241         namelen = strlen(name) - 1;
242         while (namelen > 0 && isspace(name[namelen]))
243                 name[namelen--] = 0;
245         return name;
248 static bool
249 string_nformat(char *buf, size_t bufsize, size_t *bufpos, const char *fmt, ...)
251         va_list args;
252         size_t pos = bufpos ? *bufpos : 0;
254         va_start(args, fmt);
255         pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
256         va_end(args);
258         if (bufpos)
259                 *bufpos = pos;
261         return pos >= bufsize ? FALSE : TRUE;
264 #define string_format(buf, fmt, args...) \
265         string_nformat(buf, sizeof(buf), NULL, fmt, args)
267 #define string_format_from(buf, from, fmt, args...) \
268         string_nformat(buf, sizeof(buf), from, fmt, args)
270 static int
271 string_enum_compare(const char *str1, const char *str2, int len)
273         size_t i;
275 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
277         /* Diff-Header == DIFF_HEADER */
278         for (i = 0; i < len; i++) {
279                 if (toupper(str1[i]) == toupper(str2[i]))
280                         continue;
282                 if (string_enum_sep(str1[i]) &&
283                     string_enum_sep(str2[i]))
284                         continue;
286                 return str1[i] - str2[i];
287         }
289         return 0;
292 #define enum_equals(entry, str, len) \
293         ((entry).namelen == (len) && !string_enum_compare((entry).name, str, len))
295 struct enum_map {
296         const char *name;
297         int namelen;
298         int value;
299 };
301 #define ENUM_MAP(name, value) { name, STRING_SIZE(name), value }
303 static char *
304 enum_map_name(const char *name, size_t namelen)
306         static char buf[SIZEOF_STR];
307         int bufpos;
309         for (bufpos = 0; bufpos <= namelen; bufpos++) {
310                 buf[bufpos] = tolower(name[bufpos]);
311                 if (buf[bufpos] == '_')
312                         buf[bufpos] = '-';
313         }
315         buf[bufpos] = 0;
316         return buf;
319 #define enum_name(entry) enum_map_name((entry).name, (entry).namelen)
321 static bool
322 map_enum_do(const struct enum_map *map, size_t map_size, int *value, const char *name)
324         size_t namelen = strlen(name);
325         int i;
327         for (i = 0; i < map_size; i++)
328                 if (enum_equals(map[i], name, namelen)) {
329                         *value = map[i].value;
330                         return TRUE;
331                 }
333         return FALSE;
336 #define map_enum(attr, map, name) \
337         map_enum_do(map, ARRAY_SIZE(map), attr, name)
339 #define prefixcmp(str1, str2) \
340         strncmp(str1, str2, STRING_SIZE(str2))
342 static inline int
343 suffixcmp(const char *str, int slen, const char *suffix)
345         size_t len = slen >= 0 ? slen : strlen(str);
346         size_t suffixlen = strlen(suffix);
348         return suffixlen < len ? strcmp(str + len - suffixlen, suffix) : -1;
352 /*
353  * Unicode / UTF-8 handling
354  *
355  * NOTE: Much of the following code for dealing with Unicode is derived from
356  * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
357  * src/intl/charset.c from the UTF-8 branch commit elinks-0.11.0-g31f2c28.
358  */
360 static inline int
361 unicode_width(unsigned long c, int tab_size)
363         if (c >= 0x1100 &&
364            (c <= 0x115f                         /* Hangul Jamo */
365             || c == 0x2329
366             || c == 0x232a
367             || (c >= 0x2e80  && c <= 0xa4cf && c != 0x303f)
368                                                 /* CJK ... Yi */
369             || (c >= 0xac00  && c <= 0xd7a3)    /* Hangul Syllables */
370             || (c >= 0xf900  && c <= 0xfaff)    /* CJK Compatibility Ideographs */
371             || (c >= 0xfe30  && c <= 0xfe6f)    /* CJK Compatibility Forms */
372             || (c >= 0xff00  && c <= 0xff60)    /* Fullwidth Forms */
373             || (c >= 0xffe0  && c <= 0xffe6)
374             || (c >= 0x20000 && c <= 0x2fffd)
375             || (c >= 0x30000 && c <= 0x3fffd)))
376                 return 2;
378         if (c == '\t')
379                 return tab_size;
381         return 1;
384 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
385  * Illegal bytes are set one. */
386 static const unsigned char utf8_bytes[256] = {
387         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,
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         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,
394         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,
395 };
397 static inline unsigned char
398 utf8_char_length(const char *string, const char *end)
400         int c = *(unsigned char *) string;
402         return utf8_bytes[c];
405 /* Decode UTF-8 multi-byte representation into a Unicode character. */
406 static inline unsigned long
407 utf8_to_unicode(const char *string, size_t length)
409         unsigned long unicode;
411         switch (length) {
412         case 1:
413                 unicode  =   string[0];
414                 break;
415         case 2:
416                 unicode  =  (string[0] & 0x1f) << 6;
417                 unicode +=  (string[1] & 0x3f);
418                 break;
419         case 3:
420                 unicode  =  (string[0] & 0x0f) << 12;
421                 unicode += ((string[1] & 0x3f) << 6);
422                 unicode +=  (string[2] & 0x3f);
423                 break;
424         case 4:
425                 unicode  =  (string[0] & 0x0f) << 18;
426                 unicode += ((string[1] & 0x3f) << 12);
427                 unicode += ((string[2] & 0x3f) << 6);
428                 unicode +=  (string[3] & 0x3f);
429                 break;
430         case 5:
431                 unicode  =  (string[0] & 0x0f) << 24;
432                 unicode += ((string[1] & 0x3f) << 18);
433                 unicode += ((string[2] & 0x3f) << 12);
434                 unicode += ((string[3] & 0x3f) << 6);
435                 unicode +=  (string[4] & 0x3f);
436                 break;
437         case 6:
438                 unicode  =  (string[0] & 0x01) << 30;
439                 unicode += ((string[1] & 0x3f) << 24);
440                 unicode += ((string[2] & 0x3f) << 18);
441                 unicode += ((string[3] & 0x3f) << 12);
442                 unicode += ((string[4] & 0x3f) << 6);
443                 unicode +=  (string[5] & 0x3f);
444                 break;
445         default:
446                 return 0;
447         }
449         /* Invalid characters could return the special 0xfffd value but NUL
450          * should be just as good. */
451         return unicode > 0xffff ? 0 : unicode;
454 /* Calculates how much of string can be shown within the given maximum width
455  * and sets trimmed parameter to non-zero value if all of string could not be
456  * shown. If the reserve flag is TRUE, it will reserve at least one
457  * trailing character, which can be useful when drawing a delimiter.
458  *
459  * Returns the number of bytes to output from string to satisfy max_width. */
460 static size_t
461 utf8_length(const char **start, size_t skip, int *width, size_t max_width, int *trimmed, bool reserve, int tab_size)
463         const char *string = *start;
464         const char *end = strchr(string, '\0');
465         unsigned char last_bytes = 0;
466         size_t last_ucwidth = 0;
468         *width = 0;
469         *trimmed = 0;
471         while (string < end) {
472                 unsigned char bytes = utf8_char_length(string, end);
473                 size_t ucwidth;
474                 unsigned long unicode;
476                 if (string + bytes > end)
477                         break;
479                 /* Change representation to figure out whether
480                  * it is a single- or double-width character. */
482                 unicode = utf8_to_unicode(string, bytes);
483                 /* FIXME: Graceful handling of invalid Unicode character. */
484                 if (!unicode)
485                         break;
487                 ucwidth = unicode_width(unicode, tab_size);
488                 if (skip > 0) {
489                         skip -= ucwidth <= skip ? ucwidth : skip;
490                         *start += bytes;
491                 }
492                 *width  += ucwidth;
493                 if (*width > max_width) {
494                         *trimmed = 1;
495                         *width -= ucwidth;
496                         if (reserve && *width == max_width) {
497                                 string -= last_bytes;
498                                 *width -= last_ucwidth;
499                         }
500                         break;
501                 }
503                 string  += bytes;
504                 last_bytes = ucwidth ? bytes : 0;
505                 last_ucwidth = ucwidth;
506         }
508         return string - *start;
512 #define DATE_INFO \
513         DATE_(NO), \
514         DATE_(DEFAULT), \
515         DATE_(LOCAL), \
516         DATE_(RELATIVE), \
517         DATE_(SHORT)
519 enum date {
520 #define DATE_(name) DATE_##name
521         DATE_INFO
522 #undef  DATE_
523 };
525 static const struct enum_map date_map[] = {
526 #define DATE_(name) ENUM_MAP(#name, DATE_##name)
527         DATE_INFO
528 #undef  DATE_
529 };
531 struct time {
532         time_t sec;
533         int tz;
534 };
536 static inline int timecmp(const struct time *t1, const struct time *t2)
538         return t1->sec - t2->sec;
541 static const char *
542 mkdate(const struct time *time, enum date date)
544         static char buf[DATE_COLS + 1];
545         static const struct enum_map reldate[] = {
546                 { "second", 1,                  60 * 2 },
547                 { "minute", 60,                 60 * 60 * 2 },
548                 { "hour",   60 * 60,            60 * 60 * 24 * 2 },
549                 { "day",    60 * 60 * 24,       60 * 60 * 24 * 7 * 2 },
550                 { "week",   60 * 60 * 24 * 7,   60 * 60 * 24 * 7 * 5 },
551                 { "month",  60 * 60 * 24 * 30,  60 * 60 * 24 * 30 * 12 },
552         };
553         struct tm tm;
555         if (!date || !time || !time->sec)
556                 return "";
558         if (date == DATE_RELATIVE) {
559                 struct timeval now;
560                 time_t date = time->sec + time->tz;
561                 time_t seconds;
562                 int i;
564                 gettimeofday(&now, NULL);
565                 seconds = now.tv_sec < date ? date - now.tv_sec : now.tv_sec - date;
566                 for (i = 0; i < ARRAY_SIZE(reldate); i++) {
567                         if (seconds >= reldate[i].value)
568                                 continue;
570                         seconds /= reldate[i].namelen;
571                         if (!string_format(buf, "%ld %s%s %s",
572                                            seconds, reldate[i].name,
573                                            seconds > 1 ? "s" : "",
574                                            now.tv_sec >= date ? "ago" : "ahead"))
575                                 break;
576                         return buf;
577                 }
578         }
580         if (date == DATE_LOCAL) {
581                 time_t date = time->sec + time->tz;
582                 localtime_r(&date, &tm);
583         }
584         else {
585                 gmtime_r(&time->sec, &tm);
586         }
587         return strftime(buf, sizeof(buf), DATE_FORMAT, &tm) ? buf : NULL;
591 #define AUTHOR_VALUES \
592         AUTHOR_(NO), \
593         AUTHOR_(FULL), \
594         AUTHOR_(ABBREVIATED)
596 enum author {
597 #define AUTHOR_(name) AUTHOR_##name
598         AUTHOR_VALUES,
599 #undef  AUTHOR_
600         AUTHOR_DEFAULT = AUTHOR_FULL
601 };
603 static const struct enum_map author_map[] = {
604 #define AUTHOR_(name) ENUM_MAP(#name, AUTHOR_##name)
605         AUTHOR_VALUES
606 #undef  AUTHOR_
607 };
609 static const char *
610 get_author_initials(const char *author)
612         static char initials[AUTHOR_COLS * 6 + 1];
613         size_t pos = 0;
614         const char *end = strchr(author, '\0');
616 #define is_initial_sep(c) (isspace(c) || ispunct(c) || (c) == '@' || (c) == '-')
618         memset(initials, 0, sizeof(initials));
619         while (author < end) {
620                 unsigned char bytes;
621                 size_t i;
623                 while (is_initial_sep(*author))
624                         author++;
626                 bytes = utf8_char_length(author, end);
627                 if (bytes < sizeof(initials) - 1 - pos) {
628                         while (bytes--) {
629                                 initials[pos++] = *author++;
630                         }
631                 }
633                 for (i = pos; author < end && !is_initial_sep(*author); author++) {
634                         if (i < sizeof(initials) - 1)
635                                 initials[i++] = *author;
636                 }
638                 initials[i++] = 0;
639         }
641         return initials;
645 static bool
646 argv_from_string(const char *argv[SIZEOF_ARG], int *argc, char *cmd)
648         int valuelen;
650         while (*cmd && *argc < SIZEOF_ARG && (valuelen = strcspn(cmd, " \t"))) {
651                 bool advance = cmd[valuelen] != 0;
653                 cmd[valuelen] = 0;
654                 argv[(*argc)++] = chomp_string(cmd);
655                 cmd = chomp_string(cmd + valuelen + advance);
656         }
658         if (*argc < SIZEOF_ARG)
659                 argv[*argc] = NULL;
660         return *argc < SIZEOF_ARG;
663 static bool
664 argv_from_env(const char **argv, const char *name)
666         char *env = argv ? getenv(name) : NULL;
667         int argc = 0;
669         if (env && *env)
670                 env = strdup(env);
671         return !env || argv_from_string(argv, &argc, env);
674 static void
675 argv_free(const char *argv[])
677         int argc;
679         for (argc = 0; argv[argc]; argc++)
680                 free((void *) argv[argc]);
681         argv[0] = NULL;
684 static bool
685 argv_copy(const char *dst[], const char *src[])
687         int argc;
689         for (argc = 0; src[argc]; argc++)
690                 if (!(dst[argc] = strdup(src[argc])))
691                         return FALSE;
692         return TRUE;
696 /*
697  * Executing external commands.
698  */
700 enum io_type {
701         IO_FD,                  /* File descriptor based IO. */
702         IO_BG,                  /* Execute command in the background. */
703         IO_FG,                  /* Execute command with same std{in,out,err}. */
704         IO_RD,                  /* Read only fork+exec IO. */
705         IO_WR,                  /* Write only fork+exec IO. */
706         IO_AP,                  /* Append fork+exec output to file. */
707 };
709 struct io {
710         int pipe;               /* Pipe end for reading or writing. */
711         pid_t pid;              /* PID of spawned process. */
712         int error;              /* Error status. */
713         char *buf;              /* Read buffer. */
714         size_t bufalloc;        /* Allocated buffer size. */
715         size_t bufsize;         /* Buffer content size. */
716         char *bufpos;           /* Current buffer position. */
717         unsigned int eof:1;     /* Has end of file been reached. */
718 };
720 #define IO_INIT(fd) { fd }
722 static void
723 io_init(struct io *io)
725         memset(io, 0, sizeof(*io));
726         io->pipe = -1;
729 static bool
730 io_open(struct io *io, const char *fmt, ...)
732         char name[SIZEOF_STR] = "";
733         bool fits;
734         va_list args;
736         io_init(io);
738         va_start(args, fmt);
739         fits = vsnprintf(name, sizeof(name), fmt, args) < sizeof(name);
740         va_end(args);
742         if (!fits) {
743                 io->error = ENAMETOOLONG;
744                 return FALSE;
745         }
746         io->pipe = *name ? open(name, O_RDONLY) : STDIN_FILENO;
747         if (io->pipe == -1)
748                 io->error = errno;
749         return io->pipe != -1;
752 static bool
753 io_kill(struct io *io)
755         return io->pid == 0 || kill(io->pid, SIGKILL) != -1;
758 static bool
759 io_done(struct io *io)
761         pid_t pid = io->pid;
763         if (io->pipe != -1)
764                 close(io->pipe);
765         free(io->buf);
766         io_init(io);
768         while (pid > 0) {
769                 int status;
770                 pid_t waiting = waitpid(pid, &status, 0);
772                 if (waiting < 0) {
773                         if (errno == EINTR)
774                                 continue;
775                         io->error = errno;
776                         return FALSE;
777                 }
779                 return waiting == pid &&
780                        !WIFSIGNALED(status) &&
781                        WIFEXITED(status) &&
782                        !WEXITSTATUS(status);
783         }
785         return TRUE;
788 static bool
789 io_start(struct io *io, enum io_type type, const char *dir, const char *argv[])
791         int pipefds[2] = { -1, -1 };
793         if ((type == IO_RD || type == IO_WR) && pipe(pipefds) < 0) {
794                 io->error = errno;
795                 return FALSE;
796         } else if (type == IO_AP) {
797                 pipefds[1] = io->pipe;
798         }
800         if ((io->pid = fork())) {
801                 if (io->pid == -1)
802                         io->error = errno;
803                 if (pipefds[!(type == IO_WR)] != -1)
804                         close(pipefds[!(type == IO_WR)]);
805                 if (io->pid != -1) {
806                         io->pipe = pipefds[!!(type == IO_WR)];
807                         return TRUE;
808                 }
810         } else {
811                 if (type != IO_FG) {
812                         int devnull = open("/dev/null", O_RDWR);
813                         int readfd  = type == IO_WR ? pipefds[0] : devnull;
814                         int writefd = (type == IO_RD || type == IO_AP)
815                                                         ? pipefds[1] : devnull;
817                         dup2(readfd,  STDIN_FILENO);
818                         dup2(writefd, STDOUT_FILENO);
819                         dup2(devnull, STDERR_FILENO);
821                         close(devnull);
822                         if (pipefds[0] != -1)
823                                 close(pipefds[0]);
824                         if (pipefds[1] != -1)
825                                 close(pipefds[1]);
826                 }
828                 if (dir && *dir && chdir(dir) == -1)
829                         exit(errno);
831                 execvp(argv[0], (char *const*) argv);
832                 exit(errno);
833         }
835         if (pipefds[!!(type == IO_WR)] != -1)
836                 close(pipefds[!!(type == IO_WR)]);
837         return FALSE;
840 static bool
841 io_run(struct io *io, const char **argv, const char *dir, enum io_type type)
843         io_init(io);
844         return io_start(io, type, dir, argv);
847 static bool
848 io_complete(enum io_type type, const char **argv, const char *dir, int fd)
850         struct io io = IO_INIT(fd);
852         return io_start(&io, type, dir, argv) && io_done(&io);
855 static bool
856 io_run_bg(const char **argv)
858         return io_complete(IO_BG, argv, NULL, -1);
861 static bool
862 io_run_fg(const char **argv, const char *dir)
864         return io_complete(IO_FG, argv, dir, -1);
867 static bool
868 io_run_append(const char **argv, int fd)
870         return io_complete(IO_AP, argv, NULL, -1);
873 static bool
874 io_eof(struct io *io)
876         return io->eof;
879 static int
880 io_error(struct io *io)
882         return io->error;
885 static char *
886 io_strerror(struct io *io)
888         return strerror(io->error);
891 static bool
892 io_can_read(struct io *io)
894         struct timeval tv = { 0, 500 };
895         fd_set fds;
897         FD_ZERO(&fds);
898         FD_SET(io->pipe, &fds);
900         return select(io->pipe + 1, &fds, NULL, NULL, &tv) > 0;
903 static ssize_t
904 io_read(struct io *io, void *buf, size_t bufsize)
906         do {
907                 ssize_t readsize = read(io->pipe, buf, bufsize);
909                 if (readsize < 0 && (errno == EAGAIN || errno == EINTR))
910                         continue;
911                 else if (readsize == -1)
912                         io->error = errno;
913                 else if (readsize == 0)
914                         io->eof = 1;
915                 return readsize;
916         } while (1);
919 DEFINE_ALLOCATOR(io_realloc_buf, char, BUFSIZ)
921 static char *
922 io_get(struct io *io, int c, bool can_read)
924         char *eol;
925         ssize_t readsize;
927         while (TRUE) {
928                 if (io->bufsize > 0) {
929                         eol = memchr(io->bufpos, c, io->bufsize);
930                         if (eol) {
931                                 char *line = io->bufpos;
933                                 *eol = 0;
934                                 io->bufpos = eol + 1;
935                                 io->bufsize -= io->bufpos - line;
936                                 return line;
937                         }
938                 }
940                 if (io_eof(io)) {
941                         if (io->bufsize) {
942                                 io->bufpos[io->bufsize] = 0;
943                                 io->bufsize = 0;
944                                 return io->bufpos;
945                         }
946                         return NULL;
947                 }
949                 if (!can_read)
950                         return NULL;
952                 if (io->bufsize > 0 && io->bufpos > io->buf)
953                         memmove(io->buf, io->bufpos, io->bufsize);
955                 if (io->bufalloc == io->bufsize) {
956                         if (!io_realloc_buf(&io->buf, io->bufalloc, BUFSIZ))
957                                 return NULL;
958                         io->bufalloc += BUFSIZ;
959                 }
961                 io->bufpos = io->buf;
962                 readsize = io_read(io, io->buf + io->bufsize, io->bufalloc - io->bufsize);
963                 if (io_error(io))
964                         return NULL;
965                 io->bufsize += readsize;
966         }
969 static bool
970 io_write(struct io *io, const void *buf, size_t bufsize)
972         size_t written = 0;
974         while (!io_error(io) && written < bufsize) {
975                 ssize_t size;
977                 size = write(io->pipe, buf + written, bufsize - written);
978                 if (size < 0 && (errno == EAGAIN || errno == EINTR))
979                         continue;
980                 else if (size == -1)
981                         io->error = errno;
982                 else
983                         written += size;
984         }
986         return written == bufsize;
989 static bool
990 io_read_buf(struct io *io, char buf[], size_t bufsize)
992         char *result = io_get(io, '\n', TRUE);
994         if (result) {
995                 result = chomp_string(result);
996                 string_ncopy_do(buf, bufsize, result, strlen(result));
997         }
999         return io_done(io) && result;
1002 static bool
1003 io_run_buf(const char **argv, char buf[], size_t bufsize)
1005         struct io io = IO_INIT(-1);
1007         return io_start(&io, IO_RD, NULL, argv) && io_read_buf(&io, buf, bufsize);
1010 static int
1011 io_load(struct io *io, const char *separators,
1012         int (*read_property)(char *, size_t, char *, size_t))
1014         char *name;
1015         int state = OK;
1017         while (state == OK && (name = io_get(io, '\n', TRUE))) {
1018                 char *value;
1019                 size_t namelen;
1020                 size_t valuelen;
1022                 name = chomp_string(name);
1023                 namelen = strcspn(name, separators);
1025                 if (name[namelen]) {
1026                         name[namelen] = 0;
1027                         value = chomp_string(name + namelen + 1);
1028                         valuelen = strlen(value);
1030                 } else {
1031                         value = "";
1032                         valuelen = 0;
1033                 }
1035                 state = read_property(name, namelen, value, valuelen);
1036         }
1038         if (state != ERR && io_error(io))
1039                 state = ERR;
1040         io_done(io);
1042         return state;
1045 static int
1046 io_run_load(const char **argv, const char *separators,
1047             int (*read_property)(char *, size_t, char *, size_t))
1049         struct io io = IO_INIT(-1);
1051         if (!io_start(&io, IO_RD, NULL, argv))
1052                 return ERR;
1053         return io_load(&io, separators, read_property);
1057 /*
1058  * User requests
1059  */
1061 #define REQ_INFO \
1062         /* XXX: Keep the view request first and in sync with views[]. */ \
1063         REQ_GROUP("View switching") \
1064         REQ_(VIEW_MAIN,         "Show main view"), \
1065         REQ_(VIEW_DIFF,         "Show diff view"), \
1066         REQ_(VIEW_LOG,          "Show log view"), \
1067         REQ_(VIEW_TREE,         "Show tree view"), \
1068         REQ_(VIEW_BLOB,         "Show blob view"), \
1069         REQ_(VIEW_BLAME,        "Show blame view"), \
1070         REQ_(VIEW_BRANCH,       "Show branch view"), \
1071         REQ_(VIEW_HELP,         "Show help page"), \
1072         REQ_(VIEW_PAGER,        "Show pager view"), \
1073         REQ_(VIEW_STATUS,       "Show status view"), \
1074         REQ_(VIEW_STAGE,        "Show stage view"), \
1075         \
1076         REQ_GROUP("View manipulation") \
1077         REQ_(ENTER,             "Enter current line and scroll"), \
1078         REQ_(NEXT,              "Move to next"), \
1079         REQ_(PREVIOUS,          "Move to previous"), \
1080         REQ_(PARENT,            "Move to parent"), \
1081         REQ_(VIEW_NEXT,         "Move focus to next view"), \
1082         REQ_(REFRESH,           "Reload and refresh"), \
1083         REQ_(MAXIMIZE,          "Maximize the current view"), \
1084         REQ_(VIEW_CLOSE,        "Close the current view"), \
1085         REQ_(QUIT,              "Close all views and quit"), \
1086         \
1087         REQ_GROUP("View specific requests") \
1088         REQ_(STATUS_UPDATE,     "Update file status"), \
1089         REQ_(STATUS_REVERT,     "Revert file changes"), \
1090         REQ_(STATUS_MERGE,      "Merge file using external tool"), \
1091         REQ_(STAGE_NEXT,        "Find next chunk to stage"), \
1092         \
1093         REQ_GROUP("Cursor navigation") \
1094         REQ_(MOVE_UP,           "Move cursor one line up"), \
1095         REQ_(MOVE_DOWN,         "Move cursor one line down"), \
1096         REQ_(MOVE_PAGE_DOWN,    "Move cursor one page down"), \
1097         REQ_(MOVE_PAGE_UP,      "Move cursor one page up"), \
1098         REQ_(MOVE_FIRST_LINE,   "Move cursor to first line"), \
1099         REQ_(MOVE_LAST_LINE,    "Move cursor to last line"), \
1100         \
1101         REQ_GROUP("Scrolling") \
1102         REQ_(SCROLL_LEFT,       "Scroll two columns left"), \
1103         REQ_(SCROLL_RIGHT,      "Scroll two columns right"), \
1104         REQ_(SCROLL_LINE_UP,    "Scroll one line up"), \
1105         REQ_(SCROLL_LINE_DOWN,  "Scroll one line down"), \
1106         REQ_(SCROLL_PAGE_UP,    "Scroll one page up"), \
1107         REQ_(SCROLL_PAGE_DOWN,  "Scroll one page down"), \
1108         \
1109         REQ_GROUP("Searching") \
1110         REQ_(SEARCH,            "Search the view"), \
1111         REQ_(SEARCH_BACK,       "Search backwards in the view"), \
1112         REQ_(FIND_NEXT,         "Find next search match"), \
1113         REQ_(FIND_PREV,         "Find previous search match"), \
1114         \
1115         REQ_GROUP("Option manipulation") \
1116         REQ_(OPTIONS,           "Open option menu"), \
1117         REQ_(TOGGLE_LINENO,     "Toggle line numbers"), \
1118         REQ_(TOGGLE_DATE,       "Toggle date display"), \
1119         REQ_(TOGGLE_DATE_SHORT, "Toggle short (date-only) dates"), \
1120         REQ_(TOGGLE_AUTHOR,     "Toggle author display"), \
1121         REQ_(TOGGLE_REV_GRAPH,  "Toggle revision graph visualization"), \
1122         REQ_(TOGGLE_REFS,       "Toggle reference display (tags/branches)"), \
1123         REQ_(TOGGLE_SORT_ORDER, "Toggle ascending/descending sort order"), \
1124         REQ_(TOGGLE_SORT_FIELD, "Toggle field to sort by"), \
1125         \
1126         REQ_GROUP("Misc") \
1127         REQ_(PROMPT,            "Bring up the prompt"), \
1128         REQ_(SCREEN_REDRAW,     "Redraw the screen"), \
1129         REQ_(SHOW_VERSION,      "Show version information"), \
1130         REQ_(STOP_LOADING,      "Stop all loading views"), \
1131         REQ_(EDIT,              "Open in editor"), \
1132         REQ_(NONE,              "Do nothing")
1135 /* User action requests. */
1136 enum request {
1137 #define REQ_GROUP(help)
1138 #define REQ_(req, help) REQ_##req
1140         /* Offset all requests to avoid conflicts with ncurses getch values. */
1141         REQ_UNKNOWN = KEY_MAX + 1,
1142         REQ_OFFSET,
1143         REQ_INFO
1145 #undef  REQ_GROUP
1146 #undef  REQ_
1147 };
1149 struct request_info {
1150         enum request request;
1151         const char *name;
1152         int namelen;
1153         const char *help;
1154 };
1156 static const struct request_info req_info[] = {
1157 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
1158 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
1159         REQ_INFO
1160 #undef  REQ_GROUP
1161 #undef  REQ_
1162 };
1164 static enum request
1165 get_request(const char *name)
1167         int namelen = strlen(name);
1168         int i;
1170         for (i = 0; i < ARRAY_SIZE(req_info); i++)
1171                 if (enum_equals(req_info[i], name, namelen))
1172                         return req_info[i].request;
1174         return REQ_UNKNOWN;
1178 /*
1179  * Options
1180  */
1182 /* Option and state variables. */
1183 static enum date opt_date               = DATE_DEFAULT;
1184 static enum author opt_author           = AUTHOR_DEFAULT;
1185 static bool opt_line_number             = FALSE;
1186 static bool opt_line_graphics           = TRUE;
1187 static bool opt_rev_graph               = FALSE;
1188 static bool opt_show_refs               = TRUE;
1189 static int opt_num_interval             = 5;
1190 static double opt_hscroll               = 0.50;
1191 static double opt_scale_split_view      = 2.0 / 3.0;
1192 static int opt_tab_size                 = 8;
1193 static int opt_author_cols              = AUTHOR_COLS;
1194 static char opt_path[SIZEOF_STR]        = "";
1195 static char opt_file[SIZEOF_STR]        = "";
1196 static char opt_ref[SIZEOF_REF]         = "";
1197 static char opt_head[SIZEOF_REF]        = "";
1198 static char opt_remote[SIZEOF_REF]      = "";
1199 static char opt_encoding[20]            = "UTF-8";
1200 static iconv_t opt_iconv_in             = ICONV_NONE;
1201 static iconv_t opt_iconv_out            = ICONV_NONE;
1202 static char opt_search[SIZEOF_STR]      = "";
1203 static char opt_cdup[SIZEOF_STR]        = "";
1204 static char opt_prefix[SIZEOF_STR]      = "";
1205 static char opt_git_dir[SIZEOF_STR]     = "";
1206 static signed char opt_is_inside_work_tree      = -1; /* set to TRUE or FALSE */
1207 static char opt_editor[SIZEOF_STR]      = "";
1208 static FILE *opt_tty                    = NULL;
1210 #define is_initial_commit()     (!get_ref_head())
1211 #define is_head_commit(rev)     (!strcmp((rev), "HEAD") || (get_ref_head() && !strcmp(rev, get_ref_head()->id)))
1214 /*
1215  * Line-oriented content detection.
1216  */
1218 #define LINE_INFO \
1219 LINE(DIFF_HEADER,  "diff --git ",       COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1220 LINE(DIFF_CHUNK,   "@@",                COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1221 LINE(DIFF_ADD,     "+",                 COLOR_GREEN,    COLOR_DEFAULT,  0), \
1222 LINE(DIFF_DEL,     "-",                 COLOR_RED,      COLOR_DEFAULT,  0), \
1223 LINE(DIFF_INDEX,        "index ",         COLOR_BLUE,   COLOR_DEFAULT,  0), \
1224 LINE(DIFF_OLDMODE,      "old file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
1225 LINE(DIFF_NEWMODE,      "new file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
1226 LINE(DIFF_COPY_FROM,    "copy from",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
1227 LINE(DIFF_COPY_TO,      "copy to",        COLOR_YELLOW, COLOR_DEFAULT,  0), \
1228 LINE(DIFF_RENAME_FROM,  "rename from",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
1229 LINE(DIFF_RENAME_TO,    "rename to",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
1230 LINE(DIFF_SIMILARITY,   "similarity ",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
1231 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
1232 LINE(DIFF_TREE,         "diff-tree ",     COLOR_BLUE,   COLOR_DEFAULT,  0), \
1233 LINE(PP_AUTHOR,    "Author: ",          COLOR_CYAN,     COLOR_DEFAULT,  0), \
1234 LINE(PP_COMMIT,    "Commit: ",          COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1235 LINE(PP_MERGE,     "Merge: ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
1236 LINE(PP_DATE,      "Date:   ",          COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1237 LINE(PP_ADATE,     "AuthorDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1238 LINE(PP_CDATE,     "CommitDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1239 LINE(PP_REFS,      "Refs: ",            COLOR_RED,      COLOR_DEFAULT,  0), \
1240 LINE(COMMIT,       "commit ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
1241 LINE(PARENT,       "parent ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
1242 LINE(TREE,         "tree ",             COLOR_BLUE,     COLOR_DEFAULT,  0), \
1243 LINE(AUTHOR,       "author ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
1244 LINE(COMMITTER,    "committer ",        COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1245 LINE(SIGNOFF,      "    Signed-off-by", COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1246 LINE(ACKED,        "    Acked-by",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1247 LINE(TESTED,       "    Tested-by",     COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1248 LINE(REVIEWED,     "    Reviewed-by",   COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1249 LINE(DEFAULT,      "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
1250 LINE(CURSOR,       "",                  COLOR_WHITE,    COLOR_GREEN,    A_BOLD), \
1251 LINE(STATUS,       "",                  COLOR_GREEN,    COLOR_DEFAULT,  0), \
1252 LINE(DELIMITER,    "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1253 LINE(DATE,         "",                  COLOR_BLUE,     COLOR_DEFAULT,  0), \
1254 LINE(MODE,         "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1255 LINE(LINE_NUMBER,  "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1256 LINE(TITLE_BLUR,   "",                  COLOR_WHITE,    COLOR_BLUE,     0), \
1257 LINE(TITLE_FOCUS,  "",                  COLOR_WHITE,    COLOR_BLUE,     A_BOLD), \
1258 LINE(MAIN_COMMIT,  "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
1259 LINE(MAIN_TAG,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  A_BOLD), \
1260 LINE(MAIN_LOCAL_TAG,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1261 LINE(MAIN_REMOTE,  "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1262 LINE(MAIN_TRACKED, "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_BOLD), \
1263 LINE(MAIN_REF,     "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1264 LINE(MAIN_HEAD,    "",                  COLOR_CYAN,     COLOR_DEFAULT,  A_BOLD), \
1265 LINE(MAIN_REVGRAPH,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1266 LINE(TREE_HEAD,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_BOLD), \
1267 LINE(TREE_DIR,     "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_NORMAL), \
1268 LINE(TREE_FILE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
1269 LINE(STAT_HEAD,    "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1270 LINE(STAT_SECTION, "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1271 LINE(STAT_NONE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
1272 LINE(STAT_STAGED,  "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1273 LINE(STAT_UNSTAGED,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1274 LINE(STAT_UNTRACKED,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1275 LINE(HELP_KEYMAP,  "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1276 LINE(HELP_GROUP,   "",                  COLOR_BLUE,     COLOR_DEFAULT,  0), \
1277 LINE(BLAME_ID,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0)
1279 enum line_type {
1280 #define LINE(type, line, fg, bg, attr) \
1281         LINE_##type
1282         LINE_INFO,
1283         LINE_NONE
1284 #undef  LINE
1285 };
1287 struct line_info {
1288         const char *name;       /* Option name. */
1289         int namelen;            /* Size of option name. */
1290         const char *line;       /* The start of line to match. */
1291         int linelen;            /* Size of string to match. */
1292         int fg, bg, attr;       /* Color and text attributes for the lines. */
1293 };
1295 static struct line_info line_info[] = {
1296 #define LINE(type, line, fg, bg, attr) \
1297         { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
1298         LINE_INFO
1299 #undef  LINE
1300 };
1302 static enum line_type
1303 get_line_type(const char *line)
1305         int linelen = strlen(line);
1306         enum line_type type;
1308         for (type = 0; type < ARRAY_SIZE(line_info); type++)
1309                 /* Case insensitive search matches Signed-off-by lines better. */
1310                 if (linelen >= line_info[type].linelen &&
1311                     !strncasecmp(line_info[type].line, line, line_info[type].linelen))
1312                         return type;
1314         return LINE_DEFAULT;
1317 static inline int
1318 get_line_attr(enum line_type type)
1320         assert(type < ARRAY_SIZE(line_info));
1321         return COLOR_PAIR(type) | line_info[type].attr;
1324 static struct line_info *
1325 get_line_info(const char *name)
1327         size_t namelen = strlen(name);
1328         enum line_type type;
1330         for (type = 0; type < ARRAY_SIZE(line_info); type++)
1331                 if (enum_equals(line_info[type], name, namelen))
1332                         return &line_info[type];
1334         return NULL;
1337 static void
1338 init_colors(void)
1340         int default_bg = line_info[LINE_DEFAULT].bg;
1341         int default_fg = line_info[LINE_DEFAULT].fg;
1342         enum line_type type;
1344         start_color();
1346         if (assume_default_colors(default_fg, default_bg) == ERR) {
1347                 default_bg = COLOR_BLACK;
1348                 default_fg = COLOR_WHITE;
1349         }
1351         for (type = 0; type < ARRAY_SIZE(line_info); type++) {
1352                 struct line_info *info = &line_info[type];
1353                 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
1354                 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
1356                 init_pair(type, fg, bg);
1357         }
1360 struct line {
1361         enum line_type type;
1363         /* State flags */
1364         unsigned int selected:1;
1365         unsigned int dirty:1;
1366         unsigned int cleareol:1;
1367         unsigned int other:16;
1369         void *data;             /* User data */
1370 };
1373 /*
1374  * Keys
1375  */
1377 struct keybinding {
1378         int alias;
1379         enum request request;
1380 };
1382 static struct keybinding default_keybindings[] = {
1383         /* View switching */
1384         { 'm',          REQ_VIEW_MAIN },
1385         { 'd',          REQ_VIEW_DIFF },
1386         { 'l',          REQ_VIEW_LOG },
1387         { 't',          REQ_VIEW_TREE },
1388         { 'f',          REQ_VIEW_BLOB },
1389         { 'B',          REQ_VIEW_BLAME },
1390         { 'H',          REQ_VIEW_BRANCH },
1391         { 'p',          REQ_VIEW_PAGER },
1392         { 'h',          REQ_VIEW_HELP },
1393         { 'S',          REQ_VIEW_STATUS },
1394         { 'c',          REQ_VIEW_STAGE },
1396         /* View manipulation */
1397         { 'q',          REQ_VIEW_CLOSE },
1398         { KEY_TAB,      REQ_VIEW_NEXT },
1399         { KEY_RETURN,   REQ_ENTER },
1400         { KEY_UP,       REQ_PREVIOUS },
1401         { KEY_DOWN,     REQ_NEXT },
1402         { 'R',          REQ_REFRESH },
1403         { KEY_F(5),     REQ_REFRESH },
1404         { 'O',          REQ_MAXIMIZE },
1406         /* Cursor navigation */
1407         { 'k',          REQ_MOVE_UP },
1408         { 'j',          REQ_MOVE_DOWN },
1409         { KEY_HOME,     REQ_MOVE_FIRST_LINE },
1410         { KEY_END,      REQ_MOVE_LAST_LINE },
1411         { KEY_NPAGE,    REQ_MOVE_PAGE_DOWN },
1412         { ' ',          REQ_MOVE_PAGE_DOWN },
1413         { KEY_PPAGE,    REQ_MOVE_PAGE_UP },
1414         { 'b',          REQ_MOVE_PAGE_UP },
1415         { '-',          REQ_MOVE_PAGE_UP },
1417         /* Scrolling */
1418         { KEY_LEFT,     REQ_SCROLL_LEFT },
1419         { KEY_RIGHT,    REQ_SCROLL_RIGHT },
1420         { KEY_IC,       REQ_SCROLL_LINE_UP },
1421         { KEY_DC,       REQ_SCROLL_LINE_DOWN },
1422         { 'w',          REQ_SCROLL_PAGE_UP },
1423         { 's',          REQ_SCROLL_PAGE_DOWN },
1425         /* Searching */
1426         { '/',          REQ_SEARCH },
1427         { '?',          REQ_SEARCH_BACK },
1428         { 'n',          REQ_FIND_NEXT },
1429         { 'N',          REQ_FIND_PREV },
1431         /* Misc */
1432         { 'Q',          REQ_QUIT },
1433         { 'z',          REQ_STOP_LOADING },
1434         { 'v',          REQ_SHOW_VERSION },
1435         { 'r',          REQ_SCREEN_REDRAW },
1436         { 'o',          REQ_OPTIONS },
1437         { '.',          REQ_TOGGLE_LINENO },
1438         { 'D',          REQ_TOGGLE_DATE },
1439         { 'A',          REQ_TOGGLE_AUTHOR },
1440         { 'g',          REQ_TOGGLE_REV_GRAPH },
1441         { 'F',          REQ_TOGGLE_REFS },
1442         { 'I',          REQ_TOGGLE_SORT_ORDER },
1443         { 'i',          REQ_TOGGLE_SORT_FIELD },
1444         { ':',          REQ_PROMPT },
1445         { 'u',          REQ_STATUS_UPDATE },
1446         { '!',          REQ_STATUS_REVERT },
1447         { 'M',          REQ_STATUS_MERGE },
1448         { '@',          REQ_STAGE_NEXT },
1449         { ',',          REQ_PARENT },
1450         { 'e',          REQ_EDIT },
1451 };
1453 #define KEYMAP_INFO \
1454         KEYMAP_(GENERIC), \
1455         KEYMAP_(MAIN), \
1456         KEYMAP_(DIFF), \
1457         KEYMAP_(LOG), \
1458         KEYMAP_(TREE), \
1459         KEYMAP_(BLOB), \
1460         KEYMAP_(BLAME), \
1461         KEYMAP_(BRANCH), \
1462         KEYMAP_(PAGER), \
1463         KEYMAP_(HELP), \
1464         KEYMAP_(STATUS), \
1465         KEYMAP_(STAGE)
1467 enum keymap {
1468 #define KEYMAP_(name) KEYMAP_##name
1469         KEYMAP_INFO
1470 #undef  KEYMAP_
1471 };
1473 static const struct enum_map keymap_table[] = {
1474 #define KEYMAP_(name) ENUM_MAP(#name, KEYMAP_##name)
1475         KEYMAP_INFO
1476 #undef  KEYMAP_
1477 };
1479 #define set_keymap(map, name) map_enum(map, keymap_table, name)
1481 struct keybinding_table {
1482         struct keybinding *data;
1483         size_t size;
1484 };
1486 static struct keybinding_table keybindings[ARRAY_SIZE(keymap_table)];
1488 static void
1489 add_keybinding(enum keymap keymap, enum request request, int key)
1491         struct keybinding_table *table = &keybindings[keymap];
1492         size_t i;
1494         for (i = 0; i < keybindings[keymap].size; i++) {
1495                 if (keybindings[keymap].data[i].alias == key) {
1496                         keybindings[keymap].data[i].request = request;
1497                         return;
1498                 }
1499         }
1501         table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data));
1502         if (!table->data)
1503                 die("Failed to allocate keybinding");
1504         table->data[table->size].alias = key;
1505         table->data[table->size++].request = request;
1507         if (request == REQ_NONE && keymap == KEYMAP_GENERIC) {
1508                 int i;
1510                 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1511                         if (default_keybindings[i].alias == key)
1512                                 default_keybindings[i].request = REQ_NONE;
1513         }
1516 /* Looks for a key binding first in the given map, then in the generic map, and
1517  * lastly in the default keybindings. */
1518 static enum request
1519 get_keybinding(enum keymap keymap, int key)
1521         size_t i;
1523         for (i = 0; i < keybindings[keymap].size; i++)
1524                 if (keybindings[keymap].data[i].alias == key)
1525                         return keybindings[keymap].data[i].request;
1527         for (i = 0; i < keybindings[KEYMAP_GENERIC].size; i++)
1528                 if (keybindings[KEYMAP_GENERIC].data[i].alias == key)
1529                         return keybindings[KEYMAP_GENERIC].data[i].request;
1531         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1532                 if (default_keybindings[i].alias == key)
1533                         return default_keybindings[i].request;
1535         return (enum request) key;
1539 struct key {
1540         const char *name;
1541         int value;
1542 };
1544 static const struct key key_table[] = {
1545         { "Enter",      KEY_RETURN },
1546         { "Space",      ' ' },
1547         { "Backspace",  KEY_BACKSPACE },
1548         { "Tab",        KEY_TAB },
1549         { "Escape",     KEY_ESC },
1550         { "Left",       KEY_LEFT },
1551         { "Right",      KEY_RIGHT },
1552         { "Up",         KEY_UP },
1553         { "Down",       KEY_DOWN },
1554         { "Insert",     KEY_IC },
1555         { "Delete",     KEY_DC },
1556         { "Hash",       '#' },
1557         { "Home",       KEY_HOME },
1558         { "End",        KEY_END },
1559         { "PageUp",     KEY_PPAGE },
1560         { "PageDown",   KEY_NPAGE },
1561         { "F1",         KEY_F(1) },
1562         { "F2",         KEY_F(2) },
1563         { "F3",         KEY_F(3) },
1564         { "F4",         KEY_F(4) },
1565         { "F5",         KEY_F(5) },
1566         { "F6",         KEY_F(6) },
1567         { "F7",         KEY_F(7) },
1568         { "F8",         KEY_F(8) },
1569         { "F9",         KEY_F(9) },
1570         { "F10",        KEY_F(10) },
1571         { "F11",        KEY_F(11) },
1572         { "F12",        KEY_F(12) },
1573 };
1575 static int
1576 get_key_value(const char *name)
1578         int i;
1580         for (i = 0; i < ARRAY_SIZE(key_table); i++)
1581                 if (!strcasecmp(key_table[i].name, name))
1582                         return key_table[i].value;
1584         if (strlen(name) == 1 && isprint(*name))
1585                 return (int) *name;
1587         return ERR;
1590 static const char *
1591 get_key_name(int key_value)
1593         static char key_char[] = "'X'";
1594         const char *seq = NULL;
1595         int key;
1597         for (key = 0; key < ARRAY_SIZE(key_table); key++)
1598                 if (key_table[key].value == key_value)
1599                         seq = key_table[key].name;
1601         if (seq == NULL &&
1602             key_value < 127 &&
1603             isprint(key_value)) {
1604                 key_char[1] = (char) key_value;
1605                 seq = key_char;
1606         }
1608         return seq ? seq : "(no key)";
1611 static bool
1612 append_key(char *buf, size_t *pos, const struct keybinding *keybinding)
1614         const char *sep = *pos > 0 ? ", " : "";
1615         const char *keyname = get_key_name(keybinding->alias);
1617         return string_nformat(buf, BUFSIZ, pos, "%s%s", sep, keyname);
1620 static bool
1621 append_keymap_request_keys(char *buf, size_t *pos, enum request request,
1622                            enum keymap keymap, bool all)
1624         int i;
1626         for (i = 0; i < keybindings[keymap].size; i++) {
1627                 if (keybindings[keymap].data[i].request == request) {
1628                         if (!append_key(buf, pos, &keybindings[keymap].data[i]))
1629                                 return FALSE;
1630                         if (!all)
1631                                 break;
1632                 }
1633         }
1635         return TRUE;
1638 #define get_key(keymap, request) get_keys(keymap, request, FALSE)
1640 static const char *
1641 get_keys(enum keymap keymap, enum request request, bool all)
1643         static char buf[BUFSIZ];
1644         size_t pos = 0;
1645         int i;
1647         buf[pos] = 0;
1649         if (!append_keymap_request_keys(buf, &pos, request, keymap, all))
1650                 return "Too many keybindings!";
1651         if (pos > 0 && !all)
1652                 return buf;
1654         if (keymap != KEYMAP_GENERIC) {
1655                 /* Only the generic keymap includes the default keybindings when
1656                  * listing all keys. */
1657                 if (all)
1658                         return buf;
1660                 if (!append_keymap_request_keys(buf, &pos, request, KEYMAP_GENERIC, all))
1661                         return "Too many keybindings!";
1662                 if (pos)
1663                         return buf;
1664         }
1666         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1667                 if (default_keybindings[i].request == request) {
1668                         if (!append_key(buf, &pos, &default_keybindings[i]))
1669                                 return "Too many keybindings!";
1670                         if (!all)
1671                                 return buf;
1672                 }
1673         }
1675         return buf;
1678 struct run_request {
1679         enum keymap keymap;
1680         int key;
1681         const char *argv[SIZEOF_ARG];
1682 };
1684 static struct run_request *run_request;
1685 static size_t run_requests;
1687 DEFINE_ALLOCATOR(realloc_run_requests, struct run_request, 8)
1689 static enum request
1690 add_run_request(enum keymap keymap, int key, int argc, const char **argv)
1692         struct run_request *req;
1694         if (argc >= ARRAY_SIZE(req->argv) - 1)
1695                 return REQ_NONE;
1697         if (!realloc_run_requests(&run_request, run_requests, 1))
1698                 return REQ_NONE;
1700         req = &run_request[run_requests];
1701         req->keymap = keymap;
1702         req->key = key;
1703         req->argv[0] = NULL;
1705         if (!argv_copy(req->argv, argv))
1706                 return REQ_NONE;
1708         return REQ_NONE + ++run_requests;
1711 static struct run_request *
1712 get_run_request(enum request request)
1714         if (request <= REQ_NONE)
1715                 return NULL;
1716         return &run_request[request - REQ_NONE - 1];
1719 static void
1720 add_builtin_run_requests(void)
1722         const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1723         const char *checkout[] = { "git", "checkout", "%(branch)", NULL };
1724         const char *commit[] = { "git", "commit", NULL };
1725         const char *gc[] = { "git", "gc", NULL };
1726         struct {
1727                 enum keymap keymap;
1728                 int key;
1729                 int argc;
1730                 const char **argv;
1731         } reqs[] = {
1732                 { KEYMAP_MAIN,    'C', ARRAY_SIZE(cherry_pick) - 1, cherry_pick },
1733                 { KEYMAP_STATUS,  'C', ARRAY_SIZE(commit) - 1, commit },
1734                 { KEYMAP_BRANCH,  'C', ARRAY_SIZE(checkout) - 1, checkout },
1735                 { KEYMAP_GENERIC, 'G', ARRAY_SIZE(gc) - 1, gc },
1736         };
1737         int i;
1739         for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1740                 enum request req = get_keybinding(reqs[i].keymap, reqs[i].key);
1742                 if (req != reqs[i].key)
1743                         continue;
1744                 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argc, reqs[i].argv);
1745                 if (req != REQ_NONE)
1746                         add_keybinding(reqs[i].keymap, req, reqs[i].key);
1747         }
1750 /*
1751  * User config file handling.
1752  */
1754 static int   config_lineno;
1755 static bool  config_errors;
1756 static const char *config_msg;
1758 static const struct enum_map color_map[] = {
1759 #define COLOR_MAP(name) ENUM_MAP(#name, COLOR_##name)
1760         COLOR_MAP(DEFAULT),
1761         COLOR_MAP(BLACK),
1762         COLOR_MAP(BLUE),
1763         COLOR_MAP(CYAN),
1764         COLOR_MAP(GREEN),
1765         COLOR_MAP(MAGENTA),
1766         COLOR_MAP(RED),
1767         COLOR_MAP(WHITE),
1768         COLOR_MAP(YELLOW),
1769 };
1771 static const struct enum_map attr_map[] = {
1772 #define ATTR_MAP(name) ENUM_MAP(#name, A_##name)
1773         ATTR_MAP(NORMAL),
1774         ATTR_MAP(BLINK),
1775         ATTR_MAP(BOLD),
1776         ATTR_MAP(DIM),
1777         ATTR_MAP(REVERSE),
1778         ATTR_MAP(STANDOUT),
1779         ATTR_MAP(UNDERLINE),
1780 };
1782 #define set_attribute(attr, name)       map_enum(attr, attr_map, name)
1784 static int parse_step(double *opt, const char *arg)
1786         *opt = atoi(arg);
1787         if (!strchr(arg, '%'))
1788                 return OK;
1790         /* "Shift down" so 100% and 1 does not conflict. */
1791         *opt = (*opt - 1) / 100;
1792         if (*opt >= 1.0) {
1793                 *opt = 0.99;
1794                 config_msg = "Step value larger than 100%";
1795                 return ERR;
1796         }
1797         if (*opt < 0.0) {
1798                 *opt = 1;
1799                 config_msg = "Invalid step value";
1800                 return ERR;
1801         }
1802         return OK;
1805 static int
1806 parse_int(int *opt, const char *arg, int min, int max)
1808         int value = atoi(arg);
1810         if (min <= value && value <= max) {
1811                 *opt = value;
1812                 return OK;
1813         }
1815         config_msg = "Integer value out of bound";
1816         return ERR;
1819 static bool
1820 set_color(int *color, const char *name)
1822         if (map_enum(color, color_map, name))
1823                 return TRUE;
1824         if (!prefixcmp(name, "color"))
1825                 return parse_int(color, name + 5, 0, 255) == OK;
1826         return FALSE;
1829 /* Wants: object fgcolor bgcolor [attribute] */
1830 static int
1831 option_color_command(int argc, const char *argv[])
1833         struct line_info *info;
1835         if (argc < 3) {
1836                 config_msg = "Wrong number of arguments given to color command";
1837                 return ERR;
1838         }
1840         info = get_line_info(argv[0]);
1841         if (!info) {
1842                 static const struct enum_map obsolete[] = {
1843                         ENUM_MAP("main-delim",  LINE_DELIMITER),
1844                         ENUM_MAP("main-date",   LINE_DATE),
1845                         ENUM_MAP("main-author", LINE_AUTHOR),
1846                 };
1847                 int index;
1849                 if (!map_enum(&index, obsolete, argv[0])) {
1850                         config_msg = "Unknown color name";
1851                         return ERR;
1852                 }
1853                 info = &line_info[index];
1854         }
1856         if (!set_color(&info->fg, argv[1]) ||
1857             !set_color(&info->bg, argv[2])) {
1858                 config_msg = "Unknown color";
1859                 return ERR;
1860         }
1862         info->attr = 0;
1863         while (argc-- > 3) {
1864                 int attr;
1866                 if (!set_attribute(&attr, argv[argc])) {
1867                         config_msg = "Unknown attribute";
1868                         return ERR;
1869                 }
1870                 info->attr |= attr;
1871         }
1873         return OK;
1876 static int parse_bool(bool *opt, const char *arg)
1878         *opt = (!strcmp(arg, "1") || !strcmp(arg, "true") || !strcmp(arg, "yes"))
1879                 ? TRUE : FALSE;
1880         return OK;
1883 static int parse_enum_do(unsigned int *opt, const char *arg,
1884                          const struct enum_map *map, size_t map_size)
1886         bool is_true;
1888         assert(map_size > 1);
1890         if (map_enum_do(map, map_size, (int *) opt, arg))
1891                 return OK;
1893         if (parse_bool(&is_true, arg) != OK)
1894                 return ERR;
1896         *opt = is_true ? map[1].value : map[0].value;
1897         return OK;
1900 #define parse_enum(opt, arg, map) \
1901         parse_enum_do(opt, arg, map, ARRAY_SIZE(map))
1903 static int
1904 parse_string(char *opt, const char *arg, size_t optsize)
1906         int arglen = strlen(arg);
1908         switch (arg[0]) {
1909         case '\"':
1910         case '\'':
1911                 if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1912                         config_msg = "Unmatched quotation";
1913                         return ERR;
1914                 }
1915                 arg += 1; arglen -= 2;
1916         default:
1917                 string_ncopy_do(opt, optsize, arg, arglen);
1918                 return OK;
1919         }
1922 /* Wants: name = value */
1923 static int
1924 option_set_command(int argc, const char *argv[])
1926         if (argc != 3) {
1927                 config_msg = "Wrong number of arguments given to set command";
1928                 return ERR;
1929         }
1931         if (strcmp(argv[1], "=")) {
1932                 config_msg = "No value assigned";
1933                 return ERR;
1934         }
1936         if (!strcmp(argv[0], "show-author"))
1937                 return parse_enum(&opt_author, argv[2], author_map);
1939         if (!strcmp(argv[0], "show-date"))
1940                 return parse_enum(&opt_date, argv[2], date_map);
1942         if (!strcmp(argv[0], "show-rev-graph"))
1943                 return parse_bool(&opt_rev_graph, argv[2]);
1945         if (!strcmp(argv[0], "show-refs"))
1946                 return parse_bool(&opt_show_refs, argv[2]);
1948         if (!strcmp(argv[0], "show-line-numbers"))
1949                 return parse_bool(&opt_line_number, argv[2]);
1951         if (!strcmp(argv[0], "line-graphics"))
1952                 return parse_bool(&opt_line_graphics, argv[2]);
1954         if (!strcmp(argv[0], "line-number-interval"))
1955                 return parse_int(&opt_num_interval, argv[2], 1, 1024);
1957         if (!strcmp(argv[0], "author-width"))
1958                 return parse_int(&opt_author_cols, argv[2], 0, 1024);
1960         if (!strcmp(argv[0], "horizontal-scroll"))
1961                 return parse_step(&opt_hscroll, argv[2]);
1963         if (!strcmp(argv[0], "split-view-height"))
1964                 return parse_step(&opt_scale_split_view, argv[2]);
1966         if (!strcmp(argv[0], "tab-size"))
1967                 return parse_int(&opt_tab_size, argv[2], 1, 1024);
1969         if (!strcmp(argv[0], "commit-encoding"))
1970                 return parse_string(opt_encoding, argv[2], sizeof(opt_encoding));
1972         config_msg = "Unknown variable name";
1973         return ERR;
1976 /* Wants: mode request key */
1977 static int
1978 option_bind_command(int argc, const char *argv[])
1980         enum request request;
1981         int keymap = -1;
1982         int key;
1984         if (argc < 3) {
1985                 config_msg = "Wrong number of arguments given to bind command";
1986                 return ERR;
1987         }
1989         if (!set_keymap(&keymap, argv[0])) {
1990                 config_msg = "Unknown key map";
1991                 return ERR;
1992         }
1994         key = get_key_value(argv[1]);
1995         if (key == ERR) {
1996                 config_msg = "Unknown key";
1997                 return ERR;
1998         }
2000         request = get_request(argv[2]);
2001         if (request == REQ_UNKNOWN) {
2002                 static const struct enum_map obsolete[] = {
2003                         ENUM_MAP("cherry-pick",         REQ_NONE),
2004                         ENUM_MAP("screen-resize",       REQ_NONE),
2005                         ENUM_MAP("tree-parent",         REQ_PARENT),
2006                 };
2007                 int alias;
2009                 if (map_enum(&alias, obsolete, argv[2])) {
2010                         if (alias != REQ_NONE)
2011                                 add_keybinding(keymap, alias, key);
2012                         config_msg = "Obsolete request name";
2013                         return ERR;
2014                 }
2015         }
2016         if (request == REQ_UNKNOWN && *argv[2]++ == '!')
2017                 request = add_run_request(keymap, key, argc - 2, argv + 2);
2018         if (request == REQ_UNKNOWN) {
2019                 config_msg = "Unknown request name";
2020                 return ERR;
2021         }
2023         add_keybinding(keymap, request, key);
2025         return OK;
2028 static int
2029 set_option(const char *opt, char *value)
2031         const char *argv[SIZEOF_ARG];
2032         int argc = 0;
2034         if (!argv_from_string(argv, &argc, value)) {
2035                 config_msg = "Too many option arguments";
2036                 return ERR;
2037         }
2039         if (!strcmp(opt, "color"))
2040                 return option_color_command(argc, argv);
2042         if (!strcmp(opt, "set"))
2043                 return option_set_command(argc, argv);
2045         if (!strcmp(opt, "bind"))
2046                 return option_bind_command(argc, argv);
2048         config_msg = "Unknown option command";
2049         return ERR;
2052 static int
2053 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
2055         int status = OK;
2057         config_lineno++;
2058         config_msg = "Internal error";
2060         /* Check for comment markers, since read_properties() will
2061          * only ensure opt and value are split at first " \t". */
2062         optlen = strcspn(opt, "#");
2063         if (optlen == 0)
2064                 return OK;
2066         if (opt[optlen] != 0) {
2067                 config_msg = "No option value";
2068                 status = ERR;
2070         }  else {
2071                 /* Look for comment endings in the value. */
2072                 size_t len = strcspn(value, "#");
2074                 if (len < valuelen) {
2075                         valuelen = len;
2076                         value[valuelen] = 0;
2077                 }
2079                 status = set_option(opt, value);
2080         }
2082         if (status == ERR) {
2083                 warn("Error on line %d, near '%.*s': %s",
2084                      config_lineno, (int) optlen, opt, config_msg);
2085                 config_errors = TRUE;
2086         }
2088         /* Always keep going if errors are encountered. */
2089         return OK;
2092 static void
2093 load_option_file(const char *path)
2095         struct io io = {};
2097         /* It's OK that the file doesn't exist. */
2098         if (!io_open(&io, "%s", path))
2099                 return;
2101         config_lineno = 0;
2102         config_errors = FALSE;
2104         if (io_load(&io, " \t", read_option) == ERR ||
2105             config_errors == TRUE)
2106                 warn("Errors while loading %s.", path);
2109 static int
2110 load_options(void)
2112         const char *home = getenv("HOME");
2113         const char *tigrc_user = getenv("TIGRC_USER");
2114         const char *tigrc_system = getenv("TIGRC_SYSTEM");
2115         char buf[SIZEOF_STR];
2117         if (!tigrc_system)
2118                 tigrc_system = SYSCONFDIR "/tigrc";
2119         load_option_file(tigrc_system);
2121         if (!tigrc_user) {
2122                 if (!home || !string_format(buf, "%s/.tigrc", home))
2123                         return ERR;
2124                 tigrc_user = buf;
2125         }
2126         load_option_file(tigrc_user);
2128         /* Add _after_ loading config files to avoid adding run requests
2129          * that conflict with keybindings. */
2130         add_builtin_run_requests();
2132         return OK;
2136 /*
2137  * The viewer
2138  */
2140 struct view;
2141 struct view_ops;
2143 /* The display array of active views and the index of the current view. */
2144 static struct view *display[2];
2145 static unsigned int current_view;
2147 #define foreach_displayed_view(view, i) \
2148         for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
2150 #define displayed_views()       (display[1] != NULL ? 2 : 1)
2152 /* Current head and commit ID */
2153 static char ref_blob[SIZEOF_REF]        = "";
2154 static char ref_commit[SIZEOF_REF]      = "HEAD";
2155 static char ref_head[SIZEOF_REF]        = "HEAD";
2156 static char ref_branch[SIZEOF_REF]      = "";
2158 enum view_type {
2159         VIEW_MAIN,
2160         VIEW_DIFF,
2161         VIEW_LOG,
2162         VIEW_TREE,
2163         VIEW_BLOB,
2164         VIEW_BLAME,
2165         VIEW_BRANCH,
2166         VIEW_HELP,
2167         VIEW_PAGER,
2168         VIEW_STATUS,
2169         VIEW_STAGE,
2170 };
2172 struct view {
2173         enum view_type type;    /* View type */
2174         const char *name;       /* View name */
2175         const char *cmd_env;    /* Command line set via environment */
2176         const char *id;         /* Points to either of ref_{head,commit,blob} */
2178         struct view_ops *ops;   /* View operations */
2180         enum keymap keymap;     /* What keymap does this view have */
2181         bool git_dir;           /* Whether the view requires a git directory. */
2183         char ref[SIZEOF_REF];   /* Hovered commit reference */
2184         char vid[SIZEOF_REF];   /* View ID. Set to id member when updating. */
2186         int height, width;      /* The width and height of the main window */
2187         WINDOW *win;            /* The main window */
2188         WINDOW *title;          /* The title window living below the main window */
2190         /* Navigation */
2191         unsigned long offset;   /* Offset of the window top */
2192         unsigned long yoffset;  /* Offset from the window side. */
2193         unsigned long lineno;   /* Current line number */
2194         unsigned long p_offset; /* Previous offset of the window top */
2195         unsigned long p_yoffset;/* Previous offset from the window side */
2196         unsigned long p_lineno; /* Previous current line number */
2197         bool p_restore;         /* Should the previous position be restored. */
2199         /* Searching */
2200         char grep[SIZEOF_STR];  /* Search string */
2201         regex_t *regex;         /* Pre-compiled regexp */
2203         /* If non-NULL, points to the view that opened this view. If this view
2204          * is closed tig will switch back to the parent view. */
2205         struct view *parent;
2206         struct view *prev;
2208         /* Buffering */
2209         size_t lines;           /* Total number of lines */
2210         struct line *line;      /* Line index */
2211         unsigned int digits;    /* Number of digits in the lines member. */
2213         /* Drawing */
2214         struct line *curline;   /* Line currently being drawn. */
2215         enum line_type curtype; /* Attribute currently used for drawing. */
2216         unsigned long col;      /* Column when drawing. */
2217         bool has_scrolled;      /* View was scrolled. */
2219         /* Loading */
2220         const char *argv[SIZEOF_ARG];   /* Shell command arguments. */
2221         const char *dir;        /* Directory from which to execute. */
2222         struct io io;
2223         struct io *pipe;
2224         time_t start_time;
2225         time_t update_secs;
2226 };
2228 struct view_ops {
2229         /* What type of content being displayed. Used in the title bar. */
2230         const char *type;
2231         /* Default command arguments. */
2232         const char **argv;
2233         /* Open and reads in all view content. */
2234         bool (*open)(struct view *view);
2235         /* Read one line; updates view->line. */
2236         bool (*read)(struct view *view, char *data);
2237         /* Draw one line; @lineno must be < view->height. */
2238         bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
2239         /* Depending on view handle a special requests. */
2240         enum request (*request)(struct view *view, enum request request, struct line *line);
2241         /* Search for regexp in a line. */
2242         bool (*grep)(struct view *view, struct line *line);
2243         /* Select line */
2244         void (*select)(struct view *view, struct line *line);
2245         /* Prepare view for loading */
2246         bool (*prepare)(struct view *view);
2247 };
2249 static struct view_ops blame_ops;
2250 static struct view_ops blob_ops;
2251 static struct view_ops diff_ops;
2252 static struct view_ops help_ops;
2253 static struct view_ops log_ops;
2254 static struct view_ops main_ops;
2255 static struct view_ops pager_ops;
2256 static struct view_ops stage_ops;
2257 static struct view_ops status_ops;
2258 static struct view_ops tree_ops;
2259 static struct view_ops branch_ops;
2261 #define VIEW_STR(type, name, env, ref, ops, map, git) \
2262         { type, name, #env, ref, ops, map, git }
2264 #define VIEW_(id, name, ops, git, ref) \
2265         VIEW_STR(VIEW_##id, name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
2267 static struct view views[] = {
2268         VIEW_(MAIN,   "main",   &main_ops,   TRUE,  ref_head),
2269         VIEW_(DIFF,   "diff",   &diff_ops,   TRUE,  ref_commit),
2270         VIEW_(LOG,    "log",    &log_ops,    TRUE,  ref_head),
2271         VIEW_(TREE,   "tree",   &tree_ops,   TRUE,  ref_commit),
2272         VIEW_(BLOB,   "blob",   &blob_ops,   TRUE,  ref_blob),
2273         VIEW_(BLAME,  "blame",  &blame_ops,  TRUE,  ref_commit),
2274         VIEW_(BRANCH, "branch", &branch_ops, TRUE,  ref_head),
2275         VIEW_(HELP,   "help",   &help_ops,   FALSE, ""),
2276         VIEW_(PAGER,  "pager",  &pager_ops,  FALSE, "stdin"),
2277         VIEW_(STATUS, "status", &status_ops, TRUE,  ""),
2278         VIEW_(STAGE,  "stage",  &stage_ops,  TRUE,  ""),
2279 };
2281 #define VIEW(req)       (&views[(req) - REQ_OFFSET - 1])
2283 #define foreach_view(view, i) \
2284         for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
2286 #define view_is_displayed(view) \
2287         (view == display[0] || view == display[1])
2289 static enum request
2290 view_request(struct view *view, enum request request)
2292         if (!view || !view->lines)
2293                 return request;
2294         return view->ops->request(view, request, &view->line[view->lineno]);
2298 /*
2299  * View drawing.
2300  */
2302 static inline void
2303 set_view_attr(struct view *view, enum line_type type)
2305         if (!view->curline->selected && view->curtype != type) {
2306                 (void) wattrset(view->win, get_line_attr(type));
2307                 wchgat(view->win, -1, 0, type, NULL);
2308                 view->curtype = type;
2309         }
2312 static int
2313 draw_chars(struct view *view, enum line_type type, const char *string,
2314            int max_len, bool use_tilde)
2316         static char out_buffer[BUFSIZ * 2];
2317         int len = 0;
2318         int col = 0;
2319         int trimmed = FALSE;
2320         size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2322         if (max_len <= 0)
2323                 return 0;
2325         len = utf8_length(&string, skip, &col, max_len, &trimmed, use_tilde, opt_tab_size);
2327         set_view_attr(view, type);
2328         if (len > 0) {
2329                 if (opt_iconv_out != ICONV_NONE) {
2330                         ICONV_CONST char *inbuf = (ICONV_CONST char *) string;
2331                         size_t inlen = len + 1;
2333                         char *outbuf = out_buffer;
2334                         size_t outlen = sizeof(out_buffer);
2336                         size_t ret;
2338                         ret = iconv(opt_iconv_out, &inbuf, &inlen, &outbuf, &outlen);
2339                         if (ret != (size_t) -1) {
2340                                 string = out_buffer;
2341                                 len = sizeof(out_buffer) - outlen;
2342                         }
2343                 }
2345                 waddnstr(view->win, string, len);
2346         }
2347         if (trimmed && use_tilde) {
2348                 set_view_attr(view, LINE_DELIMITER);
2349                 waddch(view->win, '~');
2350                 col++;
2351         }
2353         return col;
2356 static int
2357 draw_space(struct view *view, enum line_type type, int max, int spaces)
2359         static char space[] = "                    ";
2360         int col = 0;
2362         spaces = MIN(max, spaces);
2364         while (spaces > 0) {
2365                 int len = MIN(spaces, sizeof(space) - 1);
2367                 col += draw_chars(view, type, space, len, FALSE);
2368                 spaces -= len;
2369         }
2371         return col;
2374 static bool
2375 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
2377         view->col += draw_chars(view, type, string, view->width + view->yoffset - view->col, trim);
2378         return view->width + view->yoffset <= view->col;
2381 static bool
2382 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
2384         size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2385         int max = view->width + view->yoffset - view->col;
2386         int i;
2388         if (max < size)
2389                 size = max;
2391         set_view_attr(view, type);
2392         /* Using waddch() instead of waddnstr() ensures that
2393          * they'll be rendered correctly for the cursor line. */
2394         for (i = skip; i < size; i++)
2395                 waddch(view->win, graphic[i]);
2397         view->col += size;
2398         if (size < max && skip <= size)
2399                 waddch(view->win, ' ');
2400         view->col++;
2402         return view->width + view->yoffset <= view->col;
2405 static bool
2406 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
2408         int max = MIN(view->width + view->yoffset - view->col, len);
2409         int col;
2411         if (text)
2412                 col = draw_chars(view, type, text, max - 1, trim);
2413         else
2414                 col = draw_space(view, type, max - 1, max - 1);
2416         view->col += col;
2417         view->col += draw_space(view, LINE_DEFAULT, max - col, max - col);
2418         return view->width + view->yoffset <= view->col;
2421 static bool
2422 draw_date(struct view *view, struct time *time)
2424         const char *date = mkdate(time, opt_date);
2425         int cols = opt_date == DATE_SHORT ? DATE_SHORT_COLS : DATE_COLS;
2427         return draw_field(view, LINE_DATE, date, cols, FALSE);
2430 static bool
2431 draw_author(struct view *view, const char *author)
2433         bool trim = opt_author_cols == 0 || opt_author_cols > 5;
2434         bool abbreviate = opt_author == AUTHOR_ABBREVIATED || !trim;
2436         if (abbreviate && author)
2437                 author = get_author_initials(author);
2439         return draw_field(view, LINE_AUTHOR, author, opt_author_cols, trim);
2442 static bool
2443 draw_mode(struct view *view, mode_t mode)
2445         const char *str;
2447         if (S_ISDIR(mode))
2448                 str = "drwxr-xr-x";
2449         else if (S_ISLNK(mode))
2450                 str = "lrwxrwxrwx";
2451         else if (S_ISGITLINK(mode))
2452                 str = "m---------";
2453         else if (S_ISREG(mode) && mode & S_IXUSR)
2454                 str = "-rwxr-xr-x";
2455         else if (S_ISREG(mode))
2456                 str = "-rw-r--r--";
2457         else
2458                 str = "----------";
2460         return draw_field(view, LINE_MODE, str, STRING_SIZE("-rw-r--r-- "), FALSE);
2463 static bool
2464 draw_lineno(struct view *view, unsigned int lineno)
2466         char number[10];
2467         int digits3 = view->digits < 3 ? 3 : view->digits;
2468         int max = MIN(view->width + view->yoffset - view->col, digits3);
2469         char *text = NULL;
2470         chtype separator = opt_line_graphics ? ACS_VLINE : '|';
2472         lineno += view->offset + 1;
2473         if (lineno == 1 || (lineno % opt_num_interval) == 0) {
2474                 static char fmt[] = "%1ld";
2476                 fmt[1] = '0' + (view->digits <= 9 ? digits3 : 1);
2477                 if (string_format(number, fmt, lineno))
2478                         text = number;
2479         }
2480         if (text)
2481                 view->col += draw_chars(view, LINE_LINE_NUMBER, text, max, TRUE);
2482         else
2483                 view->col += draw_space(view, LINE_LINE_NUMBER, max, digits3);
2484         return draw_graphic(view, LINE_DEFAULT, &separator, 1);
2487 static bool
2488 draw_view_line(struct view *view, unsigned int lineno)
2490         struct line *line;
2491         bool selected = (view->offset + lineno == view->lineno);
2493         assert(view_is_displayed(view));
2495         if (view->offset + lineno >= view->lines)
2496                 return FALSE;
2498         line = &view->line[view->offset + lineno];
2500         wmove(view->win, lineno, 0);
2501         if (line->cleareol)
2502                 wclrtoeol(view->win);
2503         view->col = 0;
2504         view->curline = line;
2505         view->curtype = LINE_NONE;
2506         line->selected = FALSE;
2507         line->dirty = line->cleareol = 0;
2509         if (selected) {
2510                 set_view_attr(view, LINE_CURSOR);
2511                 line->selected = TRUE;
2512                 view->ops->select(view, line);
2513         }
2515         return view->ops->draw(view, line, lineno);
2518 static void
2519 redraw_view_dirty(struct view *view)
2521         bool dirty = FALSE;
2522         int lineno;
2524         for (lineno = 0; lineno < view->height; lineno++) {
2525                 if (view->offset + lineno >= view->lines)
2526                         break;
2527                 if (!view->line[view->offset + lineno].dirty)
2528                         continue;
2529                 dirty = TRUE;
2530                 if (!draw_view_line(view, lineno))
2531                         break;
2532         }
2534         if (!dirty)
2535                 return;
2536         wnoutrefresh(view->win);
2539 static void
2540 redraw_view_from(struct view *view, int lineno)
2542         assert(0 <= lineno && lineno < view->height);
2544         for (; lineno < view->height; lineno++) {
2545                 if (!draw_view_line(view, lineno))
2546                         break;
2547         }
2549         wnoutrefresh(view->win);
2552 static void
2553 redraw_view(struct view *view)
2555         werase(view->win);
2556         redraw_view_from(view, 0);
2560 static void
2561 update_view_title(struct view *view)
2563         char buf[SIZEOF_STR];
2564         char state[SIZEOF_STR];
2565         size_t bufpos = 0, statelen = 0;
2567         assert(view_is_displayed(view));
2569         if (view->type != VIEW_STATUS && view->lines) {
2570                 unsigned int view_lines = view->offset + view->height;
2571                 unsigned int lines = view->lines
2572                                    ? MIN(view_lines, view->lines) * 100 / view->lines
2573                                    : 0;
2575                 string_format_from(state, &statelen, " - %s %d of %d (%d%%)",
2576                                    view->ops->type,
2577                                    view->lineno + 1,
2578                                    view->lines,
2579                                    lines);
2581         }
2583         if (view->pipe) {
2584                 time_t secs = time(NULL) - view->start_time;
2586                 /* Three git seconds are a long time ... */
2587                 if (secs > 2)
2588                         string_format_from(state, &statelen, " loading %lds", secs);
2589         }
2591         string_format_from(buf, &bufpos, "[%s]", view->name);
2592         if (*view->ref && bufpos < view->width) {
2593                 size_t refsize = strlen(view->ref);
2594                 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2596                 if (minsize < view->width)
2597                         refsize = view->width - minsize + 7;
2598                 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2599         }
2601         if (statelen && bufpos < view->width) {
2602                 string_format_from(buf, &bufpos, "%s", state);
2603         }
2605         if (view == display[current_view])
2606                 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2607         else
2608                 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2610         mvwaddnstr(view->title, 0, 0, buf, bufpos);
2611         wclrtoeol(view->title);
2612         wnoutrefresh(view->title);
2615 static int
2616 apply_step(double step, int value)
2618         if (step >= 1)
2619                 return (int) step;
2620         value *= step + 0.01;
2621         return value ? value : 1;
2624 static void
2625 resize_display(void)
2627         int offset, i;
2628         struct view *base = display[0];
2629         struct view *view = display[1] ? display[1] : display[0];
2631         /* Setup window dimensions */
2633         getmaxyx(stdscr, base->height, base->width);
2635         /* Make room for the status window. */
2636         base->height -= 1;
2638         if (view != base) {
2639                 /* Horizontal split. */
2640                 view->width   = base->width;
2641                 view->height  = apply_step(opt_scale_split_view, base->height);
2642                 view->height  = MAX(view->height, MIN_VIEW_HEIGHT);
2643                 view->height  = MIN(view->height, base->height - MIN_VIEW_HEIGHT);
2644                 base->height -= view->height;
2646                 /* Make room for the title bar. */
2647                 view->height -= 1;
2648         }
2650         /* Make room for the title bar. */
2651         base->height -= 1;
2653         offset = 0;
2655         foreach_displayed_view (view, i) {
2656                 if (!view->win) {
2657                         view->win = newwin(view->height, 0, offset, 0);
2658                         if (!view->win)
2659                                 die("Failed to create %s view", view->name);
2661                         scrollok(view->win, FALSE);
2663                         view->title = newwin(1, 0, offset + view->height, 0);
2664                         if (!view->title)
2665                                 die("Failed to create title window");
2667                 } else {
2668                         wresize(view->win, view->height, view->width);
2669                         mvwin(view->win,   offset, 0);
2670                         mvwin(view->title, offset + view->height, 0);
2671                 }
2673                 offset += view->height + 1;
2674         }
2677 static void
2678 redraw_display(bool clear)
2680         struct view *view;
2681         int i;
2683         foreach_displayed_view (view, i) {
2684                 if (clear)
2685                         wclear(view->win);
2686                 redraw_view(view);
2687                 update_view_title(view);
2688         }
2692 /*
2693  * Option management
2694  */
2696 static void
2697 toggle_enum_option_do(unsigned int *opt, const char *help,
2698                       const struct enum_map *map, size_t size)
2700         *opt = (*opt + 1) % size;
2701         redraw_display(FALSE);
2702         report("Displaying %s %s", enum_name(map[*opt]), help);
2705 #define toggle_enum_option(opt, help, map) \
2706         toggle_enum_option_do(opt, help, map, ARRAY_SIZE(map))
2708 #define toggle_date() toggle_enum_option(&opt_date, "dates", date_map)
2709 #define toggle_author() toggle_enum_option(&opt_author, "author names", author_map)
2711 static void
2712 toggle_view_option(bool *option, const char *help)
2714         *option = !*option;
2715         redraw_display(FALSE);
2716         report("%sabling %s", *option ? "En" : "Dis", help);
2719 static void
2720 open_option_menu(void)
2722         const struct menu_item menu[] = {
2723                 { '.', "line numbers", &opt_line_number },
2724                 { 'D', "date display", &opt_date },
2725                 { 'A', "author display", &opt_author },
2726                 { 'g', "revision graph display", &opt_rev_graph },
2727                 { 'F', "reference display", &opt_show_refs },
2728                 { 0 }
2729         };
2730         int selected = 0;
2732         if (prompt_menu("Toggle option", menu, &selected)) {
2733                 if (menu[selected].data == &opt_date)
2734                         toggle_date();
2735                 else if (menu[selected].data == &opt_author)
2736                         toggle_author();
2737                 else
2738                         toggle_view_option(menu[selected].data, menu[selected].text);
2739         }
2742 static void
2743 maximize_view(struct view *view)
2745         memset(display, 0, sizeof(display));
2746         current_view = 0;
2747         display[current_view] = view;
2748         resize_display();
2749         redraw_display(FALSE);
2750         report("");
2754 /*
2755  * Navigation
2756  */
2758 static bool
2759 goto_view_line(struct view *view, unsigned long offset, unsigned long lineno)
2761         if (lineno >= view->lines)
2762                 lineno = view->lines > 0 ? view->lines - 1 : 0;
2764         if (offset > lineno || offset + view->height <= lineno) {
2765                 unsigned long half = view->height / 2;
2767                 if (lineno > half)
2768                         offset = lineno - half;
2769                 else
2770                         offset = 0;
2771         }
2773         if (offset != view->offset || lineno != view->lineno) {
2774                 view->offset = offset;
2775                 view->lineno = lineno;
2776                 return TRUE;
2777         }
2779         return FALSE;
2782 /* Scrolling backend */
2783 static void
2784 do_scroll_view(struct view *view, int lines)
2786         bool redraw_current_line = FALSE;
2788         /* The rendering expects the new offset. */
2789         view->offset += lines;
2791         assert(0 <= view->offset && view->offset < view->lines);
2792         assert(lines);
2794         /* Move current line into the view. */
2795         if (view->lineno < view->offset) {
2796                 view->lineno = view->offset;
2797                 redraw_current_line = TRUE;
2798         } else if (view->lineno >= view->offset + view->height) {
2799                 view->lineno = view->offset + view->height - 1;
2800                 redraw_current_line = TRUE;
2801         }
2803         assert(view->offset <= view->lineno && view->lineno < view->lines);
2805         /* Redraw the whole screen if scrolling is pointless. */
2806         if (view->height < ABS(lines)) {
2807                 redraw_view(view);
2809         } else {
2810                 int line = lines > 0 ? view->height - lines : 0;
2811                 int end = line + ABS(lines);
2813                 scrollok(view->win, TRUE);
2814                 wscrl(view->win, lines);
2815                 scrollok(view->win, FALSE);
2817                 while (line < end && draw_view_line(view, line))
2818                         line++;
2820                 if (redraw_current_line)
2821                         draw_view_line(view, view->lineno - view->offset);
2822                 wnoutrefresh(view->win);
2823         }
2825         view->has_scrolled = TRUE;
2826         report("");
2829 /* Scroll frontend */
2830 static void
2831 scroll_view(struct view *view, enum request request)
2833         int lines = 1;
2835         assert(view_is_displayed(view));
2837         switch (request) {
2838         case REQ_SCROLL_LEFT:
2839                 if (view->yoffset == 0) {
2840                         report("Cannot scroll beyond the first column");
2841                         return;
2842                 }
2843                 if (view->yoffset <= apply_step(opt_hscroll, view->width))
2844                         view->yoffset = 0;
2845                 else
2846                         view->yoffset -= apply_step(opt_hscroll, view->width);
2847                 redraw_view_from(view, 0);
2848                 report("");
2849                 return;
2850         case REQ_SCROLL_RIGHT:
2851                 view->yoffset += apply_step(opt_hscroll, view->width);
2852                 redraw_view(view);
2853                 report("");
2854                 return;
2855         case REQ_SCROLL_PAGE_DOWN:
2856                 lines = view->height;
2857         case REQ_SCROLL_LINE_DOWN:
2858                 if (view->offset + lines > view->lines)
2859                         lines = view->lines - view->offset;
2861                 if (lines == 0 || view->offset + view->height >= view->lines) {
2862                         report("Cannot scroll beyond the last line");
2863                         return;
2864                 }
2865                 break;
2867         case REQ_SCROLL_PAGE_UP:
2868                 lines = view->height;
2869         case REQ_SCROLL_LINE_UP:
2870                 if (lines > view->offset)
2871                         lines = view->offset;
2873                 if (lines == 0) {
2874                         report("Cannot scroll beyond the first line");
2875                         return;
2876                 }
2878                 lines = -lines;
2879                 break;
2881         default:
2882                 die("request %d not handled in switch", request);
2883         }
2885         do_scroll_view(view, lines);
2888 /* Cursor moving */
2889 static void
2890 move_view(struct view *view, enum request request)
2892         int scroll_steps = 0;
2893         int steps;
2895         switch (request) {
2896         case REQ_MOVE_FIRST_LINE:
2897                 steps = -view->lineno;
2898                 break;
2900         case REQ_MOVE_LAST_LINE:
2901                 steps = view->lines - view->lineno - 1;
2902                 break;
2904         case REQ_MOVE_PAGE_UP:
2905                 steps = view->height > view->lineno
2906                       ? -view->lineno : -view->height;
2907                 break;
2909         case REQ_MOVE_PAGE_DOWN:
2910                 steps = view->lineno + view->height >= view->lines
2911                       ? view->lines - view->lineno - 1 : view->height;
2912                 break;
2914         case REQ_MOVE_UP:
2915                 steps = -1;
2916                 break;
2918         case REQ_MOVE_DOWN:
2919                 steps = 1;
2920                 break;
2922         default:
2923                 die("request %d not handled in switch", request);
2924         }
2926         if (steps <= 0 && view->lineno == 0) {
2927                 report("Cannot move beyond the first line");
2928                 return;
2930         } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2931                 report("Cannot move beyond the last line");
2932                 return;
2933         }
2935         /* Move the current line */
2936         view->lineno += steps;
2937         assert(0 <= view->lineno && view->lineno < view->lines);
2939         /* Check whether the view needs to be scrolled */
2940         if (view->lineno < view->offset ||
2941             view->lineno >= view->offset + view->height) {
2942                 scroll_steps = steps;
2943                 if (steps < 0 && -steps > view->offset) {
2944                         scroll_steps = -view->offset;
2946                 } else if (steps > 0) {
2947                         if (view->lineno == view->lines - 1 &&
2948                             view->lines > view->height) {
2949                                 scroll_steps = view->lines - view->offset - 1;
2950                                 if (scroll_steps >= view->height)
2951                                         scroll_steps -= view->height - 1;
2952                         }
2953                 }
2954         }
2956         if (!view_is_displayed(view)) {
2957                 view->offset += scroll_steps;
2958                 assert(0 <= view->offset && view->offset < view->lines);
2959                 view->ops->select(view, &view->line[view->lineno]);
2960                 return;
2961         }
2963         /* Repaint the old "current" line if we be scrolling */
2964         if (ABS(steps) < view->height)
2965                 draw_view_line(view, view->lineno - steps - view->offset);
2967         if (scroll_steps) {
2968                 do_scroll_view(view, scroll_steps);
2969                 return;
2970         }
2972         /* Draw the current line */
2973         draw_view_line(view, view->lineno - view->offset);
2975         wnoutrefresh(view->win);
2976         report("");
2980 /*
2981  * Searching
2982  */
2984 static void search_view(struct view *view, enum request request);
2986 static bool
2987 grep_text(struct view *view, const char *text[])
2989         regmatch_t pmatch;
2990         size_t i;
2992         for (i = 0; text[i]; i++)
2993                 if (*text[i] &&
2994                     regexec(view->regex, text[i], 1, &pmatch, 0) != REG_NOMATCH)
2995                         return TRUE;
2996         return FALSE;
2999 static void
3000 select_view_line(struct view *view, unsigned long lineno)
3002         unsigned long old_lineno = view->lineno;
3003         unsigned long old_offset = view->offset;
3005         if (goto_view_line(view, view->offset, lineno)) {
3006                 if (view_is_displayed(view)) {
3007                         if (old_offset != view->offset) {
3008                                 redraw_view(view);
3009                         } else {
3010                                 draw_view_line(view, old_lineno - view->offset);
3011                                 draw_view_line(view, view->lineno - view->offset);
3012                                 wnoutrefresh(view->win);
3013                         }
3014                 } else {
3015                         view->ops->select(view, &view->line[view->lineno]);
3016                 }
3017         }
3020 static void
3021 find_next(struct view *view, enum request request)
3023         unsigned long lineno = view->lineno;
3024         int direction;
3026         if (!*view->grep) {
3027                 if (!*opt_search)
3028                         report("No previous search");
3029                 else
3030                         search_view(view, request);
3031                 return;
3032         }
3034         switch (request) {
3035         case REQ_SEARCH:
3036         case REQ_FIND_NEXT:
3037                 direction = 1;
3038                 break;
3040         case REQ_SEARCH_BACK:
3041         case REQ_FIND_PREV:
3042                 direction = -1;
3043                 break;
3045         default:
3046                 return;
3047         }
3049         if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
3050                 lineno += direction;
3052         /* Note, lineno is unsigned long so will wrap around in which case it
3053          * will become bigger than view->lines. */
3054         for (; lineno < view->lines; lineno += direction) {
3055                 if (view->ops->grep(view, &view->line[lineno])) {
3056                         select_view_line(view, lineno);
3057                         report("Line %ld matches '%s'", lineno + 1, view->grep);
3058                         return;
3059                 }
3060         }
3062         report("No match found for '%s'", view->grep);
3065 static void
3066 search_view(struct view *view, enum request request)
3068         int regex_err;
3070         if (view->regex) {
3071                 regfree(view->regex);
3072                 *view->grep = 0;
3073         } else {
3074                 view->regex = calloc(1, sizeof(*view->regex));
3075                 if (!view->regex)
3076                         return;
3077         }
3079         regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
3080         if (regex_err != 0) {
3081                 char buf[SIZEOF_STR] = "unknown error";
3083                 regerror(regex_err, view->regex, buf, sizeof(buf));
3084                 report("Search failed: %s", buf);
3085                 return;
3086         }
3088         string_copy(view->grep, opt_search);
3090         find_next(view, request);
3093 /*
3094  * Incremental updating
3095  */
3097 static void
3098 reset_view(struct view *view)
3100         int i;
3102         for (i = 0; i < view->lines; i++)
3103                 free(view->line[i].data);
3104         free(view->line);
3106         view->p_offset = view->offset;
3107         view->p_yoffset = view->yoffset;
3108         view->p_lineno = view->lineno;
3110         view->line = NULL;
3111         view->offset = 0;
3112         view->yoffset = 0;
3113         view->lines  = 0;
3114         view->lineno = 0;
3115         view->vid[0] = 0;
3116         view->update_secs = 0;
3119 static const char *
3120 format_arg(const char *name)
3122         static struct {
3123                 const char *name;
3124                 size_t namelen;
3125                 const char *value;
3126                 const char *value_if_empty;
3127         } vars[] = {
3128 #define FORMAT_VAR(name, value, value_if_empty) \
3129         { name, STRING_SIZE(name), value, value_if_empty }
3130                 FORMAT_VAR("%(directory)",      opt_path,       ""),
3131                 FORMAT_VAR("%(file)",           opt_file,       ""),
3132                 FORMAT_VAR("%(ref)",            opt_ref,        "HEAD"),
3133                 FORMAT_VAR("%(head)",           ref_head,       ""),
3134                 FORMAT_VAR("%(commit)",         ref_commit,     ""),
3135                 FORMAT_VAR("%(blob)",           ref_blob,       ""),
3136                 FORMAT_VAR("%(branch)",         ref_branch,     ""),
3137         };
3138         int i;
3140         for (i = 0; i < ARRAY_SIZE(vars); i++)
3141                 if (!strncmp(name, vars[i].name, vars[i].namelen))
3142                         return *vars[i].value ? vars[i].value : vars[i].value_if_empty;
3144         report("Unknown replacement: `%s`", name);
3145         return NULL;
3148 static bool
3149 format_argv(const char *dst_argv[], const char *src_argv[], bool replace)
3151         char buf[SIZEOF_STR];
3152         int argc;
3154         argv_free(dst_argv);
3156         for (argc = 0; src_argv[argc]; argc++) {
3157                 const char *arg = src_argv[argc];
3158                 size_t bufpos = 0;
3160                 while (arg) {
3161                         char *next = strstr(arg, "%(");
3162                         int len = next - arg;
3163                         const char *value;
3165                         if (!next || !replace) {
3166                                 len = strlen(arg);
3167                                 value = "";
3169                         } else {
3170                                 value = format_arg(next);
3172                                 if (!value) {
3173                                         return FALSE;
3174                                 }
3175                         }
3177                         if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
3178                                 return FALSE;
3180                         arg = next && replace ? strchr(next, ')') + 1 : NULL;
3181                 }
3183                 dst_argv[argc] = strdup(buf);
3184                 if (!dst_argv[argc])
3185                         break;
3186         }
3188         dst_argv[argc] = NULL;
3190         return src_argv[argc] == NULL;
3193 static bool
3194 restore_view_position(struct view *view)
3196         if (!view->p_restore || (view->pipe && view->lines <= view->p_lineno))
3197                 return FALSE;
3199         /* Changing the view position cancels the restoring. */
3200         /* FIXME: Changing back to the first line is not detected. */
3201         if (view->offset != 0 || view->lineno != 0) {
3202                 view->p_restore = FALSE;
3203                 return FALSE;
3204         }
3206         if (goto_view_line(view, view->p_offset, view->p_lineno) &&
3207             view_is_displayed(view))
3208                 werase(view->win);
3210         view->yoffset = view->p_yoffset;
3211         view->p_restore = FALSE;
3213         return TRUE;
3216 static void
3217 end_update(struct view *view, bool force)
3219         if (!view->pipe)
3220                 return;
3221         while (!view->ops->read(view, NULL))
3222                 if (!force)
3223                         return;
3224         if (force)
3225                 io_kill(view->pipe);
3226         io_done(view->pipe);
3227         view->pipe = NULL;
3230 static void
3231 setup_update(struct view *view, const char *vid)
3233         reset_view(view);
3234         string_copy_rev(view->vid, vid);
3235         view->pipe = &view->io;
3236         view->start_time = time(NULL);
3239 static bool
3240 prepare_io(struct view *view, const char *dir, const char *argv[], bool replace)
3242         io_init(&view->io);
3243         view->dir = dir;
3244         return format_argv(view->argv, argv, replace);
3247 static bool
3248 prepare_update(struct view *view, const char *argv[], const char *dir)
3250         if (view->pipe)
3251                 end_update(view, TRUE);
3252         return prepare_io(view, dir, argv, FALSE);
3255 static bool
3256 start_update(struct view *view, const char **argv, const char *dir)
3258         if (view->pipe)
3259                 io_done(view->pipe);
3260         return prepare_io(view, dir, argv, FALSE) &&
3261                io_start(&view->io, IO_RD, dir, view->argv);
3264 static bool
3265 prepare_update_file(struct view *view, const char *name)
3267         if (view->pipe)
3268                 end_update(view, TRUE);
3269         argv_free(view->argv);
3270         return io_open(&view->io, "%s/%s", opt_cdup[0] ? opt_cdup : ".", name);
3273 static bool
3274 begin_update(struct view *view, bool refresh)
3276         if (view->pipe)
3277                 end_update(view, TRUE);
3279         if (!refresh) {
3280                 if (view->ops->prepare) {
3281                         if (!view->ops->prepare(view))
3282                                 return FALSE;
3283                 } else if (!prepare_io(view, NULL, view->ops->argv, TRUE)) {
3284                         return FALSE;
3285                 }
3287                 /* Put the current ref_* value to the view title ref
3288                  * member. This is needed by the blob view. Most other
3289                  * views sets it automatically after loading because the
3290                  * first line is a commit line. */
3291                 string_copy_rev(view->ref, view->id);
3292         }
3294         if (view->argv[0] && !io_start(&view->io, IO_RD, view->dir, view->argv))
3295                 return FALSE;
3297         setup_update(view, view->id);
3299         return TRUE;
3302 static bool
3303 update_view(struct view *view)
3305         char out_buffer[BUFSIZ * 2];
3306         char *line;
3307         /* Clear the view and redraw everything since the tree sorting
3308          * might have rearranged things. */
3309         bool redraw = view->lines == 0;
3310         bool can_read = TRUE;
3312         if (!view->pipe)
3313                 return TRUE;
3315         if (!io_can_read(view->pipe)) {
3316                 if (view->lines == 0 && view_is_displayed(view)) {
3317                         time_t secs = time(NULL) - view->start_time;
3319                         if (secs > 1 && secs > view->update_secs) {
3320                                 if (view->update_secs == 0)
3321                                         redraw_view(view);
3322                                 update_view_title(view);
3323                                 view->update_secs = secs;
3324                         }
3325                 }
3326                 return TRUE;
3327         }
3329         for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
3330                 if (opt_iconv_in != ICONV_NONE) {
3331                         ICONV_CONST char *inbuf = line;
3332                         size_t inlen = strlen(line) + 1;
3334                         char *outbuf = out_buffer;
3335                         size_t outlen = sizeof(out_buffer);
3337                         size_t ret;
3339                         ret = iconv(opt_iconv_in, &inbuf, &inlen, &outbuf, &outlen);
3340                         if (ret != (size_t) -1)
3341                                 line = out_buffer;
3342                 }
3344                 if (!view->ops->read(view, line)) {
3345                         report("Allocation failure");
3346                         end_update(view, TRUE);
3347                         return FALSE;
3348                 }
3349         }
3351         {
3352                 unsigned long lines = view->lines;
3353                 int digits;
3355                 for (digits = 0; lines; digits++)
3356                         lines /= 10;
3358                 /* Keep the displayed view in sync with line number scaling. */
3359                 if (digits != view->digits) {
3360                         view->digits = digits;
3361                         if (opt_line_number || view->type == VIEW_BLAME)
3362                                 redraw = TRUE;
3363                 }
3364         }
3366         if (io_error(view->pipe)) {
3367                 report("Failed to read: %s", io_strerror(view->pipe));
3368                 end_update(view, TRUE);
3370         } else if (io_eof(view->pipe)) {
3371                 if (view_is_displayed(view))
3372                         report("");
3373                 end_update(view, FALSE);
3374         }
3376         if (restore_view_position(view))
3377                 redraw = TRUE;
3379         if (!view_is_displayed(view))
3380                 return TRUE;
3382         if (redraw)
3383                 redraw_view_from(view, 0);
3384         else
3385                 redraw_view_dirty(view);
3387         /* Update the title _after_ the redraw so that if the redraw picks up a
3388          * commit reference in view->ref it'll be available here. */
3389         update_view_title(view);
3390         return TRUE;
3393 DEFINE_ALLOCATOR(realloc_lines, struct line, 256)
3395 static struct line *
3396 add_line_data(struct view *view, void *data, enum line_type type)
3398         struct line *line;
3400         if (!realloc_lines(&view->line, view->lines, 1))
3401                 return NULL;
3403         line = &view->line[view->lines++];
3404         memset(line, 0, sizeof(*line));
3405         line->type = type;
3406         line->data = data;
3407         line->dirty = 1;
3409         return line;
3412 static struct line *
3413 add_line_text(struct view *view, const char *text, enum line_type type)
3415         char *data = text ? strdup(text) : NULL;
3417         return data ? add_line_data(view, data, type) : NULL;
3420 static struct line *
3421 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
3423         char buf[SIZEOF_STR];
3424         va_list args;
3426         va_start(args, fmt);
3427         if (vsnprintf(buf, sizeof(buf), fmt, args) >= sizeof(buf))
3428                 buf[0] = 0;
3429         va_end(args);
3431         return buf[0] ? add_line_text(view, buf, type) : NULL;
3434 /*
3435  * View opening
3436  */
3438 enum open_flags {
3439         OPEN_DEFAULT = 0,       /* Use default view switching. */
3440         OPEN_SPLIT = 1,         /* Split current view. */
3441         OPEN_RELOAD = 4,        /* Reload view even if it is the current. */
3442         OPEN_REFRESH = 16,      /* Refresh view using previous command. */
3443         OPEN_PREPARED = 32,     /* Open already prepared command. */
3444 };
3446 static void
3447 open_view(struct view *prev, enum request request, enum open_flags flags)
3449         bool split = !!(flags & OPEN_SPLIT);
3450         bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
3451         bool nomaximize = !!(flags & OPEN_REFRESH);
3452         struct view *view = VIEW(request);
3453         int nviews = displayed_views();
3454         struct view *base_view = display[0];
3456         if (view == prev && nviews == 1 && !reload) {
3457                 report("Already in %s view", view->name);
3458                 return;
3459         }
3461         if (view->git_dir && !opt_git_dir[0]) {
3462                 report("The %s view is disabled in pager view", view->name);
3463                 return;
3464         }
3466         if (split) {
3467                 display[1] = view;
3468                 current_view = 1;
3469                 view->parent = prev;
3470         } else if (!nomaximize) {
3471                 /* Maximize the current view. */
3472                 memset(display, 0, sizeof(display));
3473                 current_view = 0;
3474                 display[current_view] = view;
3475         }
3477         /* No prev signals that this is the first loaded view. */
3478         if (prev && view != prev) {
3479                 view->prev = prev;
3480         }
3482         /* Resize the view when switching between split- and full-screen,
3483          * or when switching between two different full-screen views. */
3484         if (nviews != displayed_views() ||
3485             (nviews == 1 && base_view != display[0]))
3486                 resize_display();
3488         if (view->ops->open) {
3489                 if (view->pipe)
3490                         end_update(view, TRUE);
3491                 if (!view->ops->open(view)) {
3492                         report("Failed to load %s view", view->name);
3493                         return;
3494                 }
3495                 restore_view_position(view);
3497         } else if ((reload || strcmp(view->vid, view->id)) &&
3498                    !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
3499                 report("Failed to load %s view", view->name);
3500                 return;
3501         }
3503         if (split && prev->lineno - prev->offset >= prev->height) {
3504                 /* Take the title line into account. */
3505                 int lines = prev->lineno - prev->offset - prev->height + 1;
3507                 /* Scroll the view that was split if the current line is
3508                  * outside the new limited view. */
3509                 do_scroll_view(prev, lines);
3510         }
3512         if (prev && view != prev && split && view_is_displayed(prev)) {
3513                 /* "Blur" the previous view. */
3514                 update_view_title(prev);
3515         }
3517         if (view->pipe && view->lines == 0) {
3518                 /* Clear the old view and let the incremental updating refill
3519                  * the screen. */
3520                 werase(view->win);
3521                 view->p_restore = flags & (OPEN_RELOAD | OPEN_REFRESH);
3522                 report("");
3523         } else if (view_is_displayed(view)) {
3524                 redraw_view(view);
3525                 report("");
3526         }
3529 static void
3530 open_external_viewer(const char *argv[], const char *dir)
3532         def_prog_mode();           /* save current tty modes */
3533         endwin();                  /* restore original tty modes */
3534         io_run_fg(argv, dir);
3535         fprintf(stderr, "Press Enter to continue");
3536         getc(opt_tty);
3537         reset_prog_mode();
3538         redraw_display(TRUE);
3541 static void
3542 open_mergetool(const char *file)
3544         const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
3546         open_external_viewer(mergetool_argv, opt_cdup);
3549 static void
3550 open_editor(const char *file)
3552         const char *editor_argv[] = { "vi", file, NULL };
3553         const char *editor;
3555         editor = getenv("GIT_EDITOR");
3556         if (!editor && *opt_editor)
3557                 editor = opt_editor;
3558         if (!editor)
3559                 editor = getenv("VISUAL");
3560         if (!editor)
3561                 editor = getenv("EDITOR");
3562         if (!editor)
3563                 editor = "vi";
3565         editor_argv[0] = editor;
3566         open_external_viewer(editor_argv, opt_cdup);
3569 static void
3570 open_run_request(enum request request)
3572         struct run_request *req = get_run_request(request);
3573         const char *argv[ARRAY_SIZE(req->argv)] = { NULL };
3575         if (!req) {
3576                 report("Unknown run request");
3577                 return;
3578         }
3580         if (format_argv(argv, req->argv, TRUE))
3581                 open_external_viewer(argv, NULL);
3582         argv_free(argv);
3585 /*
3586  * User request switch noodle
3587  */
3589 static int
3590 view_driver(struct view *view, enum request request)
3592         int i;
3594         if (request == REQ_NONE)
3595                 return TRUE;
3597         if (request > REQ_NONE) {
3598                 open_run_request(request);
3599                 view_request(view, REQ_REFRESH);
3600                 return TRUE;
3601         }
3603         request = view_request(view, request);
3604         if (request == REQ_NONE)
3605                 return TRUE;
3607         switch (request) {
3608         case REQ_MOVE_UP:
3609         case REQ_MOVE_DOWN:
3610         case REQ_MOVE_PAGE_UP:
3611         case REQ_MOVE_PAGE_DOWN:
3612         case REQ_MOVE_FIRST_LINE:
3613         case REQ_MOVE_LAST_LINE:
3614                 move_view(view, request);
3615                 break;
3617         case REQ_SCROLL_LEFT:
3618         case REQ_SCROLL_RIGHT:
3619         case REQ_SCROLL_LINE_DOWN:
3620         case REQ_SCROLL_LINE_UP:
3621         case REQ_SCROLL_PAGE_DOWN:
3622         case REQ_SCROLL_PAGE_UP:
3623                 scroll_view(view, request);
3624                 break;
3626         case REQ_VIEW_BLAME:
3627                 if (!opt_file[0]) {
3628                         report("No file chosen, press %s to open tree view",
3629                                get_key(view->keymap, REQ_VIEW_TREE));
3630                         break;
3631                 }
3632                 open_view(view, request, OPEN_DEFAULT);
3633                 break;
3635         case REQ_VIEW_BLOB:
3636                 if (!ref_blob[0]) {
3637                         report("No file chosen, press %s to open tree view",
3638                                get_key(view->keymap, REQ_VIEW_TREE));
3639                         break;
3640                 }
3641                 open_view(view, request, OPEN_DEFAULT);
3642                 break;
3644         case REQ_VIEW_PAGER:
3645                 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3646                         report("No pager content, press %s to run command from prompt",
3647                                get_key(view->keymap, REQ_PROMPT));
3648                         break;
3649                 }
3650                 open_view(view, request, OPEN_DEFAULT);
3651                 break;
3653         case REQ_VIEW_STAGE:
3654                 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3655                         report("No stage content, press %s to open the status view and choose file",
3656                                get_key(view->keymap, REQ_VIEW_STATUS));
3657                         break;
3658                 }
3659                 open_view(view, request, OPEN_DEFAULT);
3660                 break;
3662         case REQ_VIEW_STATUS:
3663                 if (opt_is_inside_work_tree == FALSE) {
3664                         report("The status view requires a working tree");
3665                         break;
3666                 }
3667                 open_view(view, request, OPEN_DEFAULT);
3668                 break;
3670         case REQ_VIEW_MAIN:
3671         case REQ_VIEW_DIFF:
3672         case REQ_VIEW_LOG:
3673         case REQ_VIEW_TREE:
3674         case REQ_VIEW_HELP:
3675         case REQ_VIEW_BRANCH:
3676                 open_view(view, request, OPEN_DEFAULT);
3677                 break;
3679         case REQ_NEXT:
3680         case REQ_PREVIOUS:
3681                 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3683                 if (view->parent) {
3684                         int line;
3686                         view = view->parent;
3687                         line = view->lineno;
3688                         move_view(view, request);
3689                         if (view_is_displayed(view))
3690                                 update_view_title(view);
3691                         if (line != view->lineno)
3692                                 view_request(view, REQ_ENTER);
3693                 } else {
3694                         move_view(view, request);
3695                 }
3696                 break;
3698         case REQ_VIEW_NEXT:
3699         {
3700                 int nviews = displayed_views();
3701                 int next_view = (current_view + 1) % nviews;
3703                 if (next_view == current_view) {
3704                         report("Only one view is displayed");
3705                         break;
3706                 }
3708                 current_view = next_view;
3709                 /* Blur out the title of the previous view. */
3710                 update_view_title(view);
3711                 report("");
3712                 break;
3713         }
3714         case REQ_REFRESH:
3715                 report("Refreshing is not yet supported for the %s view", view->name);
3716                 break;
3718         case REQ_MAXIMIZE:
3719                 if (displayed_views() == 2)
3720                         maximize_view(view);
3721                 break;
3723         case REQ_OPTIONS:
3724                 open_option_menu();
3725                 break;
3727         case REQ_TOGGLE_LINENO:
3728                 toggle_view_option(&opt_line_number, "line numbers");
3729                 break;
3731         case REQ_TOGGLE_DATE:
3732                 toggle_date();
3733                 break;
3735         case REQ_TOGGLE_AUTHOR:
3736                 toggle_author();
3737                 break;
3739         case REQ_TOGGLE_REV_GRAPH:
3740                 toggle_view_option(&opt_rev_graph, "revision graph display");
3741                 break;
3743         case REQ_TOGGLE_REFS:
3744                 toggle_view_option(&opt_show_refs, "reference display");
3745                 break;
3747         case REQ_TOGGLE_SORT_FIELD:
3748         case REQ_TOGGLE_SORT_ORDER:
3749                 report("Sorting is not yet supported for the %s view", view->name);
3750                 break;
3752         case REQ_SEARCH:
3753         case REQ_SEARCH_BACK:
3754                 search_view(view, request);
3755                 break;
3757         case REQ_FIND_NEXT:
3758         case REQ_FIND_PREV:
3759                 find_next(view, request);
3760                 break;
3762         case REQ_STOP_LOADING:
3763                 foreach_view(view, i) {
3764                         if (view->pipe)
3765                                 report("Stopped loading the %s view", view->name),
3766                         end_update(view, TRUE);
3767                 }
3768                 break;
3770         case REQ_SHOW_VERSION:
3771                 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3772                 return TRUE;
3774         case REQ_SCREEN_REDRAW:
3775                 redraw_display(TRUE);
3776                 break;
3778         case REQ_EDIT:
3779                 report("Nothing to edit");
3780                 break;
3782         case REQ_ENTER:
3783                 report("Nothing to enter");
3784                 break;
3786         case REQ_VIEW_CLOSE:
3787                 /* XXX: Mark closed views by letting view->prev point to the
3788                  * view itself. Parents to closed view should never be
3789                  * followed. */
3790                 if (view->prev && view->prev != view) {
3791                         maximize_view(view->prev);
3792                         view->prev = view;
3793                         break;
3794                 }
3795                 /* Fall-through */
3796         case REQ_QUIT:
3797                 return FALSE;
3799         default:
3800                 report("Unknown key, press %s for help",
3801                        get_key(view->keymap, REQ_VIEW_HELP));
3802                 return TRUE;
3803         }
3805         return TRUE;
3809 /*
3810  * View backend utilities
3811  */
3813 enum sort_field {
3814         ORDERBY_NAME,
3815         ORDERBY_DATE,
3816         ORDERBY_AUTHOR,
3817 };
3819 struct sort_state {
3820         const enum sort_field *fields;
3821         size_t size, current;
3822         bool reverse;
3823 };
3825 #define SORT_STATE(fields) { fields, ARRAY_SIZE(fields), 0 }
3826 #define get_sort_field(state) ((state).fields[(state).current])
3827 #define sort_order(state, result) ((state).reverse ? -(result) : (result))
3829 static void
3830 sort_view(struct view *view, enum request request, struct sort_state *state,
3831           int (*compare)(const void *, const void *))
3833         switch (request) {
3834         case REQ_TOGGLE_SORT_FIELD:
3835                 state->current = (state->current + 1) % state->size;
3836                 break;
3838         case REQ_TOGGLE_SORT_ORDER:
3839                 state->reverse = !state->reverse;
3840                 break;
3841         default:
3842                 die("Not a sort request");
3843         }
3845         qsort(view->line, view->lines, sizeof(*view->line), compare);
3846         redraw_view(view);
3849 DEFINE_ALLOCATOR(realloc_authors, const char *, 256)
3851 /* Small author cache to reduce memory consumption. It uses binary
3852  * search to lookup or find place to position new entries. No entries
3853  * are ever freed. */
3854 static const char *
3855 get_author(const char *name)
3857         static const char **authors;
3858         static size_t authors_size;
3859         int from = 0, to = authors_size - 1;
3861         while (from <= to) {
3862                 size_t pos = (to + from) / 2;
3863                 int cmp = strcmp(name, authors[pos]);
3865                 if (!cmp)
3866                         return authors[pos];
3868                 if (cmp < 0)
3869                         to = pos - 1;
3870                 else
3871                         from = pos + 1;
3872         }
3874         if (!realloc_authors(&authors, authors_size, 1))
3875                 return NULL;
3876         name = strdup(name);
3877         if (!name)
3878                 return NULL;
3880         memmove(authors + from + 1, authors + from, (authors_size - from) * sizeof(*authors));
3881         authors[from] = name;
3882         authors_size++;
3884         return name;
3887 static void
3888 parse_timesec(struct time *time, const char *sec)
3890         time->sec = (time_t) atol(sec);
3893 static void
3894 parse_timezone(struct time *time, const char *zone)
3896         long tz;
3898         tz  = ('0' - zone[1]) * 60 * 60 * 10;
3899         tz += ('0' - zone[2]) * 60 * 60;
3900         tz += ('0' - zone[3]) * 60 * 10;
3901         tz += ('0' - zone[4]) * 60;
3903         if (zone[0] == '-')
3904                 tz = -tz;
3906         time->tz = tz;
3907         time->sec -= tz;
3910 /* Parse author lines where the name may be empty:
3911  *      author  <email@address.tld> 1138474660 +0100
3912  */
3913 static void
3914 parse_author_line(char *ident, const char **author, struct time *time)
3916         char *nameend = strchr(ident, '<');
3917         char *emailend = strchr(ident, '>');
3919         if (nameend && emailend)
3920                 *nameend = *emailend = 0;
3921         ident = chomp_string(ident);
3922         if (!*ident) {
3923                 if (nameend)
3924                         ident = chomp_string(nameend + 1);
3925                 if (!*ident)
3926                         ident = "Unknown";
3927         }
3929         *author = get_author(ident);
3931         /* Parse epoch and timezone */
3932         if (emailend && emailend[1] == ' ') {
3933                 char *secs = emailend + 2;
3934                 char *zone = strchr(secs, ' ');
3936                 parse_timesec(time, secs);
3938                 if (zone && strlen(zone) == STRING_SIZE(" +0700"))
3939                         parse_timezone(time, zone + 1);
3940         }
3943 static bool
3944 open_commit_parent_menu(char buf[SIZEOF_STR], int *parents)
3946         char rev[SIZEOF_REV];
3947         const char *revlist_argv[] = {
3948                 "git", "log", "--no-color", "-1", "--pretty=format:%s", rev, NULL
3949         };
3950         struct menu_item *items;
3951         char text[SIZEOF_STR];
3952         bool ok = TRUE;
3953         int i;
3955         items = calloc(*parents + 1, sizeof(*items));
3956         if (!items)
3957                 return FALSE;
3959         for (i = 0; i < *parents; i++) {
3960                 string_copy_rev(rev, &buf[SIZEOF_REV * i]);
3961                 if (!io_run_buf(revlist_argv, text, sizeof(text)) ||
3962                     !(items[i].text = strdup(text))) {
3963                         ok = FALSE;
3964                         break;
3965                 }
3966         }
3968         if (ok) {
3969                 *parents = 0;
3970                 ok = prompt_menu("Select parent", items, parents);
3971         }
3972         for (i = 0; items[i].text; i++)
3973                 free((char *) items[i].text);
3974         free(items);
3975         return ok;
3978 static bool
3979 select_commit_parent(const char *id, char rev[SIZEOF_REV], const char *path)
3981         char buf[SIZEOF_STR * 4];
3982         const char *revlist_argv[] = {
3983                 "git", "log", "--no-color", "-1",
3984                         "--pretty=format:%P", id, "--", path, NULL
3985         };
3986         int parents;
3988         if (!io_run_buf(revlist_argv, buf, sizeof(buf)) ||
3989             (parents = strlen(buf) / 40) < 0) {
3990                 report("Failed to get parent information");
3991                 return FALSE;
3993         } else if (parents == 0) {
3994                 if (path)
3995                         report("Path '%s' does not exist in the parent", path);
3996                 else
3997                         report("The selected commit has no parents");
3998                 return FALSE;
3999         }
4001         if (parents == 1)
4002                 parents = 0;
4003         else if (!open_commit_parent_menu(buf, &parents))
4004                 return FALSE;
4006         string_copy_rev(rev, &buf[41 * parents]);
4007         return TRUE;
4010 /*
4011  * Pager backend
4012  */
4014 static bool
4015 pager_draw(struct view *view, struct line *line, unsigned int lineno)
4017         char text[SIZEOF_STR];
4019         if (opt_line_number && draw_lineno(view, lineno))
4020                 return TRUE;
4022         string_expand(text, sizeof(text), line->data, opt_tab_size);
4023         draw_text(view, line->type, text, TRUE);
4024         return TRUE;
4027 static bool
4028 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
4030         const char *describe_argv[] = { "git", "describe", commit_id, NULL };
4031         char ref[SIZEOF_STR];
4033         if (!io_run_buf(describe_argv, ref, sizeof(ref)) || !*ref)
4034                 return TRUE;
4036         /* This is the only fatal call, since it can "corrupt" the buffer. */
4037         if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
4038                 return FALSE;
4040         return TRUE;
4043 static void
4044 add_pager_refs(struct view *view, struct line *line)
4046         char buf[SIZEOF_STR];
4047         char *commit_id = (char *)line->data + STRING_SIZE("commit ");
4048         struct ref_list *list;
4049         size_t bufpos = 0, i;
4050         const char *sep = "Refs: ";
4051         bool is_tag = FALSE;
4053         assert(line->type == LINE_COMMIT);
4055         list = get_ref_list(commit_id);
4056         if (!list) {
4057                 if (view->type == VIEW_DIFF)
4058                         goto try_add_describe_ref;
4059                 return;
4060         }
4062         for (i = 0; i < list->size; i++) {
4063                 struct ref *ref = list->refs[i];
4064                 const char *fmt = ref->tag    ? "%s[%s]" :
4065                                   ref->remote ? "%s<%s>" : "%s%s";
4067                 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
4068                         return;
4069                 sep = ", ";
4070                 if (ref->tag)
4071                         is_tag = TRUE;
4072         }
4074         if (!is_tag && view->type == VIEW_DIFF) {
4075 try_add_describe_ref:
4076                 /* Add <tag>-g<commit_id> "fake" reference. */
4077                 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
4078                         return;
4079         }
4081         if (bufpos == 0)
4082                 return;
4084         add_line_text(view, buf, LINE_PP_REFS);
4087 static bool
4088 pager_read(struct view *view, char *data)
4090         struct line *line;
4092         if (!data)
4093                 return TRUE;
4095         line = add_line_text(view, data, get_line_type(data));
4096         if (!line)
4097                 return FALSE;
4099         if (line->type == LINE_COMMIT &&
4100             (view->type == VIEW_DIFF ||
4101              view->type == VIEW_LOG))
4102                 add_pager_refs(view, line);
4104         return TRUE;
4107 static enum request
4108 pager_request(struct view *view, enum request request, struct line *line)
4110         int split = 0;
4112         if (request != REQ_ENTER)
4113                 return request;
4115         if (line->type == LINE_COMMIT &&
4116            (view->type == VIEW_LOG ||
4117             view->type == VIEW_PAGER)) {
4118                 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
4119                 split = 1;
4120         }
4122         /* Always scroll the view even if it was split. That way
4123          * you can use Enter to scroll through the log view and
4124          * split open each commit diff. */
4125         scroll_view(view, REQ_SCROLL_LINE_DOWN);
4127         /* FIXME: A minor workaround. Scrolling the view will call report("")
4128          * but if we are scrolling a non-current view this won't properly
4129          * update the view title. */
4130         if (split)
4131                 update_view_title(view);
4133         return REQ_NONE;
4136 static bool
4137 pager_grep(struct view *view, struct line *line)
4139         const char *text[] = { line->data, NULL };
4141         return grep_text(view, text);
4144 static void
4145 pager_select(struct view *view, struct line *line)
4147         if (line->type == LINE_COMMIT) {
4148                 char *text = (char *)line->data + STRING_SIZE("commit ");
4150                 if (view->type != VIEW_PAGER)
4151                         string_copy_rev(view->ref, text);
4152                 string_copy_rev(ref_commit, text);
4153         }
4156 static struct view_ops pager_ops = {
4157         "line",
4158         NULL,
4159         NULL,
4160         pager_read,
4161         pager_draw,
4162         pager_request,
4163         pager_grep,
4164         pager_select,
4165 };
4167 static const char *log_argv[SIZEOF_ARG] = {
4168         "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
4169 };
4171 static enum request
4172 log_request(struct view *view, enum request request, struct line *line)
4174         switch (request) {
4175         case REQ_REFRESH:
4176                 load_refs();
4177                 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
4178                 return REQ_NONE;
4179         default:
4180                 return pager_request(view, request, line);
4181         }
4184 static struct view_ops log_ops = {
4185         "line",
4186         log_argv,
4187         NULL,
4188         pager_read,
4189         pager_draw,
4190         log_request,
4191         pager_grep,
4192         pager_select,
4193 };
4195 static const char *diff_argv[SIZEOF_ARG] = {
4196         "git", "show", "--pretty=fuller", "--no-color", "--root",
4197                 "--patch-with-stat", "--find-copies-harder", "-C", "%(commit)", NULL
4198 };
4200 static struct view_ops diff_ops = {
4201         "line",
4202         diff_argv,
4203         NULL,
4204         pager_read,
4205         pager_draw,
4206         pager_request,
4207         pager_grep,
4208         pager_select,
4209 };
4211 /*
4212  * Help backend
4213  */
4215 static bool help_keymap_hidden[ARRAY_SIZE(keymap_table)];
4217 static bool
4218 help_open_keymap_title(struct view *view, enum keymap keymap)
4220         struct line *line;
4222         line = add_line_format(view, LINE_HELP_KEYMAP, "[%c] %s bindings",
4223                                help_keymap_hidden[keymap] ? '+' : '-',
4224                                enum_name(keymap_table[keymap]));
4225         if (line)
4226                 line->other = keymap;
4228         return help_keymap_hidden[keymap];
4231 static void
4232 help_open_keymap(struct view *view, enum keymap keymap)
4234         const char *group = NULL;
4235         char buf[SIZEOF_STR];
4236         size_t bufpos;
4237         bool add_title = TRUE;
4238         int i;
4240         for (i = 0; i < ARRAY_SIZE(req_info); i++) {
4241                 const char *key = NULL;
4243                 if (req_info[i].request == REQ_NONE)
4244                         continue;
4246                 if (!req_info[i].request) {
4247                         group = req_info[i].help;
4248                         continue;
4249                 }
4251                 key = get_keys(keymap, req_info[i].request, TRUE);
4252                 if (!key || !*key)
4253                         continue;
4255                 if (add_title && help_open_keymap_title(view, keymap))
4256                         return;
4257                 add_title = FALSE;
4259                 if (group) {
4260                         add_line_text(view, group, LINE_HELP_GROUP);
4261                         group = NULL;
4262                 }
4264                 add_line_format(view, LINE_DEFAULT, "    %-25s %-20s %s", key,
4265                                 enum_name(req_info[i]), req_info[i].help);
4266         }
4268         group = "External commands:";
4270         for (i = 0; i < run_requests; i++) {
4271                 struct run_request *req = get_run_request(REQ_NONE + i + 1);
4272                 const char *key;
4273                 int argc;
4275                 if (!req || req->keymap != keymap)
4276                         continue;
4278                 key = get_key_name(req->key);
4279                 if (!*key)
4280                         key = "(no key defined)";
4282                 if (add_title && help_open_keymap_title(view, keymap))
4283                         return;
4284                 if (group) {
4285                         add_line_text(view, group, LINE_HELP_GROUP);
4286                         group = NULL;
4287                 }
4289                 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
4290                         if (!string_format_from(buf, &bufpos, "%s%s",
4291                                                 argc ? " " : "", req->argv[argc]))
4292                                 return;
4294                 add_line_format(view, LINE_DEFAULT, "    %-25s `%s`", key, buf);
4295         }
4298 static bool
4299 help_open(struct view *view)
4301         enum keymap keymap;
4303         reset_view(view);
4304         add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
4305         add_line_text(view, "", LINE_DEFAULT);
4307         for (keymap = 0; keymap < ARRAY_SIZE(keymap_table); keymap++)
4308                 help_open_keymap(view, keymap);
4310         return TRUE;
4313 static enum request
4314 help_request(struct view *view, enum request request, struct line *line)
4316         switch (request) {
4317         case REQ_ENTER:
4318                 if (line->type == LINE_HELP_KEYMAP) {
4319                         help_keymap_hidden[line->other] =
4320                                 !help_keymap_hidden[line->other];
4321                         view->p_restore = TRUE;
4322                         open_view(view, REQ_VIEW_HELP, OPEN_REFRESH);
4323                 }
4325                 return REQ_NONE;
4326         default:
4327                 return pager_request(view, request, line);
4328         }
4331 static struct view_ops help_ops = {
4332         "line",
4333         NULL,
4334         help_open,
4335         NULL,
4336         pager_draw,
4337         help_request,
4338         pager_grep,
4339         pager_select,
4340 };
4343 /*
4344  * Tree backend
4345  */
4347 struct tree_stack_entry {
4348         struct tree_stack_entry *prev;  /* Entry below this in the stack */
4349         unsigned long lineno;           /* Line number to restore */
4350         char *name;                     /* Position of name in opt_path */
4351 };
4353 /* The top of the path stack. */
4354 static struct tree_stack_entry *tree_stack = NULL;
4355 unsigned long tree_lineno = 0;
4357 static void
4358 pop_tree_stack_entry(void)
4360         struct tree_stack_entry *entry = tree_stack;
4362         tree_lineno = entry->lineno;
4363         entry->name[0] = 0;
4364         tree_stack = entry->prev;
4365         free(entry);
4368 static void
4369 push_tree_stack_entry(const char *name, unsigned long lineno)
4371         struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
4372         size_t pathlen = strlen(opt_path);
4374         if (!entry)
4375                 return;
4377         entry->prev = tree_stack;
4378         entry->name = opt_path + pathlen;
4379         tree_stack = entry;
4381         if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
4382                 pop_tree_stack_entry();
4383                 return;
4384         }
4386         /* Move the current line to the first tree entry. */
4387         tree_lineno = 1;
4388         entry->lineno = lineno;
4391 /* Parse output from git-ls-tree(1):
4392  *
4393  * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
4394  */
4396 #define SIZEOF_TREE_ATTR \
4397         STRING_SIZE("100644 blob f931e1d229c3e185caad4449bf5b66ed72462657\t")
4399 #define SIZEOF_TREE_MODE \
4400         STRING_SIZE("100644 ")
4402 #define TREE_ID_OFFSET \
4403         STRING_SIZE("100644 blob ")
4405 struct tree_entry {
4406         char id[SIZEOF_REV];
4407         mode_t mode;
4408         struct time time;               /* Date from the author ident. */
4409         const char *author;             /* Author of the commit. */
4410         char name[1];
4411 };
4413 static const char *
4414 tree_path(const struct line *line)
4416         return ((struct tree_entry *) line->data)->name;
4419 static int
4420 tree_compare_entry(const struct line *line1, const struct line *line2)
4422         if (line1->type != line2->type)
4423                 return line1->type == LINE_TREE_DIR ? -1 : 1;
4424         return strcmp(tree_path(line1), tree_path(line2));
4427 static const enum sort_field tree_sort_fields[] = {
4428         ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
4429 };
4430 static struct sort_state tree_sort_state = SORT_STATE(tree_sort_fields);
4432 static int
4433 tree_compare(const void *l1, const void *l2)
4435         const struct line *line1 = (const struct line *) l1;
4436         const struct line *line2 = (const struct line *) l2;
4437         const struct tree_entry *entry1 = ((const struct line *) l1)->data;
4438         const struct tree_entry *entry2 = ((const struct line *) l2)->data;
4440         if (line1->type == LINE_TREE_HEAD)
4441                 return -1;
4442         if (line2->type == LINE_TREE_HEAD)
4443                 return 1;
4445         switch (get_sort_field(tree_sort_state)) {
4446         case ORDERBY_DATE:
4447                 return sort_order(tree_sort_state, timecmp(&entry1->time, &entry2->time));
4449         case ORDERBY_AUTHOR:
4450                 return sort_order(tree_sort_state, strcmp(entry1->author, entry2->author));
4452         case ORDERBY_NAME:
4453         default:
4454                 return sort_order(tree_sort_state, tree_compare_entry(line1, line2));
4455         }
4459 static struct line *
4460 tree_entry(struct view *view, enum line_type type, const char *path,
4461            const char *mode, const char *id)
4463         struct tree_entry *entry = calloc(1, sizeof(*entry) + strlen(path));
4464         struct line *line = entry ? add_line_data(view, entry, type) : NULL;
4466         if (!entry || !line) {
4467                 free(entry);
4468                 return NULL;
4469         }
4471         strncpy(entry->name, path, strlen(path));
4472         if (mode)
4473                 entry->mode = strtoul(mode, NULL, 8);
4474         if (id)
4475                 string_copy_rev(entry->id, id);
4477         return line;
4480 static bool
4481 tree_read_date(struct view *view, char *text, bool *read_date)
4483         static const char *author_name;
4484         static struct time author_time;
4486         if (!text && *read_date) {
4487                 *read_date = FALSE;
4488                 return TRUE;
4490         } else if (!text) {
4491                 char *path = *opt_path ? opt_path : ".";
4492                 /* Find next entry to process */
4493                 const char *log_file[] = {
4494                         "git", "log", "--no-color", "--pretty=raw",
4495                                 "--cc", "--raw", view->id, "--", path, NULL
4496                 };
4498                 if (!view->lines) {
4499                         tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL);
4500                         report("Tree is empty");
4501                         return TRUE;
4502                 }
4504                 if (!start_update(view, log_file, opt_cdup)) {
4505                         report("Failed to load tree data");
4506                         return TRUE;
4507                 }
4509                 *read_date = TRUE;
4510                 return FALSE;
4512         } else if (*text == 'a' && get_line_type(text) == LINE_AUTHOR) {
4513                 parse_author_line(text + STRING_SIZE("author "),
4514                                   &author_name, &author_time);
4516         } else if (*text == ':') {
4517                 char *pos;
4518                 size_t annotated = 1;
4519                 size_t i;
4521                 pos = strchr(text, '\t');
4522                 if (!pos)
4523                         return TRUE;
4524                 text = pos + 1;
4525                 if (*opt_path && !strncmp(text, opt_path, strlen(opt_path)))
4526                         text += strlen(opt_path);
4527                 pos = strchr(text, '/');
4528                 if (pos)
4529                         *pos = 0;
4531                 for (i = 1; i < view->lines; i++) {
4532                         struct line *line = &view->line[i];
4533                         struct tree_entry *entry = line->data;
4535                         annotated += !!entry->author;
4536                         if (entry->author || strcmp(entry->name, text))
4537                                 continue;
4539                         entry->author = author_name;
4540                         entry->time = author_time;
4541                         line->dirty = 1;
4542                         break;
4543                 }
4545                 if (annotated == view->lines)
4546                         io_kill(view->pipe);
4547         }
4548         return TRUE;
4551 static bool
4552 tree_read(struct view *view, char *text)
4554         static bool read_date = FALSE;
4555         struct tree_entry *data;
4556         struct line *entry, *line;
4557         enum line_type type;
4558         size_t textlen = text ? strlen(text) : 0;
4559         char *path = text + SIZEOF_TREE_ATTR;
4561         if (read_date || !text)
4562                 return tree_read_date(view, text, &read_date);
4564         if (textlen <= SIZEOF_TREE_ATTR)
4565                 return FALSE;
4566         if (view->lines == 0 &&
4567             !tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL))
4568                 return FALSE;
4570         /* Strip the path part ... */
4571         if (*opt_path) {
4572                 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
4573                 size_t striplen = strlen(opt_path);
4575                 if (pathlen > striplen)
4576                         memmove(path, path + striplen,
4577                                 pathlen - striplen + 1);
4579                 /* Insert "link" to parent directory. */
4580                 if (view->lines == 1 &&
4581                     !tree_entry(view, LINE_TREE_DIR, "..", "040000", view->ref))
4582                         return FALSE;
4583         }
4585         type = text[SIZEOF_TREE_MODE] == 't' ? LINE_TREE_DIR : LINE_TREE_FILE;
4586         entry = tree_entry(view, type, path, text, text + TREE_ID_OFFSET);
4587         if (!entry)
4588                 return FALSE;
4589         data = entry->data;
4591         /* Skip "Directory ..." and ".." line. */
4592         for (line = &view->line[1 + !!*opt_path]; line < entry; line++) {
4593                 if (tree_compare_entry(line, entry) <= 0)
4594                         continue;
4596                 memmove(line + 1, line, (entry - line) * sizeof(*entry));
4598                 line->data = data;
4599                 line->type = type;
4600                 for (; line <= entry; line++)
4601                         line->dirty = line->cleareol = 1;
4602                 return TRUE;
4603         }
4605         if (tree_lineno > view->lineno) {
4606                 view->lineno = tree_lineno;
4607                 tree_lineno = 0;
4608         }
4610         return TRUE;
4613 static bool
4614 tree_draw(struct view *view, struct line *line, unsigned int lineno)
4616         struct tree_entry *entry = line->data;
4618         if (line->type == LINE_TREE_HEAD) {
4619                 if (draw_text(view, line->type, "Directory path /", TRUE))
4620                         return TRUE;
4621         } else {
4622                 if (draw_mode(view, entry->mode))
4623                         return TRUE;
4625                 if (opt_author && draw_author(view, entry->author))
4626                         return TRUE;
4628                 if (opt_date && draw_date(view, &entry->time))
4629                         return TRUE;
4630         }
4631         if (draw_text(view, line->type, entry->name, TRUE))
4632                 return TRUE;
4633         return TRUE;
4636 static void
4637 open_blob_editor(const char *id)
4639         const char *blob_argv[] = { "git", "cat-file", "blob", id, NULL };
4640         char file[SIZEOF_STR] = "/tmp/tigblob.XXXXXX";
4641         int fd = mkstemp(file);
4643         if (fd == -1)
4644                 report("Failed to create temporary file");
4645         else if (!io_run_append(blob_argv, fd))
4646                 report("Failed to save blob data to file");
4647         else
4648                 open_editor(file);
4649         if (fd != -1)
4650                 unlink(file);
4653 static enum request
4654 tree_request(struct view *view, enum request request, struct line *line)
4656         enum open_flags flags;
4657         struct tree_entry *entry = line->data;
4659         switch (request) {
4660         case REQ_VIEW_BLAME:
4661                 if (line->type != LINE_TREE_FILE) {
4662                         report("Blame only supported for files");
4663                         return REQ_NONE;
4664                 }
4666                 string_copy(opt_ref, view->vid);
4667                 return request;
4669         case REQ_EDIT:
4670                 if (line->type != LINE_TREE_FILE) {
4671                         report("Edit only supported for files");
4672                 } else if (!is_head_commit(view->vid)) {
4673                         open_blob_editor(entry->id);
4674                 } else {
4675                         open_editor(opt_file);
4676                 }
4677                 return REQ_NONE;
4679         case REQ_TOGGLE_SORT_FIELD:
4680         case REQ_TOGGLE_SORT_ORDER:
4681                 sort_view(view, request, &tree_sort_state, tree_compare);
4682                 return REQ_NONE;
4684         case REQ_PARENT:
4685                 if (!*opt_path) {
4686                         /* quit view if at top of tree */
4687                         return REQ_VIEW_CLOSE;
4688                 }
4689                 /* fake 'cd  ..' */
4690                 line = &view->line[1];
4691                 break;
4693         case REQ_ENTER:
4694                 break;
4696         default:
4697                 return request;
4698         }
4700         /* Cleanup the stack if the tree view is at a different tree. */
4701         while (!*opt_path && tree_stack)
4702                 pop_tree_stack_entry();
4704         switch (line->type) {
4705         case LINE_TREE_DIR:
4706                 /* Depending on whether it is a subdirectory or parent link
4707                  * mangle the path buffer. */
4708                 if (line == &view->line[1] && *opt_path) {
4709                         pop_tree_stack_entry();
4711                 } else {
4712                         const char *basename = tree_path(line);
4714                         push_tree_stack_entry(basename, view->lineno);
4715                 }
4717                 /* Trees and subtrees share the same ID, so they are not not
4718                  * unique like blobs. */
4719                 flags = OPEN_RELOAD;
4720                 request = REQ_VIEW_TREE;
4721                 break;
4723         case LINE_TREE_FILE:
4724                 flags = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
4725                 request = REQ_VIEW_BLOB;
4726                 break;
4728         default:
4729                 return REQ_NONE;
4730         }
4732         open_view(view, request, flags);
4733         if (request == REQ_VIEW_TREE)
4734                 view->lineno = tree_lineno;
4736         return REQ_NONE;
4739 static bool
4740 tree_grep(struct view *view, struct line *line)
4742         struct tree_entry *entry = line->data;
4743         const char *text[] = {
4744                 entry->name,
4745                 opt_author ? entry->author : "",
4746                 mkdate(&entry->time, opt_date),
4747                 NULL
4748         };
4750         return grep_text(view, text);
4753 static void
4754 tree_select(struct view *view, struct line *line)
4756         struct tree_entry *entry = line->data;
4758         if (line->type == LINE_TREE_FILE) {
4759                 string_copy_rev(ref_blob, entry->id);
4760                 string_format(opt_file, "%s%s", opt_path, tree_path(line));
4762         } else if (line->type != LINE_TREE_DIR) {
4763                 return;
4764         }
4766         string_copy_rev(view->ref, entry->id);
4769 static bool
4770 tree_prepare(struct view *view)
4772         if (view->lines == 0 && opt_prefix[0]) {
4773                 char *pos = opt_prefix;
4775                 while (pos && *pos) {
4776                         char *end = strchr(pos, '/');
4778                         if (end)
4779                                 *end = 0;
4780                         push_tree_stack_entry(pos, 0);
4781                         pos = end;
4782                         if (end) {
4783                                 *end = '/';
4784                                 pos++;
4785                         }
4786                 }
4788         } else if (strcmp(view->vid, view->id)) {
4789                 opt_path[0] = 0;
4790         }
4792         return prepare_io(view, opt_cdup, view->ops->argv, TRUE);
4795 static const char *tree_argv[SIZEOF_ARG] = {
4796         "git", "ls-tree", "%(commit)", "%(directory)", NULL
4797 };
4799 static struct view_ops tree_ops = {
4800         "file",
4801         tree_argv,
4802         NULL,
4803         tree_read,
4804         tree_draw,
4805         tree_request,
4806         tree_grep,
4807         tree_select,
4808         tree_prepare,
4809 };
4811 static bool
4812 blob_read(struct view *view, char *line)
4814         if (!line)
4815                 return TRUE;
4816         return add_line_text(view, line, LINE_DEFAULT) != NULL;
4819 static enum request
4820 blob_request(struct view *view, enum request request, struct line *line)
4822         switch (request) {
4823         case REQ_EDIT:
4824                 open_blob_editor(view->vid);
4825                 return REQ_NONE;
4826         default:
4827                 return pager_request(view, request, line);
4828         }
4831 static const char *blob_argv[SIZEOF_ARG] = {
4832         "git", "cat-file", "blob", "%(blob)", NULL
4833 };
4835 static struct view_ops blob_ops = {
4836         "line",
4837         blob_argv,
4838         NULL,
4839         blob_read,
4840         pager_draw,
4841         blob_request,
4842         pager_grep,
4843         pager_select,
4844 };
4846 /*
4847  * Blame backend
4848  *
4849  * Loading the blame view is a two phase job:
4850  *
4851  *  1. File content is read either using opt_file from the
4852  *     filesystem or using git-cat-file.
4853  *  2. Then blame information is incrementally added by
4854  *     reading output from git-blame.
4855  */
4857 struct blame_commit {
4858         char id[SIZEOF_REV];            /* SHA1 ID. */
4859         char title[128];                /* First line of the commit message. */
4860         const char *author;             /* Author of the commit. */
4861         struct time time;               /* Date from the author ident. */
4862         char filename[128];             /* Name of file. */
4863         bool has_previous;              /* Was a "previous" line detected. */
4864 };
4866 struct blame {
4867         struct blame_commit *commit;
4868         unsigned long lineno;
4869         char text[1];
4870 };
4872 static bool
4873 blame_open(struct view *view)
4875         char path[SIZEOF_STR];
4877         if (!view->prev && *opt_prefix) {
4878                 string_copy(path, opt_file);
4879                 if (!string_format(opt_file, "%s%s", opt_prefix, path))
4880                         return FALSE;
4881         }
4883         if (*opt_ref || !io_open(&view->io, "%s%s", opt_cdup, opt_file)) {
4884                 const char *blame_cat_file_argv[] = {
4885                         "git", "cat-file", "blob", path, NULL
4886                 };
4888                 if (!string_format(path, "%s:%s", opt_ref, opt_file) ||
4889                     !start_update(view, blame_cat_file_argv, opt_cdup))
4890                         return FALSE;
4891         }
4893         setup_update(view, opt_file);
4894         string_format(view->ref, "%s ...", opt_file);
4896         return TRUE;
4899 static struct blame_commit *
4900 get_blame_commit(struct view *view, const char *id)
4902         size_t i;
4904         for (i = 0; i < view->lines; i++) {
4905                 struct blame *blame = view->line[i].data;
4907                 if (!blame->commit)
4908                         continue;
4910                 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
4911                         return blame->commit;
4912         }
4914         {
4915                 struct blame_commit *commit = calloc(1, sizeof(*commit));
4917                 if (commit)
4918                         string_ncopy(commit->id, id, SIZEOF_REV);
4919                 return commit;
4920         }
4923 static bool
4924 parse_number(const char **posref, size_t *number, size_t min, size_t max)
4926         const char *pos = *posref;
4928         *posref = NULL;
4929         pos = strchr(pos + 1, ' ');
4930         if (!pos || !isdigit(pos[1]))
4931                 return FALSE;
4932         *number = atoi(pos + 1);
4933         if (*number < min || *number > max)
4934                 return FALSE;
4936         *posref = pos;
4937         return TRUE;
4940 static struct blame_commit *
4941 parse_blame_commit(struct view *view, const char *text, int *blamed)
4943         struct blame_commit *commit;
4944         struct blame *blame;
4945         const char *pos = text + SIZEOF_REV - 2;
4946         size_t orig_lineno = 0;
4947         size_t lineno;
4948         size_t group;
4950         if (strlen(text) <= SIZEOF_REV || pos[1] != ' ')
4951                 return NULL;
4953         if (!parse_number(&pos, &orig_lineno, 1, 9999999) ||
4954             !parse_number(&pos, &lineno, 1, view->lines) ||
4955             !parse_number(&pos, &group, 1, view->lines - lineno + 1))
4956                 return NULL;
4958         commit = get_blame_commit(view, text);
4959         if (!commit)
4960                 return NULL;
4962         *blamed += group;
4963         while (group--) {
4964                 struct line *line = &view->line[lineno + group - 1];
4966                 blame = line->data;
4967                 blame->commit = commit;
4968                 blame->lineno = orig_lineno + group - 1;
4969                 line->dirty = 1;
4970         }
4972         return commit;
4975 static bool
4976 blame_read_file(struct view *view, const char *line, bool *read_file)
4978         if (!line) {
4979                 const char *blame_argv[] = {
4980                         "git", "blame", "--incremental",
4981                                 *opt_ref ? opt_ref : "--incremental", "--", opt_file, NULL
4982                 };
4984                 if (view->lines == 0 && !view->prev)
4985                         die("No blame exist for %s", view->vid);
4987                 if (view->lines == 0 || !start_update(view, blame_argv, opt_cdup)) {
4988                         report("Failed to load blame data");
4989                         return TRUE;
4990                 }
4992                 *read_file = FALSE;
4993                 return FALSE;
4995         } else {
4996                 size_t linelen = strlen(line);
4997                 struct blame *blame = malloc(sizeof(*blame) + linelen);
4999                 if (!blame)
5000                         return FALSE;
5002                 blame->commit = NULL;
5003                 strncpy(blame->text, line, linelen);
5004                 blame->text[linelen] = 0;
5005                 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
5006         }
5009 static bool
5010 match_blame_header(const char *name, char **line)
5012         size_t namelen = strlen(name);
5013         bool matched = !strncmp(name, *line, namelen);
5015         if (matched)
5016                 *line += namelen;
5018         return matched;
5021 static bool
5022 blame_read(struct view *view, char *line)
5024         static struct blame_commit *commit = NULL;
5025         static int blamed = 0;
5026         static bool read_file = TRUE;
5028         if (read_file)
5029                 return blame_read_file(view, line, &read_file);
5031         if (!line) {
5032                 /* Reset all! */
5033                 commit = NULL;
5034                 blamed = 0;
5035                 read_file = TRUE;
5036                 string_format(view->ref, "%s", view->vid);
5037                 if (view_is_displayed(view)) {
5038                         update_view_title(view);
5039                         redraw_view_from(view, 0);
5040                 }
5041                 return TRUE;
5042         }
5044         if (!commit) {
5045                 commit = parse_blame_commit(view, line, &blamed);
5046                 string_format(view->ref, "%s %2d%%", view->vid,
5047                               view->lines ? blamed * 100 / view->lines : 0);
5049         } else if (match_blame_header("author ", &line)) {
5050                 commit->author = get_author(line);
5052         } else if (match_blame_header("author-time ", &line)) {
5053                 parse_timesec(&commit->time, line);
5055         } else if (match_blame_header("author-tz ", &line)) {
5056                 parse_timezone(&commit->time, line);
5058         } else if (match_blame_header("summary ", &line)) {
5059                 string_ncopy(commit->title, line, strlen(line));
5061         } else if (match_blame_header("previous ", &line)) {
5062                 commit->has_previous = TRUE;
5064         } else if (match_blame_header("filename ", &line)) {
5065                 string_ncopy(commit->filename, line, strlen(line));
5066                 commit = NULL;
5067         }
5069         return TRUE;
5072 static bool
5073 blame_draw(struct view *view, struct line *line, unsigned int lineno)
5075         struct blame *blame = line->data;
5076         struct time *time = NULL;
5077         const char *id = NULL, *author = NULL;
5078         char text[SIZEOF_STR];
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         string_expand(text, sizeof(text), blame->text, opt_tab_size);
5099         draw_text(view, LINE_DEFAULT, text, TRUE);
5100         return TRUE;
5103 static bool
5104 check_blame_commit(struct blame *blame, bool check_null_id)
5106         if (!blame->commit)
5107                 report("Commit data not loaded yet");
5108         else if (check_null_id && !strcmp(blame->commit->id, NULL_ID))
5109                 report("No commit exist for the selected line");
5110         else
5111                 return TRUE;
5112         return FALSE;
5115 static void
5116 setup_blame_parent_line(struct view *view, struct blame *blame)
5118         const char *diff_tree_argv[] = {
5119                 "git", "diff-tree", "-U0", blame->commit->id,
5120                         "--", blame->commit->filename, NULL
5121         };
5122         struct io io = {};
5123         int parent_lineno = -1;
5124         int blamed_lineno = -1;
5125         char *line;
5127         if (!io_run(&io, diff_tree_argv, NULL, IO_RD))
5128                 return;
5130         while ((line = io_get(&io, '\n', TRUE))) {
5131                 if (*line == '@') {
5132                         char *pos = strchr(line, '+');
5134                         parent_lineno = atoi(line + 4);
5135                         if (pos)
5136                                 blamed_lineno = atoi(pos + 1);
5138                 } else if (*line == '+' && parent_lineno != -1) {
5139                         if (blame->lineno == blamed_lineno - 1 &&
5140                             !strcmp(blame->text, line + 1)) {
5141                                 view->lineno = parent_lineno ? parent_lineno - 1 : 0;
5142                                 break;
5143                         }
5144                         blamed_lineno++;
5145                 }
5146         }
5148         io_done(&io);
5151 static enum request
5152 blame_request(struct view *view, enum request request, struct line *line)
5154         enum open_flags flags = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
5155         struct blame *blame = line->data;
5157         switch (request) {
5158         case REQ_VIEW_BLAME:
5159                 if (check_blame_commit(blame, TRUE)) {
5160                         string_copy(opt_ref, blame->commit->id);
5161                         string_copy(opt_file, blame->commit->filename);
5162                         if (blame->lineno)
5163                                 view->lineno = blame->lineno;
5164                         open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
5165                 }
5166                 break;
5168         case REQ_PARENT:
5169                 if (check_blame_commit(blame, TRUE) &&
5170                     select_commit_parent(blame->commit->id, opt_ref,
5171                                          blame->commit->filename)) {
5172                         string_copy(opt_file, blame->commit->filename);
5173                         setup_blame_parent_line(view, blame);
5174                         open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
5175                 }
5176                 break;
5178         case REQ_ENTER:
5179                 if (!check_blame_commit(blame, FALSE))
5180                         break;
5182                 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
5183                     !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
5184                         break;
5186                 if (!strcmp(blame->commit->id, NULL_ID)) {
5187                         struct view *diff = VIEW(REQ_VIEW_DIFF);
5188                         const char *diff_index_argv[] = {
5189                                 "git", "diff-index", "--root", "--patch-with-stat",
5190                                         "-C", "-M", "HEAD", "--", view->vid, NULL
5191                         };
5193                         if (!blame->commit->has_previous) {
5194                                 diff_index_argv[1] = "diff";
5195                                 diff_index_argv[2] = "--no-color";
5196                                 diff_index_argv[6] = "--";
5197                                 diff_index_argv[7] = "/dev/null";
5198                         }
5200                         if (!prepare_update(diff, diff_index_argv, NULL)) {
5201                                 report("Failed to allocate diff command");
5202                                 break;
5203                         }
5204                         flags |= OPEN_PREPARED;
5205                 }
5207                 open_view(view, REQ_VIEW_DIFF, flags);
5208                 if (VIEW(REQ_VIEW_DIFF)->pipe && !strcmp(blame->commit->id, NULL_ID))
5209                         string_copy_rev(VIEW(REQ_VIEW_DIFF)->ref, NULL_ID);
5210                 break;
5212         default:
5213                 return request;
5214         }
5216         return REQ_NONE;
5219 static bool
5220 blame_grep(struct view *view, struct line *line)
5222         struct blame *blame = line->data;
5223         struct blame_commit *commit = blame->commit;
5224         const char *text[] = {
5225                 blame->text,
5226                 commit ? commit->title : "",
5227                 commit ? commit->id : "",
5228                 commit && opt_author ? commit->author : "",
5229                 commit ? mkdate(&commit->time, opt_date) : "",
5230                 NULL
5231         };
5233         return grep_text(view, text);
5236 static void
5237 blame_select(struct view *view, struct line *line)
5239         struct blame *blame = line->data;
5240         struct blame_commit *commit = blame->commit;
5242         if (!commit)
5243                 return;
5245         if (!strcmp(commit->id, NULL_ID))
5246                 string_ncopy(ref_commit, "HEAD", 4);
5247         else
5248                 string_copy_rev(ref_commit, commit->id);
5251 static struct view_ops blame_ops = {
5252         "line",
5253         NULL,
5254         blame_open,
5255         blame_read,
5256         blame_draw,
5257         blame_request,
5258         blame_grep,
5259         blame_select,
5260 };
5262 /*
5263  * Branch backend
5264  */
5266 struct branch {
5267         const char *author;             /* Author of the last commit. */
5268         struct time time;               /* Date of the last activity. */
5269         const struct ref *ref;          /* Name and commit ID information. */
5270 };
5272 static const struct ref branch_all;
5274 static const enum sort_field branch_sort_fields[] = {
5275         ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
5276 };
5277 static struct sort_state branch_sort_state = SORT_STATE(branch_sort_fields);
5279 static int
5280 branch_compare(const void *l1, const void *l2)
5282         const struct branch *branch1 = ((const struct line *) l1)->data;
5283         const struct branch *branch2 = ((const struct line *) l2)->data;
5285         switch (get_sort_field(branch_sort_state)) {
5286         case ORDERBY_DATE:
5287                 return sort_order(branch_sort_state, timecmp(&branch1->time, &branch2->time));
5289         case ORDERBY_AUTHOR:
5290                 return sort_order(branch_sort_state, strcmp(branch1->author, branch2->author));
5292         case ORDERBY_NAME:
5293         default:
5294                 return sort_order(branch_sort_state, strcmp(branch1->ref->name, branch2->ref->name));
5295         }
5298 static bool
5299 branch_draw(struct view *view, struct line *line, unsigned int lineno)
5301         struct branch *branch = line->data;
5302         enum line_type type = branch->ref->head ? LINE_MAIN_HEAD : LINE_DEFAULT;
5304         if (opt_date && draw_date(view, &branch->time))
5305                 return TRUE;
5307         if (opt_author && draw_author(view, branch->author))
5308                 return TRUE;
5310         draw_text(view, type, branch->ref == &branch_all ? "All branches" : branch->ref->name, TRUE);
5311         return TRUE;
5314 static enum request
5315 branch_request(struct view *view, enum request request, struct line *line)
5317         struct branch *branch = line->data;
5319         switch (request) {
5320         case REQ_REFRESH:
5321                 load_refs();
5322                 open_view(view, REQ_VIEW_BRANCH, OPEN_REFRESH);
5323                 return REQ_NONE;
5325         case REQ_TOGGLE_SORT_FIELD:
5326         case REQ_TOGGLE_SORT_ORDER:
5327                 sort_view(view, request, &branch_sort_state, branch_compare);
5328                 return REQ_NONE;
5330         case REQ_ENTER:
5331                 if (branch->ref == &branch_all) {
5332                         const char *all_branches_argv[] = {
5333                                 "git", "log", "--no-color", "--pretty=raw", "--parents",
5334                                       "--topo-order", "--all", NULL
5335                         };
5336                         struct view *main_view = VIEW(REQ_VIEW_MAIN);
5338                         if (!prepare_update(main_view, all_branches_argv, NULL)) {
5339                                 report("Failed to load view of all branches");
5340                                 return REQ_NONE;
5341                         }
5342                         open_view(view, REQ_VIEW_MAIN, OPEN_PREPARED | OPEN_SPLIT);
5343                 } else {
5344                         open_view(view, REQ_VIEW_MAIN, OPEN_SPLIT);
5345                 }
5346                 return REQ_NONE;
5348         default:
5349                 return request;
5350         }
5353 static bool
5354 branch_read(struct view *view, char *line)
5356         static char id[SIZEOF_REV];
5357         struct branch *reference;
5358         size_t i;
5360         if (!line)
5361                 return TRUE;
5363         switch (get_line_type(line)) {
5364         case LINE_COMMIT:
5365                 string_copy_rev(id, line + STRING_SIZE("commit "));
5366                 return TRUE;
5368         case LINE_AUTHOR:
5369                 for (i = 0, reference = NULL; i < view->lines; i++) {
5370                         struct branch *branch = view->line[i].data;
5372                         if (strcmp(branch->ref->id, id))
5373                                 continue;
5375                         view->line[i].dirty = TRUE;
5376                         if (reference) {
5377                                 branch->author = reference->author;
5378                                 branch->time = reference->time;
5379                                 continue;
5380                         }
5382                         parse_author_line(line + STRING_SIZE("author "),
5383                                           &branch->author, &branch->time);
5384                         reference = branch;
5385                 }
5386                 return TRUE;
5388         default:
5389                 return TRUE;
5390         }
5394 static bool
5395 branch_open_visitor(void *data, const struct ref *ref)
5397         struct view *view = data;
5398         struct branch *branch;
5400         if (ref->tag || ref->ltag || ref->remote)
5401                 return TRUE;
5403         branch = calloc(1, sizeof(*branch));
5404         if (!branch)
5405                 return FALSE;
5407         branch->ref = ref;
5408         return !!add_line_data(view, branch, LINE_DEFAULT);
5411 static bool
5412 branch_open(struct view *view)
5414         const char *branch_log[] = {
5415                 "git", "log", "--no-color", "--pretty=raw",
5416                         "--simplify-by-decoration", "--all", NULL
5417         };
5419         if (!start_update(view, branch_log, NULL)) {
5420                 report("Failed to load branch data");
5421                 return TRUE;
5422         }
5424         setup_update(view, view->id);
5425         branch_open_visitor(view, &branch_all);
5426         foreach_ref(branch_open_visitor, view);
5427         view->p_restore = TRUE;
5429         return TRUE;
5432 static bool
5433 branch_grep(struct view *view, struct line *line)
5435         struct branch *branch = line->data;
5436         const char *text[] = {
5437                 branch->ref->name,
5438                 branch->author,
5439                 NULL
5440         };
5442         return grep_text(view, text);
5445 static void
5446 branch_select(struct view *view, struct line *line)
5448         struct branch *branch = line->data;
5450         string_copy_rev(view->ref, branch->ref->id);
5451         string_copy_rev(ref_commit, branch->ref->id);
5452         string_copy_rev(ref_head, branch->ref->id);
5453         string_copy_rev(ref_branch, branch->ref->name);
5456 static struct view_ops branch_ops = {
5457         "branch",
5458         NULL,
5459         branch_open,
5460         branch_read,
5461         branch_draw,
5462         branch_request,
5463         branch_grep,
5464         branch_select,
5465 };
5467 /*
5468  * Status backend
5469  */
5471 struct status {
5472         char status;
5473         struct {
5474                 mode_t mode;
5475                 char rev[SIZEOF_REV];
5476                 char name[SIZEOF_STR];
5477         } old;
5478         struct {
5479                 mode_t mode;
5480                 char rev[SIZEOF_REV];
5481                 char name[SIZEOF_STR];
5482         } new;
5483 };
5485 static char status_onbranch[SIZEOF_STR];
5486 static struct status stage_status;
5487 static enum line_type stage_line_type;
5488 static size_t stage_chunks;
5489 static int *stage_chunk;
5491 DEFINE_ALLOCATOR(realloc_ints, int, 32)
5493 /* This should work even for the "On branch" line. */
5494 static inline bool
5495 status_has_none(struct view *view, struct line *line)
5497         return line < view->line + view->lines && !line[1].data;
5500 /* Get fields from the diff line:
5501  * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
5502  */
5503 static inline bool
5504 status_get_diff(struct status *file, const char *buf, size_t bufsize)
5506         const char *old_mode = buf +  1;
5507         const char *new_mode = buf +  8;
5508         const char *old_rev  = buf + 15;
5509         const char *new_rev  = buf + 56;
5510         const char *status   = buf + 97;
5512         if (bufsize < 98 ||
5513             old_mode[-1] != ':' ||
5514             new_mode[-1] != ' ' ||
5515             old_rev[-1]  != ' ' ||
5516             new_rev[-1]  != ' ' ||
5517             status[-1]   != ' ')
5518                 return FALSE;
5520         file->status = *status;
5522         string_copy_rev(file->old.rev, old_rev);
5523         string_copy_rev(file->new.rev, new_rev);
5525         file->old.mode = strtoul(old_mode, NULL, 8);
5526         file->new.mode = strtoul(new_mode, NULL, 8);
5528         file->old.name[0] = file->new.name[0] = 0;
5530         return TRUE;
5533 static bool
5534 status_run(struct view *view, const char *argv[], char status, enum line_type type)
5536         struct status *unmerged = NULL;
5537         char *buf;
5538         struct io io = {};
5540         if (!io_run(&io, argv, opt_cdup, IO_RD))
5541                 return FALSE;
5543         add_line_data(view, NULL, type);
5545         while ((buf = io_get(&io, 0, TRUE))) {
5546                 struct status *file = unmerged;
5548                 if (!file) {
5549                         file = calloc(1, sizeof(*file));
5550                         if (!file || !add_line_data(view, file, type))
5551                                 goto error_out;
5552                 }
5554                 /* Parse diff info part. */
5555                 if (status) {
5556                         file->status = status;
5557                         if (status == 'A')
5558                                 string_copy(file->old.rev, NULL_ID);
5560                 } else if (!file->status || file == unmerged) {
5561                         if (!status_get_diff(file, buf, strlen(buf)))
5562                                 goto error_out;
5564                         buf = io_get(&io, 0, TRUE);
5565                         if (!buf)
5566                                 break;
5568                         /* Collapse all modified entries that follow an
5569                          * associated unmerged entry. */
5570                         if (unmerged == file) {
5571                                 unmerged->status = 'U';
5572                                 unmerged = NULL;
5573                         } else if (file->status == 'U') {
5574                                 unmerged = file;
5575                         }
5576                 }
5578                 /* Grab the old name for rename/copy. */
5579                 if (!*file->old.name &&
5580                     (file->status == 'R' || file->status == 'C')) {
5581                         string_ncopy(file->old.name, buf, strlen(buf));
5583                         buf = io_get(&io, 0, TRUE);
5584                         if (!buf)
5585                                 break;
5586                 }
5588                 /* git-ls-files just delivers a NUL separated list of
5589                  * file names similar to the second half of the
5590                  * git-diff-* output. */
5591                 string_ncopy(file->new.name, buf, strlen(buf));
5592                 if (!*file->old.name)
5593                         string_copy(file->old.name, file->new.name);
5594                 file = NULL;
5595         }
5597         if (io_error(&io)) {
5598 error_out:
5599                 io_done(&io);
5600                 return FALSE;
5601         }
5603         if (!view->line[view->lines - 1].data)
5604                 add_line_data(view, NULL, LINE_STAT_NONE);
5606         io_done(&io);
5607         return TRUE;
5610 /* Don't show unmerged entries in the staged section. */
5611 static const char *status_diff_index_argv[] = {
5612         "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
5613                              "--cached", "-M", "HEAD", NULL
5614 };
5616 static const char *status_diff_files_argv[] = {
5617         "git", "diff-files", "-z", NULL
5618 };
5620 static const char *status_list_other_argv[] = {
5621         "git", "ls-files", "-z", "--others", "--exclude-standard", opt_prefix, NULL
5622 };
5624 static const char *status_list_no_head_argv[] = {
5625         "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
5626 };
5628 static const char *update_index_argv[] = {
5629         "git", "update-index", "-q", "--unmerged", "--refresh", NULL
5630 };
5632 /* Restore the previous line number to stay in the context or select a
5633  * line with something that can be updated. */
5634 static void
5635 status_restore(struct view *view)
5637         if (view->p_lineno >= view->lines)
5638                 view->p_lineno = view->lines - 1;
5639         while (view->p_lineno < view->lines && !view->line[view->p_lineno].data)
5640                 view->p_lineno++;
5641         while (view->p_lineno > 0 && !view->line[view->p_lineno].data)
5642                 view->p_lineno--;
5644         /* If the above fails, always skip the "On branch" line. */
5645         if (view->p_lineno < view->lines)
5646                 view->lineno = view->p_lineno;
5647         else
5648                 view->lineno = 1;
5650         if (view->lineno < view->offset)
5651                 view->offset = view->lineno;
5652         else if (view->offset + view->height <= view->lineno)
5653                 view->offset = view->lineno - view->height + 1;
5655         view->p_restore = FALSE;
5658 static void
5659 status_update_onbranch(void)
5661         static const char *paths[][2] = {
5662                 { "rebase-apply/rebasing",      "Rebasing" },
5663                 { "rebase-apply/applying",      "Applying mailbox" },
5664                 { "rebase-apply/",              "Rebasing mailbox" },
5665                 { "rebase-merge/interactive",   "Interactive rebase" },
5666                 { "rebase-merge/",              "Rebase merge" },
5667                 { "MERGE_HEAD",                 "Merging" },
5668                 { "BISECT_LOG",                 "Bisecting" },
5669                 { "HEAD",                       "On branch" },
5670         };
5671         char buf[SIZEOF_STR];
5672         struct stat stat;
5673         int i;
5675         if (is_initial_commit()) {
5676                 string_copy(status_onbranch, "Initial commit");
5677                 return;
5678         }
5680         for (i = 0; i < ARRAY_SIZE(paths); i++) {
5681                 char *head = opt_head;
5683                 if (!string_format(buf, "%s/%s", opt_git_dir, paths[i][0]) ||
5684                     lstat(buf, &stat) < 0)
5685                         continue;
5687                 if (!*opt_head) {
5688                         struct io io = {};
5690                         if (io_open(&io, "%s/rebase-merge/head-name", opt_git_dir) &&
5691                             io_read_buf(&io, buf, sizeof(buf))) {
5692                                 head = buf;
5693                                 if (!prefixcmp(head, "refs/heads/"))
5694                                         head += STRING_SIZE("refs/heads/");
5695                         }
5696                 }
5698                 if (!string_format(status_onbranch, "%s %s", paths[i][1], head))
5699                         string_copy(status_onbranch, opt_head);
5700                 return;
5701         }
5703         string_copy(status_onbranch, "Not currently on any branch");
5706 /* First parse staged info using git-diff-index(1), then parse unstaged
5707  * info using git-diff-files(1), and finally untracked files using
5708  * git-ls-files(1). */
5709 static bool
5710 status_open(struct view *view)
5712         reset_view(view);
5714         add_line_data(view, NULL, LINE_STAT_HEAD);
5715         status_update_onbranch();
5717         io_run_bg(update_index_argv);
5719         if (is_initial_commit()) {
5720                 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
5721                         return FALSE;
5722         } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
5723                 return FALSE;
5724         }
5726         if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
5727             !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
5728                 return FALSE;
5730         /* Restore the exact position or use the specialized restore
5731          * mode? */
5732         if (!view->p_restore)
5733                 status_restore(view);
5734         return TRUE;
5737 static bool
5738 status_draw(struct view *view, struct line *line, unsigned int lineno)
5740         struct status *status = line->data;
5741         enum line_type type;
5742         const char *text;
5744         if (!status) {
5745                 switch (line->type) {
5746                 case LINE_STAT_STAGED:
5747                         type = LINE_STAT_SECTION;
5748                         text = "Changes to be committed:";
5749                         break;
5751                 case LINE_STAT_UNSTAGED:
5752                         type = LINE_STAT_SECTION;
5753                         text = "Changed but not updated:";
5754                         break;
5756                 case LINE_STAT_UNTRACKED:
5757                         type = LINE_STAT_SECTION;
5758                         text = "Untracked files:";
5759                         break;
5761                 case LINE_STAT_NONE:
5762                         type = LINE_DEFAULT;
5763                         text = "  (no files)";
5764                         break;
5766                 case LINE_STAT_HEAD:
5767                         type = LINE_STAT_HEAD;
5768                         text = status_onbranch;
5769                         break;
5771                 default:
5772                         return FALSE;
5773                 }
5774         } else {
5775                 static char buf[] = { '?', ' ', ' ', ' ', 0 };
5777                 buf[0] = status->status;
5778                 if (draw_text(view, line->type, buf, TRUE))
5779                         return TRUE;
5780                 type = LINE_DEFAULT;
5781                 text = status->new.name;
5782         }
5784         draw_text(view, type, text, TRUE);
5785         return TRUE;
5788 static enum request
5789 status_load_error(struct view *view, struct view *stage, const char *path)
5791         if (displayed_views() == 2 || display[current_view] != view)
5792                 maximize_view(view);
5793         report("Failed to load '%s': %s", path, io_strerror(&stage->io));
5794         return REQ_NONE;
5797 static enum request
5798 status_enter(struct view *view, struct line *line)
5800         struct status *status = line->data;
5801         const char *oldpath = status ? status->old.name : NULL;
5802         /* Diffs for unmerged entries are empty when passing the new
5803          * path, so leave it empty. */
5804         const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
5805         const char *info;
5806         enum open_flags split;
5807         struct view *stage = VIEW(REQ_VIEW_STAGE);
5809         if (line->type == LINE_STAT_NONE ||
5810             (!status && line[1].type == LINE_STAT_NONE)) {
5811                 report("No file to diff");
5812                 return REQ_NONE;
5813         }
5815         switch (line->type) {
5816         case LINE_STAT_STAGED:
5817                 if (is_initial_commit()) {
5818                         const char *no_head_diff_argv[] = {
5819                                 "git", "diff", "--no-color", "--patch-with-stat",
5820                                         "--", "/dev/null", newpath, NULL
5821                         };
5823                         if (!prepare_update(stage, no_head_diff_argv, opt_cdup))
5824                                 return status_load_error(view, stage, newpath);
5825                 } else {
5826                         const char *index_show_argv[] = {
5827                                 "git", "diff-index", "--root", "--patch-with-stat",
5828                                         "-C", "-M", "--cached", "HEAD", "--",
5829                                         oldpath, newpath, NULL
5830                         };
5832                         if (!prepare_update(stage, index_show_argv, opt_cdup))
5833                                 return status_load_error(view, stage, newpath);
5834                 }
5836                 if (status)
5837                         info = "Staged changes to %s";
5838                 else
5839                         info = "Staged changes";
5840                 break;
5842         case LINE_STAT_UNSTAGED:
5843         {
5844                 const char *files_show_argv[] = {
5845                         "git", "diff-files", "--root", "--patch-with-stat",
5846                                 "-C", "-M", "--", oldpath, newpath, NULL
5847                 };
5849                 if (!prepare_update(stage, files_show_argv, opt_cdup))
5850                         return status_load_error(view, stage, newpath);
5851                 if (status)
5852                         info = "Unstaged changes to %s";
5853                 else
5854                         info = "Unstaged changes";
5855                 break;
5856         }
5857         case LINE_STAT_UNTRACKED:
5858                 if (!newpath) {
5859                         report("No file to show");
5860                         return REQ_NONE;
5861                 }
5863                 if (!suffixcmp(status->new.name, -1, "/")) {
5864                         report("Cannot display a directory");
5865                         return REQ_NONE;
5866                 }
5868                 if (!prepare_update_file(stage, newpath))
5869                         return status_load_error(view, stage, newpath);
5870                 info = "Untracked file %s";
5871                 break;
5873         case LINE_STAT_HEAD:
5874                 return REQ_NONE;
5876         default:
5877                 die("line type %d not handled in switch", line->type);
5878         }
5880         split = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
5881         open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split);
5882         if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
5883                 if (status) {
5884                         stage_status = *status;
5885                 } else {
5886                         memset(&stage_status, 0, sizeof(stage_status));
5887                 }
5889                 stage_line_type = line->type;
5890                 stage_chunks = 0;
5891                 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
5892         }
5894         return REQ_NONE;
5897 static bool
5898 status_exists(struct status *status, enum line_type type)
5900         struct view *view = VIEW(REQ_VIEW_STATUS);
5901         unsigned long lineno;
5903         for (lineno = 0; lineno < view->lines; lineno++) {
5904                 struct line *line = &view->line[lineno];
5905                 struct status *pos = line->data;
5907                 if (line->type != type)
5908                         continue;
5909                 if (!pos && (!status || !status->status) && line[1].data) {
5910                         select_view_line(view, lineno);
5911                         return TRUE;
5912                 }
5913                 if (pos && !strcmp(status->new.name, pos->new.name)) {
5914                         select_view_line(view, lineno);
5915                         return TRUE;
5916                 }
5917         }
5919         return FALSE;
5923 static bool
5924 status_update_prepare(struct io *io, enum line_type type)
5926         const char *staged_argv[] = {
5927                 "git", "update-index", "-z", "--index-info", NULL
5928         };
5929         const char *others_argv[] = {
5930                 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
5931         };
5933         switch (type) {
5934         case LINE_STAT_STAGED:
5935                 return io_run(io, staged_argv, opt_cdup, IO_WR);
5937         case LINE_STAT_UNSTAGED:
5938         case LINE_STAT_UNTRACKED:
5939                 return io_run(io, others_argv, opt_cdup, IO_WR);
5941         default:
5942                 die("line type %d not handled in switch", type);
5943                 return FALSE;
5944         }
5947 static bool
5948 status_update_write(struct io *io, struct status *status, enum line_type type)
5950         char buf[SIZEOF_STR];
5951         size_t bufsize = 0;
5953         switch (type) {
5954         case LINE_STAT_STAGED:
5955                 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
5956                                         status->old.mode,
5957                                         status->old.rev,
5958                                         status->old.name, 0))
5959                         return FALSE;
5960                 break;
5962         case LINE_STAT_UNSTAGED:
5963         case LINE_STAT_UNTRACKED:
5964                 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
5965                         return FALSE;
5966                 break;
5968         default:
5969                 die("line type %d not handled in switch", type);
5970         }
5972         return io_write(io, buf, bufsize);
5975 static bool
5976 status_update_file(struct status *status, enum line_type type)
5978         struct io io = {};
5979         bool result;
5981         if (!status_update_prepare(&io, type))
5982                 return FALSE;
5984         result = status_update_write(&io, status, type);
5985         return io_done(&io) && result;
5988 static bool
5989 status_update_files(struct view *view, struct line *line)
5991         char buf[sizeof(view->ref)];
5992         struct io io = {};
5993         bool result = TRUE;
5994         struct line *pos = view->line + view->lines;
5995         int files = 0;
5996         int file, done;
5997         int cursor_y = -1, cursor_x = -1;
5999         if (!status_update_prepare(&io, line->type))
6000                 return FALSE;
6002         for (pos = line; pos < view->line + view->lines && pos->data; pos++)
6003                 files++;
6005         string_copy(buf, view->ref);
6006         getsyx(cursor_y, cursor_x);
6007         for (file = 0, done = 5; result && file < files; line++, file++) {
6008                 int almost_done = file * 100 / files;
6010                 if (almost_done > done) {
6011                         done = almost_done;
6012                         string_format(view->ref, "updating file %u of %u (%d%% done)",
6013                                       file, files, done);
6014                         update_view_title(view);
6015                         setsyx(cursor_y, cursor_x);
6016                         doupdate();
6017                 }
6018                 result = status_update_write(&io, line->data, line->type);
6019         }
6020         string_copy(view->ref, buf);
6022         return io_done(&io) && result;
6025 static bool
6026 status_update(struct view *view)
6028         struct line *line = &view->line[view->lineno];
6030         assert(view->lines);
6032         if (!line->data) {
6033                 /* This should work even for the "On branch" line. */
6034                 if (line < view->line + view->lines && !line[1].data) {
6035                         report("Nothing to update");
6036                         return FALSE;
6037                 }
6039                 if (!status_update_files(view, line + 1)) {
6040                         report("Failed to update file status");
6041                         return FALSE;
6042                 }
6044         } else if (!status_update_file(line->data, line->type)) {
6045                 report("Failed to update file status");
6046                 return FALSE;
6047         }
6049         return TRUE;
6052 static bool
6053 status_revert(struct status *status, enum line_type type, bool has_none)
6055         if (!status || type != LINE_STAT_UNSTAGED) {
6056                 if (type == LINE_STAT_STAGED) {
6057                         report("Cannot revert changes to staged files");
6058                 } else if (type == LINE_STAT_UNTRACKED) {
6059                         report("Cannot revert changes to untracked files");
6060                 } else if (has_none) {
6061                         report("Nothing to revert");
6062                 } else {
6063                         report("Cannot revert changes to multiple files");
6064                 }
6066         } else if (prompt_yesno("Are you sure you want to revert changes?")) {
6067                 char mode[10] = "100644";
6068                 const char *reset_argv[] = {
6069                         "git", "update-index", "--cacheinfo", mode,
6070                                 status->old.rev, status->old.name, NULL
6071                 };
6072                 const char *checkout_argv[] = {
6073                         "git", "checkout", "--", status->old.name, NULL
6074                 };
6076                 if (status->status == 'U') {
6077                         string_format(mode, "%5o", status->old.mode);
6079                         if (status->old.mode == 0 && status->new.mode == 0) {
6080                                 reset_argv[2] = "--force-remove";
6081                                 reset_argv[3] = status->old.name;
6082                                 reset_argv[4] = NULL;
6083                         }
6085                         if (!io_run_fg(reset_argv, opt_cdup))
6086                                 return FALSE;
6087                         if (status->old.mode == 0 && status->new.mode == 0)
6088                                 return TRUE;
6089                 }
6091                 return io_run_fg(checkout_argv, opt_cdup);
6092         }
6094         return FALSE;
6097 static enum request
6098 status_request(struct view *view, enum request request, struct line *line)
6100         struct status *status = line->data;
6102         switch (request) {
6103         case REQ_STATUS_UPDATE:
6104                 if (!status_update(view))
6105                         return REQ_NONE;
6106                 break;
6108         case REQ_STATUS_REVERT:
6109                 if (!status_revert(status, line->type, status_has_none(view, line)))
6110                         return REQ_NONE;
6111                 break;
6113         case REQ_STATUS_MERGE:
6114                 if (!status || status->status != 'U') {
6115                         report("Merging only possible for files with unmerged status ('U').");
6116                         return REQ_NONE;
6117                 }
6118                 open_mergetool(status->new.name);
6119                 break;
6121         case REQ_EDIT:
6122                 if (!status)
6123                         return request;
6124                 if (status->status == 'D') {
6125                         report("File has been deleted.");
6126                         return REQ_NONE;
6127                 }
6129                 open_editor(status->new.name);
6130                 break;
6132         case REQ_VIEW_BLAME:
6133                 if (status)
6134                         opt_ref[0] = 0;
6135                 return request;
6137         case REQ_ENTER:
6138                 /* After returning the status view has been split to
6139                  * show the stage view. No further reloading is
6140                  * necessary. */
6141                 return status_enter(view, line);
6143         case REQ_REFRESH:
6144                 /* Simply reload the view. */
6145                 break;
6147         default:
6148                 return request;
6149         }
6151         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
6153         return REQ_NONE;
6156 static void
6157 status_select(struct view *view, struct line *line)
6159         struct status *status = line->data;
6160         char file[SIZEOF_STR] = "all files";
6161         const char *text;
6162         const char *key;
6164         if (status && !string_format(file, "'%s'", status->new.name))
6165                 return;
6167         if (!status && line[1].type == LINE_STAT_NONE)
6168                 line++;
6170         switch (line->type) {
6171         case LINE_STAT_STAGED:
6172                 text = "Press %s to unstage %s for commit";
6173                 break;
6175         case LINE_STAT_UNSTAGED:
6176                 text = "Press %s to stage %s for commit";
6177                 break;
6179         case LINE_STAT_UNTRACKED:
6180                 text = "Press %s to stage %s for addition";
6181                 break;
6183         case LINE_STAT_HEAD:
6184         case LINE_STAT_NONE:
6185                 text = "Nothing to update";
6186                 break;
6188         default:
6189                 die("line type %d not handled in switch", line->type);
6190         }
6192         if (status && status->status == 'U') {
6193                 text = "Press %s to resolve conflict in %s";
6194                 key = get_key(KEYMAP_STATUS, REQ_STATUS_MERGE);
6196         } else {
6197                 key = get_key(KEYMAP_STATUS, REQ_STATUS_UPDATE);
6198         }
6200         string_format(view->ref, text, key, file);
6201         if (status)
6202                 string_copy(opt_file, status->new.name);
6205 static bool
6206 status_grep(struct view *view, struct line *line)
6208         struct status *status = line->data;
6210         if (status) {
6211                 const char buf[2] = { status->status, 0 };
6212                 const char *text[] = { status->new.name, buf, NULL };
6214                 return grep_text(view, text);
6215         }
6217         return FALSE;
6220 static struct view_ops status_ops = {
6221         "file",
6222         NULL,
6223         status_open,
6224         NULL,
6225         status_draw,
6226         status_request,
6227         status_grep,
6228         status_select,
6229 };
6232 static bool
6233 stage_diff_write(struct io *io, struct line *line, struct line *end)
6235         while (line < end) {
6236                 if (!io_write(io, line->data, strlen(line->data)) ||
6237                     !io_write(io, "\n", 1))
6238                         return FALSE;
6239                 line++;
6240                 if (line->type == LINE_DIFF_CHUNK ||
6241                     line->type == LINE_DIFF_HEADER)
6242                         break;
6243         }
6245         return TRUE;
6248 static struct line *
6249 stage_diff_find(struct view *view, struct line *line, enum line_type type)
6251         for (; view->line < line; line--)
6252                 if (line->type == type)
6253                         return line;
6255         return NULL;
6258 static bool
6259 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
6261         const char *apply_argv[SIZEOF_ARG] = {
6262                 "git", "apply", "--whitespace=nowarn", NULL
6263         };
6264         struct line *diff_hdr;
6265         struct io io = {};
6266         int argc = 3;
6268         diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
6269         if (!diff_hdr)
6270                 return FALSE;
6272         if (!revert)
6273                 apply_argv[argc++] = "--cached";
6274         if (revert || stage_line_type == LINE_STAT_STAGED)
6275                 apply_argv[argc++] = "-R";
6276         apply_argv[argc++] = "-";
6277         apply_argv[argc++] = NULL;
6278         if (!io_run(&io, apply_argv, opt_cdup, IO_WR))
6279                 return FALSE;
6281         if (!stage_diff_write(&io, diff_hdr, chunk) ||
6282             !stage_diff_write(&io, chunk, view->line + view->lines))
6283                 chunk = NULL;
6285         io_done(&io);
6286         io_run_bg(update_index_argv);
6288         return chunk ? TRUE : FALSE;
6291 static bool
6292 stage_update(struct view *view, struct line *line)
6294         struct line *chunk = NULL;
6296         if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
6297                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6299         if (chunk) {
6300                 if (!stage_apply_chunk(view, chunk, FALSE)) {
6301                         report("Failed to apply chunk");
6302                         return FALSE;
6303                 }
6305         } else if (!stage_status.status) {
6306                 view = VIEW(REQ_VIEW_STATUS);
6308                 for (line = view->line; line < view->line + view->lines; line++)
6309                         if (line->type == stage_line_type)
6310                                 break;
6312                 if (!status_update_files(view, line + 1)) {
6313                         report("Failed to update files");
6314                         return FALSE;
6315                 }
6317         } else if (!status_update_file(&stage_status, stage_line_type)) {
6318                 report("Failed to update file");
6319                 return FALSE;
6320         }
6322         return TRUE;
6325 static bool
6326 stage_revert(struct view *view, struct line *line)
6328         struct line *chunk = NULL;
6330         if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
6331                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6333         if (chunk) {
6334                 if (!prompt_yesno("Are you sure you want to revert changes?"))
6335                         return FALSE;
6337                 if (!stage_apply_chunk(view, chunk, TRUE)) {
6338                         report("Failed to revert chunk");
6339                         return FALSE;
6340                 }
6341                 return TRUE;
6343         } else {
6344                 return status_revert(stage_status.status ? &stage_status : NULL,
6345                                      stage_line_type, FALSE);
6346         }
6350 static void
6351 stage_next(struct view *view, struct line *line)
6353         int i;
6355         if (!stage_chunks) {
6356                 for (line = view->line; line < view->line + view->lines; line++) {
6357                         if (line->type != LINE_DIFF_CHUNK)
6358                                 continue;
6360                         if (!realloc_ints(&stage_chunk, stage_chunks, 1)) {
6361                                 report("Allocation failure");
6362                                 return;
6363                         }
6365                         stage_chunk[stage_chunks++] = line - view->line;
6366                 }
6367         }
6369         for (i = 0; i < stage_chunks; i++) {
6370                 if (stage_chunk[i] > view->lineno) {
6371                         do_scroll_view(view, stage_chunk[i] - view->lineno);
6372                         report("Chunk %d of %d", i + 1, stage_chunks);
6373                         return;
6374                 }
6375         }
6377         report("No next chunk found");
6380 static enum request
6381 stage_request(struct view *view, enum request request, struct line *line)
6383         switch (request) {
6384         case REQ_STATUS_UPDATE:
6385                 if (!stage_update(view, line))
6386                         return REQ_NONE;
6387                 break;
6389         case REQ_STATUS_REVERT:
6390                 if (!stage_revert(view, line))
6391                         return REQ_NONE;
6392                 break;
6394         case REQ_STAGE_NEXT:
6395                 if (stage_line_type == LINE_STAT_UNTRACKED) {
6396                         report("File is untracked; press %s to add",
6397                                get_key(KEYMAP_STAGE, REQ_STATUS_UPDATE));
6398                         return REQ_NONE;
6399                 }
6400                 stage_next(view, line);
6401                 return REQ_NONE;
6403         case REQ_EDIT:
6404                 if (!stage_status.new.name[0])
6405                         return request;
6406                 if (stage_status.status == 'D') {
6407                         report("File has been deleted.");
6408                         return REQ_NONE;
6409                 }
6411                 open_editor(stage_status.new.name);
6412                 break;
6414         case REQ_REFRESH:
6415                 /* Reload everything ... */
6416                 break;
6418         case REQ_VIEW_BLAME:
6419                 if (stage_status.new.name[0]) {
6420                         string_copy(opt_file, stage_status.new.name);
6421                         opt_ref[0] = 0;
6422                 }
6423                 return request;
6425         case REQ_ENTER:
6426                 return pager_request(view, request, line);
6428         default:
6429                 return request;
6430         }
6432         VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
6433         open_view(view, REQ_VIEW_STATUS, OPEN_REFRESH);
6435         /* Check whether the staged entry still exists, and close the
6436          * stage view if it doesn't. */
6437         if (!status_exists(&stage_status, stage_line_type)) {
6438                 status_restore(VIEW(REQ_VIEW_STATUS));
6439                 return REQ_VIEW_CLOSE;
6440         }
6442         if (stage_line_type == LINE_STAT_UNTRACKED) {
6443                 if (!suffixcmp(stage_status.new.name, -1, "/")) {
6444                         report("Cannot display a directory");
6445                         return REQ_NONE;
6446                 }
6448                 if (!prepare_update_file(view, stage_status.new.name)) {
6449                         report("Failed to open file: %s", strerror(errno));
6450                         return REQ_NONE;
6451                 }
6452         }
6453         open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
6455         return REQ_NONE;
6458 static struct view_ops stage_ops = {
6459         "line",
6460         NULL,
6461         NULL,
6462         pager_read,
6463         pager_draw,
6464         stage_request,
6465         pager_grep,
6466         pager_select,
6467 };
6470 /*
6471  * Revision graph
6472  */
6474 struct commit {
6475         char id[SIZEOF_REV];            /* SHA1 ID. */
6476         char title[128];                /* First line of the commit message. */
6477         const char *author;             /* Author of the commit. */
6478         struct time time;               /* Date from the author ident. */
6479         struct ref_list *refs;          /* Repository references. */
6480         chtype graph[SIZEOF_REVGRAPH];  /* Ancestry chain graphics. */
6481         size_t graph_size;              /* The width of the graph array. */
6482         bool has_parents;               /* Rewritten --parents seen. */
6483 };
6485 /* Size of rev graph with no  "padding" columns */
6486 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
6488 struct rev_graph {
6489         struct rev_graph *prev, *next, *parents;
6490         char rev[SIZEOF_REVITEMS][SIZEOF_REV];
6491         size_t size;
6492         struct commit *commit;
6493         size_t pos;
6494         unsigned int boundary:1;
6495 };
6497 /* Parents of the commit being visualized. */
6498 static struct rev_graph graph_parents[4];
6500 /* The current stack of revisions on the graph. */
6501 static struct rev_graph graph_stacks[4] = {
6502         { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
6503         { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
6504         { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
6505         { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
6506 };
6508 static inline bool
6509 graph_parent_is_merge(struct rev_graph *graph)
6511         return graph->parents->size > 1;
6514 static inline void
6515 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
6517         struct commit *commit = graph->commit;
6519         if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
6520                 commit->graph[commit->graph_size++] = symbol;
6523 static void
6524 clear_rev_graph(struct rev_graph *graph)
6526         graph->boundary = 0;
6527         graph->size = graph->pos = 0;
6528         graph->commit = NULL;
6529         memset(graph->parents, 0, sizeof(*graph->parents));
6532 static void
6533 done_rev_graph(struct rev_graph *graph)
6535         if (graph_parent_is_merge(graph) &&
6536             graph->pos < graph->size - 1 &&
6537             graph->next->size == graph->size + graph->parents->size - 1) {
6538                 size_t i = graph->pos + graph->parents->size - 1;
6540                 graph->commit->graph_size = i * 2;
6541                 while (i < graph->next->size - 1) {
6542                         append_to_rev_graph(graph, ' ');
6543                         append_to_rev_graph(graph, '\\');
6544                         i++;
6545                 }
6546         }
6548         clear_rev_graph(graph);
6551 static void
6552 push_rev_graph(struct rev_graph *graph, const char *parent)
6554         int i;
6556         /* "Collapse" duplicate parents lines.
6557          *
6558          * FIXME: This needs to also update update the drawn graph but
6559          * for now it just serves as a method for pruning graph lines. */
6560         for (i = 0; i < graph->size; i++)
6561                 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
6562                         return;
6564         if (graph->size < SIZEOF_REVITEMS) {
6565                 string_copy_rev(graph->rev[graph->size++], parent);
6566         }
6569 static chtype
6570 get_rev_graph_symbol(struct rev_graph *graph)
6572         chtype symbol;
6574         if (graph->boundary)
6575                 symbol = REVGRAPH_BOUND;
6576         else if (graph->parents->size == 0)
6577                 symbol = REVGRAPH_INIT;
6578         else if (graph_parent_is_merge(graph))
6579                 symbol = REVGRAPH_MERGE;
6580         else if (graph->pos >= graph->size)
6581                 symbol = REVGRAPH_BRANCH;
6582         else
6583                 symbol = REVGRAPH_COMMIT;
6585         return symbol;
6588 static void
6589 draw_rev_graph(struct rev_graph *graph)
6591         struct rev_filler {
6592                 chtype separator, line;
6593         };
6594         enum { DEFAULT, RSHARP, RDIAG, LDIAG };
6595         static struct rev_filler fillers[] = {
6596                 { ' ',  '|' },
6597                 { '`',  '.' },
6598                 { '\'', ' ' },
6599                 { '/',  ' ' },
6600         };
6601         chtype symbol = get_rev_graph_symbol(graph);
6602         struct rev_filler *filler;
6603         size_t i;
6605         fillers[DEFAULT].line = opt_line_graphics ? ACS_VLINE : '|';
6606         filler = &fillers[DEFAULT];
6608         for (i = 0; i < graph->pos; i++) {
6609                 append_to_rev_graph(graph, filler->line);
6610                 if (graph_parent_is_merge(graph->prev) &&
6611                     graph->prev->pos == i)
6612                         filler = &fillers[RSHARP];
6614                 append_to_rev_graph(graph, filler->separator);
6615         }
6617         /* Place the symbol for this revision. */
6618         append_to_rev_graph(graph, symbol);
6620         if (graph->prev->size > graph->size)
6621                 filler = &fillers[RDIAG];
6622         else
6623                 filler = &fillers[DEFAULT];
6625         i++;
6627         for (; i < graph->size; i++) {
6628                 append_to_rev_graph(graph, filler->separator);
6629                 append_to_rev_graph(graph, filler->line);
6630                 if (graph_parent_is_merge(graph->prev) &&
6631                     i < graph->prev->pos + graph->parents->size)
6632                         filler = &fillers[RSHARP];
6633                 if (graph->prev->size > graph->size)
6634                         filler = &fillers[LDIAG];
6635         }
6637         if (graph->prev->size > graph->size) {
6638                 append_to_rev_graph(graph, filler->separator);
6639                 if (filler->line != ' ')
6640                         append_to_rev_graph(graph, filler->line);
6641         }
6644 /* Prepare the next rev graph */
6645 static void
6646 prepare_rev_graph(struct rev_graph *graph)
6648         size_t i;
6650         /* First, traverse all lines of revisions up to the active one. */
6651         for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
6652                 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
6653                         break;
6655                 push_rev_graph(graph->next, graph->rev[graph->pos]);
6656         }
6658         /* Interleave the new revision parent(s). */
6659         for (i = 0; !graph->boundary && i < graph->parents->size; i++)
6660                 push_rev_graph(graph->next, graph->parents->rev[i]);
6662         /* Lastly, put any remaining revisions. */
6663         for (i = graph->pos + 1; i < graph->size; i++)
6664                 push_rev_graph(graph->next, graph->rev[i]);
6667 static void
6668 update_rev_graph(struct view *view, struct rev_graph *graph)
6670         /* If this is the finalizing update ... */
6671         if (graph->commit)
6672                 prepare_rev_graph(graph);
6674         /* Graph visualization needs a one rev look-ahead,
6675          * so the first update doesn't visualize anything. */
6676         if (!graph->prev->commit)
6677                 return;
6679         if (view->lines > 2)
6680                 view->line[view->lines - 3].dirty = 1;
6681         if (view->lines > 1)
6682                 view->line[view->lines - 2].dirty = 1;
6683         draw_rev_graph(graph->prev);
6684         done_rev_graph(graph->prev->prev);
6688 /*
6689  * Main view backend
6690  */
6692 static const char *main_argv[SIZEOF_ARG] = {
6693         "git", "log", "--no-color", "--pretty=raw", "--parents",
6694                       "--topo-order", "%(head)", NULL
6695 };
6697 static bool
6698 main_draw(struct view *view, struct line *line, unsigned int lineno)
6700         struct commit *commit = line->data;
6702         if (!commit->author)
6703                 return FALSE;
6705         if (opt_date && draw_date(view, &commit->time))
6706                 return TRUE;
6708         if (opt_author && draw_author(view, commit->author))
6709                 return TRUE;
6711         if (opt_rev_graph && commit->graph_size &&
6712             draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
6713                 return TRUE;
6715         if (opt_show_refs && commit->refs) {
6716                 size_t i;
6718                 for (i = 0; i < commit->refs->size; i++) {
6719                         struct ref *ref = commit->refs->refs[i];
6720                         enum line_type type;
6722                         if (ref->head)
6723                                 type = LINE_MAIN_HEAD;
6724                         else if (ref->ltag)
6725                                 type = LINE_MAIN_LOCAL_TAG;
6726                         else if (ref->tag)
6727                                 type = LINE_MAIN_TAG;
6728                         else if (ref->tracked)
6729                                 type = LINE_MAIN_TRACKED;
6730                         else if (ref->remote)
6731                                 type = LINE_MAIN_REMOTE;
6732                         else
6733                                 type = LINE_MAIN_REF;
6735                         if (draw_text(view, type, "[", TRUE) ||
6736                             draw_text(view, type, ref->name, TRUE) ||
6737                             draw_text(view, type, "]", TRUE))
6738                                 return TRUE;
6740                         if (draw_text(view, LINE_DEFAULT, " ", TRUE))
6741                                 return TRUE;
6742                 }
6743         }
6745         draw_text(view, LINE_DEFAULT, commit->title, TRUE);
6746         return TRUE;
6749 /* Reads git log --pretty=raw output and parses it into the commit struct. */
6750 static bool
6751 main_read(struct view *view, char *line)
6753         static struct rev_graph *graph = graph_stacks;
6754         enum line_type type;
6755         struct commit *commit;
6757         if (!line) {
6758                 int i;
6760                 if (!view->lines && !view->prev)
6761                         die("No revisions match the given arguments.");
6762                 if (view->lines > 0) {
6763                         commit = view->line[view->lines - 1].data;
6764                         view->line[view->lines - 1].dirty = 1;
6765                         if (!commit->author) {
6766                                 view->lines--;
6767                                 free(commit);
6768                                 graph->commit = NULL;
6769                         }
6770                 }
6771                 update_rev_graph(view, graph);
6773                 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
6774                         clear_rev_graph(&graph_stacks[i]);
6775                 return TRUE;
6776         }
6778         type = get_line_type(line);
6779         if (type == LINE_COMMIT) {
6780                 commit = calloc(1, sizeof(struct commit));
6781                 if (!commit)
6782                         return FALSE;
6784                 line += STRING_SIZE("commit ");
6785                 if (*line == '-') {
6786                         graph->boundary = 1;
6787                         line++;
6788                 }
6790                 string_copy_rev(commit->id, line);
6791                 commit->refs = get_ref_list(commit->id);
6792                 graph->commit = commit;
6793                 add_line_data(view, commit, LINE_MAIN_COMMIT);
6795                 while ((line = strchr(line, ' '))) {
6796                         line++;
6797                         push_rev_graph(graph->parents, line);
6798                         commit->has_parents = TRUE;
6799                 }
6800                 return TRUE;
6801         }
6803         if (!view->lines)
6804                 return TRUE;
6805         commit = view->line[view->lines - 1].data;
6807         switch (type) {
6808         case LINE_PARENT:
6809                 if (commit->has_parents)
6810                         break;
6811                 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
6812                 break;
6814         case LINE_AUTHOR:
6815                 parse_author_line(line + STRING_SIZE("author "),
6816                                   &commit->author, &commit->time);
6817                 update_rev_graph(view, graph);
6818                 graph = graph->next;
6819                 break;
6821         default:
6822                 /* Fill in the commit title if it has not already been set. */
6823                 if (commit->title[0])
6824                         break;
6826                 /* Require titles to start with a non-space character at the
6827                  * offset used by git log. */
6828                 if (strncmp(line, "    ", 4))
6829                         break;
6830                 line += 4;
6831                 /* Well, if the title starts with a whitespace character,
6832                  * try to be forgiving.  Otherwise we end up with no title. */
6833                 while (isspace(*line))
6834                         line++;
6835                 if (*line == '\0')
6836                         break;
6837                 /* FIXME: More graceful handling of titles; append "..." to
6838                  * shortened titles, etc. */
6840                 string_expand(commit->title, sizeof(commit->title), line, 1);
6841                 view->line[view->lines - 1].dirty = 1;
6842         }
6844         return TRUE;
6847 static enum request
6848 main_request(struct view *view, enum request request, struct line *line)
6850         enum open_flags flags = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
6852         switch (request) {
6853         case REQ_ENTER:
6854                 open_view(view, REQ_VIEW_DIFF, flags);
6855                 break;
6856         case REQ_REFRESH:
6857                 load_refs();
6858                 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
6859                 break;
6860         default:
6861                 return request;
6862         }
6864         return REQ_NONE;
6867 static bool
6868 grep_refs(struct ref_list *list, regex_t *regex)
6870         regmatch_t pmatch;
6871         size_t i;
6873         if (!opt_show_refs || !list)
6874                 return FALSE;
6876         for (i = 0; i < list->size; i++) {
6877                 if (regexec(regex, list->refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
6878                         return TRUE;
6879         }
6881         return FALSE;
6884 static bool
6885 main_grep(struct view *view, struct line *line)
6887         struct commit *commit = line->data;
6888         const char *text[] = {
6889                 commit->title,
6890                 opt_author ? commit->author : "",
6891                 mkdate(&commit->time, opt_date),
6892                 NULL
6893         };
6895         return grep_text(view, text) || grep_refs(commit->refs, view->regex);
6898 static void
6899 main_select(struct view *view, struct line *line)
6901         struct commit *commit = line->data;
6903         string_copy_rev(view->ref, commit->id);
6904         string_copy_rev(ref_commit, view->ref);
6907 static struct view_ops main_ops = {
6908         "commit",
6909         main_argv,
6910         NULL,
6911         main_read,
6912         main_draw,
6913         main_request,
6914         main_grep,
6915         main_select,
6916 };
6919 /*
6920  * Status management
6921  */
6923 /* Whether or not the curses interface has been initialized. */
6924 static bool cursed = FALSE;
6926 /* Terminal hacks and workarounds. */
6927 static bool use_scroll_redrawwin;
6928 static bool use_scroll_status_wclear;
6930 /* The status window is used for polling keystrokes. */
6931 static WINDOW *status_win;
6933 /* Reading from the prompt? */
6934 static bool input_mode = FALSE;
6936 static bool status_empty = FALSE;
6938 /* Update status and title window. */
6939 static void
6940 report(const char *msg, ...)
6942         struct view *view = display[current_view];
6944         if (input_mode)
6945                 return;
6947         if (!view) {
6948                 char buf[SIZEOF_STR];
6949                 va_list args;
6951                 va_start(args, msg);
6952                 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
6953                         buf[sizeof(buf) - 1] = 0;
6954                         buf[sizeof(buf) - 2] = '.';
6955                         buf[sizeof(buf) - 3] = '.';
6956                         buf[sizeof(buf) - 4] = '.';
6957                 }
6958                 va_end(args);
6959                 die("%s", buf);
6960         }
6962         if (!status_empty || *msg) {
6963                 va_list args;
6965                 va_start(args, msg);
6967                 wmove(status_win, 0, 0);
6968                 if (view->has_scrolled && use_scroll_status_wclear)
6969                         wclear(status_win);
6970                 if (*msg) {
6971                         vwprintw(status_win, msg, args);
6972                         status_empty = FALSE;
6973                 } else {
6974                         status_empty = TRUE;
6975                 }
6976                 wclrtoeol(status_win);
6977                 wnoutrefresh(status_win);
6979                 va_end(args);
6980         }
6982         update_view_title(view);
6985 static void
6986 init_display(void)
6988         const char *term;
6989         int x, y;
6991         /* Initialize the curses library */
6992         if (isatty(STDIN_FILENO)) {
6993                 cursed = !!initscr();
6994                 opt_tty = stdin;
6995         } else {
6996                 /* Leave stdin and stdout alone when acting as a pager. */
6997                 opt_tty = fopen("/dev/tty", "r+");
6998                 if (!opt_tty)
6999                         die("Failed to open /dev/tty");
7000                 cursed = !!newterm(NULL, opt_tty, opt_tty);
7001         }
7003         if (!cursed)
7004                 die("Failed to initialize curses");
7006         nonl();         /* Disable conversion and detect newlines from input. */
7007         cbreak();       /* Take input chars one at a time, no wait for \n */
7008         noecho();       /* Don't echo input */
7009         leaveok(stdscr, FALSE);
7011         if (has_colors())
7012                 init_colors();
7014         getmaxyx(stdscr, y, x);
7015         status_win = newwin(1, 0, y - 1, 0);
7016         if (!status_win)
7017                 die("Failed to create status window");
7019         /* Enable keyboard mapping */
7020         keypad(status_win, TRUE);
7021         wbkgdset(status_win, get_line_attr(LINE_STATUS));
7023         TABSIZE = opt_tab_size;
7025         term = getenv("XTERM_VERSION") ? NULL : getenv("COLORTERM");
7026         if (term && !strcmp(term, "gnome-terminal")) {
7027                 /* In the gnome-terminal-emulator, the message from
7028                  * scrolling up one line when impossible followed by
7029                  * scrolling down one line causes corruption of the
7030                  * status line. This is fixed by calling wclear. */
7031                 use_scroll_status_wclear = TRUE;
7032                 use_scroll_redrawwin = FALSE;
7034         } else if (term && !strcmp(term, "xrvt-xpm")) {
7035                 /* No problems with full optimizations in xrvt-(unicode)
7036                  * and aterm. */
7037                 use_scroll_status_wclear = use_scroll_redrawwin = FALSE;
7039         } else {
7040                 /* When scrolling in (u)xterm the last line in the
7041                  * scrolling direction will update slowly. */
7042                 use_scroll_redrawwin = TRUE;
7043                 use_scroll_status_wclear = FALSE;
7044         }
7047 static int
7048 get_input(int prompt_position)
7050         struct view *view;
7051         int i, key, cursor_y, cursor_x;
7052         bool loading = FALSE;
7054         if (prompt_position)
7055                 input_mode = TRUE;
7057         while (TRUE) {
7058                 foreach_view (view, i) {
7059                         update_view(view);
7060                         if (view_is_displayed(view) && view->has_scrolled &&
7061                             use_scroll_redrawwin)
7062                                 redrawwin(view->win);
7063                         view->has_scrolled = FALSE;
7064                         if (view->pipe)
7065                                 loading = TRUE;
7066                 }
7068                 /* Update the cursor position. */
7069                 if (prompt_position) {
7070                         getbegyx(status_win, cursor_y, cursor_x);
7071                         cursor_x = prompt_position;
7072                 } else {
7073                         view = display[current_view];
7074                         getbegyx(view->win, cursor_y, cursor_x);
7075                         cursor_x = view->width - 1;
7076                         cursor_y += view->lineno - view->offset;
7077                 }
7078                 setsyx(cursor_y, cursor_x);
7080                 /* Refresh, accept single keystroke of input */
7081                 doupdate();
7082                 nodelay(status_win, loading);
7083                 key = wgetch(status_win);
7085                 /* wgetch() with nodelay() enabled returns ERR when
7086                  * there's no input. */
7087                 if (key == ERR) {
7089                 } else if (key == KEY_RESIZE) {
7090                         int height, width;
7092                         getmaxyx(stdscr, height, width);
7094                         wresize(status_win, 1, width);
7095                         mvwin(status_win, height - 1, 0);
7096                         wnoutrefresh(status_win);
7097                         resize_display();
7098                         redraw_display(TRUE);
7100                 } else {
7101                         input_mode = FALSE;
7102                         return key;
7103                 }
7104         }
7107 static char *
7108 prompt_input(const char *prompt, input_handler handler, void *data)
7110         enum input_status status = INPUT_OK;
7111         static char buf[SIZEOF_STR];
7112         size_t pos = 0;
7114         buf[pos] = 0;
7116         while (status == INPUT_OK || status == INPUT_SKIP) {
7117                 int key;
7119                 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
7120                 wclrtoeol(status_win);
7122                 key = get_input(pos + 1);
7123                 switch (key) {
7124                 case KEY_RETURN:
7125                 case KEY_ENTER:
7126                 case '\n':
7127                         status = pos ? INPUT_STOP : INPUT_CANCEL;
7128                         break;
7130                 case KEY_BACKSPACE:
7131                         if (pos > 0)
7132                                 buf[--pos] = 0;
7133                         else
7134                                 status = INPUT_CANCEL;
7135                         break;
7137                 case KEY_ESC:
7138                         status = INPUT_CANCEL;
7139                         break;
7141                 default:
7142                         if (pos >= sizeof(buf)) {
7143                                 report("Input string too long");
7144                                 return NULL;
7145                         }
7147                         status = handler(data, buf, key);
7148                         if (status == INPUT_OK)
7149                                 buf[pos++] = (char) key;
7150                 }
7151         }
7153         /* Clear the status window */
7154         status_empty = FALSE;
7155         report("");
7157         if (status == INPUT_CANCEL)
7158                 return NULL;
7160         buf[pos++] = 0;
7162         return buf;
7165 static enum input_status
7166 prompt_yesno_handler(void *data, char *buf, int c)
7168         if (c == 'y' || c == 'Y')
7169                 return INPUT_STOP;
7170         if (c == 'n' || c == 'N')
7171                 return INPUT_CANCEL;
7172         return INPUT_SKIP;
7175 static bool
7176 prompt_yesno(const char *prompt)
7178         char prompt2[SIZEOF_STR];
7180         if (!string_format(prompt2, "%s [Yy/Nn]", prompt))
7181                 return FALSE;
7183         return !!prompt_input(prompt2, prompt_yesno_handler, NULL);
7186 static enum input_status
7187 read_prompt_handler(void *data, char *buf, int c)
7189         return isprint(c) ? INPUT_OK : INPUT_SKIP;
7192 static char *
7193 read_prompt(const char *prompt)
7195         return prompt_input(prompt, read_prompt_handler, NULL);
7198 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected)
7200         enum input_status status = INPUT_OK;
7201         int size = 0;
7203         while (items[size].text)
7204                 size++;
7206         while (status == INPUT_OK) {
7207                 const struct menu_item *item = &items[*selected];
7208                 int key;
7209                 int i;
7211                 mvwprintw(status_win, 0, 0, "%s (%d of %d) ",
7212                           prompt, *selected + 1, size);
7213                 if (item->hotkey)
7214                         wprintw(status_win, "[%c] ", (char) item->hotkey);
7215                 wprintw(status_win, "%s", item->text);
7216                 wclrtoeol(status_win);
7218                 key = get_input(COLS - 1);
7219                 switch (key) {
7220                 case KEY_RETURN:
7221                 case KEY_ENTER:
7222                 case '\n':
7223                         status = INPUT_STOP;
7224                         break;
7226                 case KEY_LEFT:
7227                 case KEY_UP:
7228                         *selected = *selected - 1;
7229                         if (*selected < 0)
7230                                 *selected = size - 1;
7231                         break;
7233                 case KEY_RIGHT:
7234                 case KEY_DOWN:
7235                         *selected = (*selected + 1) % size;
7236                         break;
7238                 case KEY_ESC:
7239                         status = INPUT_CANCEL;
7240                         break;
7242                 default:
7243                         for (i = 0; items[i].text; i++)
7244                                 if (items[i].hotkey == key) {
7245                                         *selected = i;
7246                                         status = INPUT_STOP;
7247                                         break;
7248                                 }
7249                 }
7250         }
7252         /* Clear the status window */
7253         status_empty = FALSE;
7254         report("");
7256         return status != INPUT_CANCEL;
7259 /*
7260  * Repository properties
7261  */
7263 static struct ref **refs = NULL;
7264 static size_t refs_size = 0;
7265 static struct ref *refs_head = NULL;
7267 static struct ref_list **ref_lists = NULL;
7268 static size_t ref_lists_size = 0;
7270 DEFINE_ALLOCATOR(realloc_refs, struct ref *, 256)
7271 DEFINE_ALLOCATOR(realloc_refs_list, struct ref *, 8)
7272 DEFINE_ALLOCATOR(realloc_ref_lists, struct ref_list *, 8)
7274 static int
7275 compare_refs(const void *ref1_, const void *ref2_)
7277         const struct ref *ref1 = *(const struct ref **)ref1_;
7278         const struct ref *ref2 = *(const struct ref **)ref2_;
7280         if (ref1->tag != ref2->tag)
7281                 return ref2->tag - ref1->tag;
7282         if (ref1->ltag != ref2->ltag)
7283                 return ref2->ltag - ref2->ltag;
7284         if (ref1->head != ref2->head)
7285                 return ref2->head - ref1->head;
7286         if (ref1->tracked != ref2->tracked)
7287                 return ref2->tracked - ref1->tracked;
7288         if (ref1->remote != ref2->remote)
7289                 return ref2->remote - ref1->remote;
7290         return strcmp(ref1->name, ref2->name);
7293 static void
7294 foreach_ref(bool (*visitor)(void *data, const struct ref *ref), void *data)
7296         size_t i;
7298         for (i = 0; i < refs_size; i++)
7299                 if (!visitor(data, refs[i]))
7300                         break;
7303 static struct ref *
7304 get_ref_head()
7306         return refs_head;
7309 static struct ref_list *
7310 get_ref_list(const char *id)
7312         struct ref_list *list;
7313         size_t i;
7315         for (i = 0; i < ref_lists_size; i++)
7316                 if (!strcmp(id, ref_lists[i]->id))
7317                         return ref_lists[i];
7319         if (!realloc_ref_lists(&ref_lists, ref_lists_size, 1))
7320                 return NULL;
7321         list = calloc(1, sizeof(*list));
7322         if (!list)
7323                 return NULL;
7325         for (i = 0; i < refs_size; i++) {
7326                 if (!strcmp(id, refs[i]->id) &&
7327                     realloc_refs_list(&list->refs, list->size, 1))
7328                         list->refs[list->size++] = refs[i];
7329         }
7331         if (!list->refs) {
7332                 free(list);
7333                 return NULL;
7334         }
7336         qsort(list->refs, list->size, sizeof(*list->refs), compare_refs);
7337         ref_lists[ref_lists_size++] = list;
7338         return list;
7341 static int
7342 read_ref(char *id, size_t idlen, char *name, size_t namelen)
7344         struct ref *ref = NULL;
7345         bool tag = FALSE;
7346         bool ltag = FALSE;
7347         bool remote = FALSE;
7348         bool tracked = FALSE;
7349         bool head = FALSE;
7350         int from = 0, to = refs_size - 1;
7352         if (!prefixcmp(name, "refs/tags/")) {
7353                 if (!suffixcmp(name, namelen, "^{}")) {
7354                         namelen -= 3;
7355                         name[namelen] = 0;
7356                 } else {
7357                         ltag = TRUE;
7358                 }
7360                 tag = TRUE;
7361                 namelen -= STRING_SIZE("refs/tags/");
7362                 name    += STRING_SIZE("refs/tags/");
7364         } else if (!prefixcmp(name, "refs/remotes/")) {
7365                 remote = TRUE;
7366                 namelen -= STRING_SIZE("refs/remotes/");
7367                 name    += STRING_SIZE("refs/remotes/");
7368                 tracked  = !strcmp(opt_remote, name);
7370         } else if (!prefixcmp(name, "refs/heads/")) {
7371                 namelen -= STRING_SIZE("refs/heads/");
7372                 name    += STRING_SIZE("refs/heads/");
7373                 if (!strncmp(opt_head, name, namelen))
7374                         return OK;
7376         } else if (!strcmp(name, "HEAD")) {
7377                 head     = TRUE;
7378                 if (*opt_head) {
7379                         namelen  = strlen(opt_head);
7380                         name     = opt_head;
7381                 }
7382         }
7384         /* If we are reloading or it's an annotated tag, replace the
7385          * previous SHA1 with the resolved commit id; relies on the fact
7386          * git-ls-remote lists the commit id of an annotated tag right
7387          * before the commit id it points to. */
7388         while (from <= to) {
7389                 size_t pos = (to + from) / 2;
7390                 int cmp = strcmp(name, refs[pos]->name);
7392                 if (!cmp) {
7393                         ref = refs[pos];
7394                         break;
7395                 }
7397                 if (cmp < 0)
7398                         to = pos - 1;
7399                 else
7400                         from = pos + 1;
7401         }
7403         if (!ref) {
7404                 if (!realloc_refs(&refs, refs_size, 1))
7405                         return ERR;
7406                 ref = calloc(1, sizeof(*ref) + namelen);
7407                 if (!ref)
7408                         return ERR;
7409                 memmove(refs + from + 1, refs + from,
7410                         (refs_size - from) * sizeof(*refs));
7411                 refs[from] = ref;
7412                 strncpy(ref->name, name, namelen);
7413                 refs_size++;
7414         }
7416         ref->head = head;
7417         ref->tag = tag;
7418         ref->ltag = ltag;
7419         ref->remote = remote;
7420         ref->tracked = tracked;
7421         string_copy_rev(ref->id, id);
7423         if (head)
7424                 refs_head = ref;
7425         return OK;
7428 static int
7429 load_refs(void)
7431         const char *head_argv[] = {
7432                 "git", "symbolic-ref", "HEAD", NULL
7433         };
7434         static const char *ls_remote_argv[SIZEOF_ARG] = {
7435                 "git", "ls-remote", opt_git_dir, NULL
7436         };
7437         static bool init = FALSE;
7438         size_t i;
7440         if (!init) {
7441                 if (!argv_from_env(ls_remote_argv, "TIG_LS_REMOTE"))
7442                         die("TIG_LS_REMOTE contains too many arguments");
7443                 init = TRUE;
7444         }
7446         if (!*opt_git_dir)
7447                 return OK;
7449         if (io_run_buf(head_argv, opt_head, sizeof(opt_head)) &&
7450             !prefixcmp(opt_head, "refs/heads/")) {
7451                 char *offset = opt_head + STRING_SIZE("refs/heads/");
7453                 memmove(opt_head, offset, strlen(offset) + 1);
7454         }
7456         refs_head = NULL;
7457         for (i = 0; i < refs_size; i++)
7458                 refs[i]->id[0] = 0;
7460         if (io_run_load(ls_remote_argv, "\t", read_ref) == ERR)
7461                 return ERR;
7463         /* Update the ref lists to reflect changes. */
7464         for (i = 0; i < ref_lists_size; i++) {
7465                 struct ref_list *list = ref_lists[i];
7466                 size_t old, new;
7468                 for (old = new = 0; old < list->size; old++)
7469                         if (!strcmp(list->id, list->refs[old]->id))
7470                                 list->refs[new++] = list->refs[old];
7471                 list->size = new;
7472         }
7474         return OK;
7477 static void
7478 set_remote_branch(const char *name, const char *value, size_t valuelen)
7480         if (!strcmp(name, ".remote")) {
7481                 string_ncopy(opt_remote, value, valuelen);
7483         } else if (*opt_remote && !strcmp(name, ".merge")) {
7484                 size_t from = strlen(opt_remote);
7486                 if (!prefixcmp(value, "refs/heads/"))
7487                         value += STRING_SIZE("refs/heads/");
7489                 if (!string_format_from(opt_remote, &from, "/%s", value))
7490                         opt_remote[0] = 0;
7491         }
7494 static void
7495 set_repo_config_option(char *name, char *value, int (*cmd)(int, const char **))
7497         const char *argv[SIZEOF_ARG] = { name, "=" };
7498         int argc = 1 + (cmd == option_set_command);
7499         int error = ERR;
7501         if (!argv_from_string(argv, &argc, value))
7502                 config_msg = "Too many option arguments";
7503         else
7504                 error = cmd(argc, argv);
7506         if (error == ERR)
7507                 warn("Option 'tig.%s': %s", name, config_msg);
7510 static bool
7511 set_environment_variable(const char *name, const char *value)
7513         size_t len = strlen(name) + 1 + strlen(value) + 1;
7514         char *env = malloc(len);
7516         if (env &&
7517             string_nformat(env, len, NULL, "%s=%s", name, value) &&
7518             putenv(env) == 0)
7519                 return TRUE;
7520         free(env);
7521         return FALSE;
7524 static void
7525 set_work_tree(const char *value)
7527         char cwd[SIZEOF_STR];
7529         if (!getcwd(cwd, sizeof(cwd)))
7530                 die("Failed to get cwd path: %s", strerror(errno));
7531         if (chdir(opt_git_dir) < 0)
7532                 die("Failed to chdir(%s): %s", strerror(errno));
7533         if (!getcwd(opt_git_dir, sizeof(opt_git_dir)))
7534                 die("Failed to get git path: %s", strerror(errno));
7535         if (chdir(cwd) < 0)
7536                 die("Failed to chdir(%s): %s", cwd, strerror(errno));
7537         if (chdir(value) < 0)
7538                 die("Failed to chdir(%s): %s", value, strerror(errno));
7539         if (!getcwd(cwd, sizeof(cwd)))
7540                 die("Failed to get cwd path: %s", strerror(errno));
7541         if (!set_environment_variable("GIT_WORK_TREE", cwd))
7542                 die("Failed to set GIT_WORK_TREE to '%s'", cwd);
7543         if (!set_environment_variable("GIT_DIR", opt_git_dir))
7544                 die("Failed to set GIT_DIR to '%s'", opt_git_dir);
7545         opt_is_inside_work_tree = TRUE;
7548 static int
7549 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
7551         if (!strcmp(name, "i18n.commitencoding"))
7552                 string_ncopy(opt_encoding, value, valuelen);
7554         else if (!strcmp(name, "core.editor"))
7555                 string_ncopy(opt_editor, value, valuelen);
7557         else if (!strcmp(name, "core.worktree"))
7558                 set_work_tree(value);
7560         else if (!prefixcmp(name, "tig.color."))
7561                 set_repo_config_option(name + 10, value, option_color_command);
7563         else if (!prefixcmp(name, "tig.bind."))
7564                 set_repo_config_option(name + 9, value, option_bind_command);
7566         else if (!prefixcmp(name, "tig."))
7567                 set_repo_config_option(name + 4, value, option_set_command);
7569         else if (*opt_head && !prefixcmp(name, "branch.") &&
7570                  !strncmp(name + 7, opt_head, strlen(opt_head)))
7571                 set_remote_branch(name + 7 + strlen(opt_head), value, valuelen);
7573         return OK;
7576 static int
7577 load_git_config(void)
7579         const char *config_list_argv[] = { "git", "config", "--list", NULL };
7581         return io_run_load(config_list_argv, "=", read_repo_config_option);
7584 static int
7585 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
7587         if (!opt_git_dir[0]) {
7588                 string_ncopy(opt_git_dir, name, namelen);
7590         } else if (opt_is_inside_work_tree == -1) {
7591                 /* This can be 3 different values depending on the
7592                  * version of git being used. If git-rev-parse does not
7593                  * understand --is-inside-work-tree it will simply echo
7594                  * the option else either "true" or "false" is printed.
7595                  * Default to true for the unknown case. */
7596                 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
7598         } else if (*name == '.') {
7599                 string_ncopy(opt_cdup, name, namelen);
7601         } else {
7602                 string_ncopy(opt_prefix, name, namelen);
7603         }
7605         return OK;
7608 static int
7609 load_repo_info(void)
7611         const char *rev_parse_argv[] = {
7612                 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
7613                         "--show-cdup", "--show-prefix", NULL
7614         };
7616         return io_run_load(rev_parse_argv, "=", read_repo_info);
7620 /*
7621  * Main
7622  */
7624 static const char usage[] =
7625 "tig " TIG_VERSION " (" __DATE__ ")\n"
7626 "\n"
7627 "Usage: tig        [options] [revs] [--] [paths]\n"
7628 "   or: tig show   [options] [revs] [--] [paths]\n"
7629 "   or: tig blame  [rev] path\n"
7630 "   or: tig status\n"
7631 "   or: tig <      [git command output]\n"
7632 "\n"
7633 "Options:\n"
7634 "  -v, --version   Show version and exit\n"
7635 "  -h, --help      Show help message and exit";
7637 static void __NORETURN
7638 quit(int sig)
7640         /* XXX: Restore tty modes and let the OS cleanup the rest! */
7641         if (cursed)
7642                 endwin();
7643         exit(0);
7646 static void __NORETURN
7647 die(const char *err, ...)
7649         va_list args;
7651         endwin();
7653         va_start(args, err);
7654         fputs("tig: ", stderr);
7655         vfprintf(stderr, err, args);
7656         fputs("\n", stderr);
7657         va_end(args);
7659         exit(1);
7662 static void
7663 warn(const char *msg, ...)
7665         va_list args;
7667         va_start(args, msg);
7668         fputs("tig warning: ", stderr);
7669         vfprintf(stderr, msg, args);
7670         fputs("\n", stderr);
7671         va_end(args);
7674 static enum request
7675 parse_options(int argc, const char *argv[])
7677         enum request request = REQ_VIEW_MAIN;
7678         const char *subcommand;
7679         bool seen_dashdash = FALSE;
7680         /* XXX: This is vulnerable to the user overriding options
7681          * required for the main view parser. */
7682         const char *custom_argv[SIZEOF_ARG] = {
7683                 "git", "log", "--no-color", "--pretty=raw", "--parents",
7684                         "--topo-order", NULL
7685         };
7686         int i, j = 6;
7688         if (!isatty(STDIN_FILENO)) {
7689                 io_open(&VIEW(REQ_VIEW_PAGER)->io, "");
7690                 return REQ_VIEW_PAGER;
7691         }
7693         if (argc <= 1)
7694                 return REQ_NONE;
7696         subcommand = argv[1];
7697         if (!strcmp(subcommand, "status")) {
7698                 if (argc > 2)
7699                         warn("ignoring arguments after `%s'", subcommand);
7700                 return REQ_VIEW_STATUS;
7702         } else if (!strcmp(subcommand, "blame")) {
7703                 if (argc <= 2 || argc > 4)
7704                         die("invalid number of options to blame\n\n%s", usage);
7706                 i = 2;
7707                 if (argc == 4) {
7708                         string_ncopy(opt_ref, argv[i], strlen(argv[i]));
7709                         i++;
7710                 }
7712                 string_ncopy(opt_file, argv[i], strlen(argv[i]));
7713                 return REQ_VIEW_BLAME;
7715         } else if (!strcmp(subcommand, "show")) {
7716                 request = REQ_VIEW_DIFF;
7718         } else {
7719                 subcommand = NULL;
7720         }
7722         if (subcommand) {
7723                 custom_argv[1] = subcommand;
7724                 j = 2;
7725         }
7727         for (i = 1 + !!subcommand; i < argc; i++) {
7728                 const char *opt = argv[i];
7730                 if (seen_dashdash || !strcmp(opt, "--")) {
7731                         seen_dashdash = TRUE;
7733                 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
7734                         printf("tig version %s\n", TIG_VERSION);
7735                         quit(0);
7737                 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
7738                         printf("%s\n", usage);
7739                         quit(0);
7740                 }
7742                 custom_argv[j++] = opt;
7743                 if (j >= ARRAY_SIZE(custom_argv))
7744                         die("command too long");
7745         }
7747         if (!prepare_update(VIEW(request), custom_argv, NULL))
7748                 die("Failed to format arguments");
7750         return request;
7753 int
7754 main(int argc, const char *argv[])
7756         const char *codeset = "UTF-8";
7757         enum request request = parse_options(argc, argv);
7758         struct view *view;
7759         size_t i;
7761         signal(SIGINT, quit);
7762         signal(SIGPIPE, SIG_IGN);
7764         if (setlocale(LC_ALL, "")) {
7765                 codeset = nl_langinfo(CODESET);
7766         }
7768         if (load_repo_info() == ERR)
7769                 die("Failed to load repo info.");
7771         if (load_options() == ERR)
7772                 die("Failed to load user config.");
7774         if (load_git_config() == ERR)
7775                 die("Failed to load repo config.");
7777         /* Require a git repository unless when running in pager mode. */
7778         if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
7779                 die("Not a git repository");
7781         if (*opt_encoding && strcmp(codeset, "UTF-8")) {
7782                 opt_iconv_in = iconv_open("UTF-8", opt_encoding);
7783                 if (opt_iconv_in == ICONV_NONE)
7784                         die("Failed to initialize character set conversion");
7785         }
7787         if (codeset && strcmp(codeset, "UTF-8")) {
7788                 opt_iconv_out = iconv_open(codeset, "UTF-8");
7789                 if (opt_iconv_out == ICONV_NONE)
7790                         die("Failed to initialize character set conversion");
7791         }
7793         if (load_refs() == ERR)
7794                 die("Failed to load refs.");
7796         foreach_view (view, i)
7797                 if (!argv_from_env(view->ops->argv, view->cmd_env))
7798                         die("Too many arguments in the `%s` environment variable",
7799                             view->cmd_env);
7801         init_display();
7803         if (request != REQ_NONE)
7804                 open_view(NULL, request, OPEN_PREPARED);
7805         request = request == REQ_NONE ? REQ_VIEW_MAIN : REQ_NONE;
7807         while (view_driver(display[current_view], request)) {
7808                 int key = get_input(0);
7810                 view = display[current_view];
7811                 request = get_keybinding(view->keymap, key);
7813                 /* Some low-level request handling. This keeps access to
7814                  * status_win restricted. */
7815                 switch (request) {
7816                 case REQ_NONE:
7817                         report("Unknown key, press %s for help",
7818                                get_key(view->keymap, REQ_VIEW_HELP));
7819                         break;
7820                 case REQ_PROMPT:
7821                 {
7822                         char *cmd = read_prompt(":");
7824                         if (cmd && isdigit(*cmd)) {
7825                                 int lineno = view->lineno + 1;
7827                                 if (parse_int(&lineno, cmd, 1, view->lines + 1) == OK) {
7828                                         select_view_line(view, lineno - 1);
7829                                         report("");
7830                                 } else {
7831                                         report("Unable to parse '%s' as a line number", cmd);
7832                                 }
7834                         } else if (cmd) {
7835                                 struct view *next = VIEW(REQ_VIEW_PAGER);
7836                                 const char *argv[SIZEOF_ARG] = { "git" };
7837                                 int argc = 1;
7839                                 /* When running random commands, initially show the
7840                                  * command in the title. However, it maybe later be
7841                                  * overwritten if a commit line is selected. */
7842                                 string_ncopy(next->ref, cmd, strlen(cmd));
7844                                 if (!argv_from_string(argv, &argc, cmd)) {
7845                                         report("Too many arguments");
7846                                 } else if (!prepare_update(next, argv, NULL)) {
7847                                         report("Failed to format command");
7848                                 } else {
7849                                         open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
7850                                 }
7851                         }
7853                         request = REQ_NONE;
7854                         break;
7855                 }
7856                 case REQ_SEARCH:
7857                 case REQ_SEARCH_BACK:
7858                 {
7859                         const char *prompt = request == REQ_SEARCH ? "/" : "?";
7860                         char *search = read_prompt(prompt);
7862                         if (search)
7863                                 string_ncopy(opt_search, search, strlen(search));
7864                         else if (*opt_search)
7865                                 request = request == REQ_SEARCH ?
7866                                         REQ_FIND_NEXT :
7867                                         REQ_FIND_PREV;
7868                         else
7869                                 request = REQ_NONE;
7870                         break;
7871                 }
7872                 default:
7873                         break;
7874                 }
7875         }
7877         quit(0);
7879         return 0;