Code

Further cleanup IO startup and initialization; fix io_run_append
[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 static void
721 io_init(struct io *io)
723         memset(io, 0, sizeof(*io));
724         io->pipe = -1;
727 static bool
728 io_open(struct io *io, const char *fmt, ...)
730         char name[SIZEOF_STR] = "";
731         bool fits;
732         va_list args;
734         io_init(io);
736         va_start(args, fmt);
737         fits = vsnprintf(name, sizeof(name), fmt, args) < sizeof(name);
738         va_end(args);
740         if (!fits) {
741                 io->error = ENAMETOOLONG;
742                 return FALSE;
743         }
744         io->pipe = *name ? open(name, O_RDONLY) : STDIN_FILENO;
745         if (io->pipe == -1)
746                 io->error = errno;
747         return io->pipe != -1;
750 static bool
751 io_kill(struct io *io)
753         return io->pid == 0 || kill(io->pid, SIGKILL) != -1;
756 static bool
757 io_done(struct io *io)
759         pid_t pid = io->pid;
761         if (io->pipe != -1)
762                 close(io->pipe);
763         free(io->buf);
764         io_init(io);
766         while (pid > 0) {
767                 int status;
768                 pid_t waiting = waitpid(pid, &status, 0);
770                 if (waiting < 0) {
771                         if (errno == EINTR)
772                                 continue;
773                         io->error = errno;
774                         return FALSE;
775                 }
777                 return waiting == pid &&
778                        !WIFSIGNALED(status) &&
779                        WIFEXITED(status) &&
780                        !WEXITSTATUS(status);
781         }
783         return TRUE;
786 static bool
787 io_run(struct io *io, enum io_type type, const char *dir, const char *argv[], ...)
789         int pipefds[2] = { -1, -1 };
790         va_list args;
792         io_init(io);
794         if ((type == IO_RD || type == IO_WR) && pipe(pipefds) < 0) {
795                 io->error = errno;
796                 return FALSE;
797         } else if (type == IO_AP) {
798                 va_start(args, argv);
799                 pipefds[1] = va_arg(args, int);
800                 va_end(args);
801         }
803         if ((io->pid = fork())) {
804                 if (io->pid == -1)
805                         io->error = errno;
806                 if (pipefds[!(type == IO_WR)] != -1)
807                         close(pipefds[!(type == IO_WR)]);
808                 if (io->pid != -1) {
809                         io->pipe = pipefds[!!(type == IO_WR)];
810                         return TRUE;
811                 }
813         } else {
814                 if (type != IO_FG) {
815                         int devnull = open("/dev/null", O_RDWR);
816                         int readfd  = type == IO_WR ? pipefds[0] : devnull;
817                         int writefd = (type == IO_RD || type == IO_AP)
818                                                         ? pipefds[1] : devnull;
820                         dup2(readfd,  STDIN_FILENO);
821                         dup2(writefd, STDOUT_FILENO);
822                         dup2(devnull, STDERR_FILENO);
824                         close(devnull);
825                         if (pipefds[0] != -1)
826                                 close(pipefds[0]);
827                         if (pipefds[1] != -1)
828                                 close(pipefds[1]);
829                 }
831                 if (dir && *dir && chdir(dir) == -1)
832                         exit(errno);
834                 execvp(argv[0], (char *const*) argv);
835                 exit(errno);
836         }
838         if (pipefds[!!(type == IO_WR)] != -1)
839                 close(pipefds[!!(type == IO_WR)]);
840         return FALSE;
843 static bool
844 io_complete(enum io_type type, const char **argv, const char *dir, int fd)
846         struct io io;
848         return io_run(&io, type, dir, argv, fd) && io_done(&io);
851 static bool
852 io_run_bg(const char **argv)
854         return io_complete(IO_BG, argv, NULL, -1);
857 static bool
858 io_run_fg(const char **argv, const char *dir)
860         return io_complete(IO_FG, argv, dir, -1);
863 static bool
864 io_run_append(const char **argv, int fd)
866         return io_complete(IO_AP, argv, NULL, fd);
869 static bool
870 io_eof(struct io *io)
872         return io->eof;
875 static int
876 io_error(struct io *io)
878         return io->error;
881 static char *
882 io_strerror(struct io *io)
884         return strerror(io->error);
887 static bool
888 io_can_read(struct io *io)
890         struct timeval tv = { 0, 500 };
891         fd_set fds;
893         FD_ZERO(&fds);
894         FD_SET(io->pipe, &fds);
896         return select(io->pipe + 1, &fds, NULL, NULL, &tv) > 0;
899 static ssize_t
900 io_read(struct io *io, void *buf, size_t bufsize)
902         do {
903                 ssize_t readsize = read(io->pipe, buf, bufsize);
905                 if (readsize < 0 && (errno == EAGAIN || errno == EINTR))
906                         continue;
907                 else if (readsize == -1)
908                         io->error = errno;
909                 else if (readsize == 0)
910                         io->eof = 1;
911                 return readsize;
912         } while (1);
915 DEFINE_ALLOCATOR(io_realloc_buf, char, BUFSIZ)
917 static char *
918 io_get(struct io *io, int c, bool can_read)
920         char *eol;
921         ssize_t readsize;
923         while (TRUE) {
924                 if (io->bufsize > 0) {
925                         eol = memchr(io->bufpos, c, io->bufsize);
926                         if (eol) {
927                                 char *line = io->bufpos;
929                                 *eol = 0;
930                                 io->bufpos = eol + 1;
931                                 io->bufsize -= io->bufpos - line;
932                                 return line;
933                         }
934                 }
936                 if (io_eof(io)) {
937                         if (io->bufsize) {
938                                 io->bufpos[io->bufsize] = 0;
939                                 io->bufsize = 0;
940                                 return io->bufpos;
941                         }
942                         return NULL;
943                 }
945                 if (!can_read)
946                         return NULL;
948                 if (io->bufsize > 0 && io->bufpos > io->buf)
949                         memmove(io->buf, io->bufpos, io->bufsize);
951                 if (io->bufalloc == io->bufsize) {
952                         if (!io_realloc_buf(&io->buf, io->bufalloc, BUFSIZ))
953                                 return NULL;
954                         io->bufalloc += BUFSIZ;
955                 }
957                 io->bufpos = io->buf;
958                 readsize = io_read(io, io->buf + io->bufsize, io->bufalloc - io->bufsize);
959                 if (io_error(io))
960                         return NULL;
961                 io->bufsize += readsize;
962         }
965 static bool
966 io_write(struct io *io, const void *buf, size_t bufsize)
968         size_t written = 0;
970         while (!io_error(io) && written < bufsize) {
971                 ssize_t size;
973                 size = write(io->pipe, buf + written, bufsize - written);
974                 if (size < 0 && (errno == EAGAIN || errno == EINTR))
975                         continue;
976                 else if (size == -1)
977                         io->error = errno;
978                 else
979                         written += size;
980         }
982         return written == bufsize;
985 static bool
986 io_read_buf(struct io *io, char buf[], size_t bufsize)
988         char *result = io_get(io, '\n', TRUE);
990         if (result) {
991                 result = chomp_string(result);
992                 string_ncopy_do(buf, bufsize, result, strlen(result));
993         }
995         return io_done(io) && result;
998 static bool
999 io_run_buf(const char **argv, char buf[], size_t bufsize)
1001         struct io io;
1003         return io_run(&io, IO_RD, NULL, argv) && io_read_buf(&io, buf, bufsize);
1006 static int
1007 io_load(struct io *io, const char *separators,
1008         int (*read_property)(char *, size_t, char *, size_t))
1010         char *name;
1011         int state = OK;
1013         while (state == OK && (name = io_get(io, '\n', TRUE))) {
1014                 char *value;
1015                 size_t namelen;
1016                 size_t valuelen;
1018                 name = chomp_string(name);
1019                 namelen = strcspn(name, separators);
1021                 if (name[namelen]) {
1022                         name[namelen] = 0;
1023                         value = chomp_string(name + namelen + 1);
1024                         valuelen = strlen(value);
1026                 } else {
1027                         value = "";
1028                         valuelen = 0;
1029                 }
1031                 state = read_property(name, namelen, value, valuelen);
1032         }
1034         if (state != ERR && io_error(io))
1035                 state = ERR;
1036         io_done(io);
1038         return state;
1041 static int
1042 io_run_load(const char **argv, const char *separators,
1043             int (*read_property)(char *, size_t, char *, size_t))
1045         struct io io;
1047         if (!io_run(&io, IO_RD, NULL, argv))
1048                 return ERR;
1049         return io_load(&io, separators, read_property);
1053 /*
1054  * User requests
1055  */
1057 #define REQ_INFO \
1058         /* XXX: Keep the view request first and in sync with views[]. */ \
1059         REQ_GROUP("View switching") \
1060         REQ_(VIEW_MAIN,         "Show main view"), \
1061         REQ_(VIEW_DIFF,         "Show diff view"), \
1062         REQ_(VIEW_LOG,          "Show log view"), \
1063         REQ_(VIEW_TREE,         "Show tree view"), \
1064         REQ_(VIEW_BLOB,         "Show blob view"), \
1065         REQ_(VIEW_BLAME,        "Show blame view"), \
1066         REQ_(VIEW_BRANCH,       "Show branch view"), \
1067         REQ_(VIEW_HELP,         "Show help page"), \
1068         REQ_(VIEW_PAGER,        "Show pager view"), \
1069         REQ_(VIEW_STATUS,       "Show status view"), \
1070         REQ_(VIEW_STAGE,        "Show stage view"), \
1071         \
1072         REQ_GROUP("View manipulation") \
1073         REQ_(ENTER,             "Enter current line and scroll"), \
1074         REQ_(NEXT,              "Move to next"), \
1075         REQ_(PREVIOUS,          "Move to previous"), \
1076         REQ_(PARENT,            "Move to parent"), \
1077         REQ_(VIEW_NEXT,         "Move focus to next view"), \
1078         REQ_(REFRESH,           "Reload and refresh"), \
1079         REQ_(MAXIMIZE,          "Maximize the current view"), \
1080         REQ_(VIEW_CLOSE,        "Close the current view"), \
1081         REQ_(QUIT,              "Close all views and quit"), \
1082         \
1083         REQ_GROUP("View specific requests") \
1084         REQ_(STATUS_UPDATE,     "Update file status"), \
1085         REQ_(STATUS_REVERT,     "Revert file changes"), \
1086         REQ_(STATUS_MERGE,      "Merge file using external tool"), \
1087         REQ_(STAGE_NEXT,        "Find next chunk to stage"), \
1088         \
1089         REQ_GROUP("Cursor navigation") \
1090         REQ_(MOVE_UP,           "Move cursor one line up"), \
1091         REQ_(MOVE_DOWN,         "Move cursor one line down"), \
1092         REQ_(MOVE_PAGE_DOWN,    "Move cursor one page down"), \
1093         REQ_(MOVE_PAGE_UP,      "Move cursor one page up"), \
1094         REQ_(MOVE_FIRST_LINE,   "Move cursor to first line"), \
1095         REQ_(MOVE_LAST_LINE,    "Move cursor to last line"), \
1096         \
1097         REQ_GROUP("Scrolling") \
1098         REQ_(SCROLL_LEFT,       "Scroll two columns left"), \
1099         REQ_(SCROLL_RIGHT,      "Scroll two columns right"), \
1100         REQ_(SCROLL_LINE_UP,    "Scroll one line up"), \
1101         REQ_(SCROLL_LINE_DOWN,  "Scroll one line down"), \
1102         REQ_(SCROLL_PAGE_UP,    "Scroll one page up"), \
1103         REQ_(SCROLL_PAGE_DOWN,  "Scroll one page down"), \
1104         \
1105         REQ_GROUP("Searching") \
1106         REQ_(SEARCH,            "Search the view"), \
1107         REQ_(SEARCH_BACK,       "Search backwards in the view"), \
1108         REQ_(FIND_NEXT,         "Find next search match"), \
1109         REQ_(FIND_PREV,         "Find previous search match"), \
1110         \
1111         REQ_GROUP("Option manipulation") \
1112         REQ_(OPTIONS,           "Open option menu"), \
1113         REQ_(TOGGLE_LINENO,     "Toggle line numbers"), \
1114         REQ_(TOGGLE_DATE,       "Toggle date display"), \
1115         REQ_(TOGGLE_DATE_SHORT, "Toggle short (date-only) dates"), \
1116         REQ_(TOGGLE_AUTHOR,     "Toggle author display"), \
1117         REQ_(TOGGLE_REV_GRAPH,  "Toggle revision graph visualization"), \
1118         REQ_(TOGGLE_REFS,       "Toggle reference display (tags/branches)"), \
1119         REQ_(TOGGLE_SORT_ORDER, "Toggle ascending/descending sort order"), \
1120         REQ_(TOGGLE_SORT_FIELD, "Toggle field to sort by"), \
1121         \
1122         REQ_GROUP("Misc") \
1123         REQ_(PROMPT,            "Bring up the prompt"), \
1124         REQ_(SCREEN_REDRAW,     "Redraw the screen"), \
1125         REQ_(SHOW_VERSION,      "Show version information"), \
1126         REQ_(STOP_LOADING,      "Stop all loading views"), \
1127         REQ_(EDIT,              "Open in editor"), \
1128         REQ_(NONE,              "Do nothing")
1131 /* User action requests. */
1132 enum request {
1133 #define REQ_GROUP(help)
1134 #define REQ_(req, help) REQ_##req
1136         /* Offset all requests to avoid conflicts with ncurses getch values. */
1137         REQ_UNKNOWN = KEY_MAX + 1,
1138         REQ_OFFSET,
1139         REQ_INFO
1141 #undef  REQ_GROUP
1142 #undef  REQ_
1143 };
1145 struct request_info {
1146         enum request request;
1147         const char *name;
1148         int namelen;
1149         const char *help;
1150 };
1152 static const struct request_info req_info[] = {
1153 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
1154 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
1155         REQ_INFO
1156 #undef  REQ_GROUP
1157 #undef  REQ_
1158 };
1160 static enum request
1161 get_request(const char *name)
1163         int namelen = strlen(name);
1164         int i;
1166         for (i = 0; i < ARRAY_SIZE(req_info); i++)
1167                 if (enum_equals(req_info[i], name, namelen))
1168                         return req_info[i].request;
1170         return REQ_UNKNOWN;
1174 /*
1175  * Options
1176  */
1178 /* Option and state variables. */
1179 static enum date opt_date               = DATE_DEFAULT;
1180 static enum author opt_author           = AUTHOR_DEFAULT;
1181 static bool opt_line_number             = FALSE;
1182 static bool opt_line_graphics           = TRUE;
1183 static bool opt_rev_graph               = FALSE;
1184 static bool opt_show_refs               = TRUE;
1185 static int opt_num_interval             = 5;
1186 static double opt_hscroll               = 0.50;
1187 static double opt_scale_split_view      = 2.0 / 3.0;
1188 static int opt_tab_size                 = 8;
1189 static int opt_author_cols              = AUTHOR_COLS;
1190 static char opt_path[SIZEOF_STR]        = "";
1191 static char opt_file[SIZEOF_STR]        = "";
1192 static char opt_ref[SIZEOF_REF]         = "";
1193 static char opt_head[SIZEOF_REF]        = "";
1194 static char opt_remote[SIZEOF_REF]      = "";
1195 static char opt_encoding[20]            = "UTF-8";
1196 static iconv_t opt_iconv_in             = ICONV_NONE;
1197 static iconv_t opt_iconv_out            = ICONV_NONE;
1198 static char opt_search[SIZEOF_STR]      = "";
1199 static char opt_cdup[SIZEOF_STR]        = "";
1200 static char opt_prefix[SIZEOF_STR]      = "";
1201 static char opt_git_dir[SIZEOF_STR]     = "";
1202 static signed char opt_is_inside_work_tree      = -1; /* set to TRUE or FALSE */
1203 static char opt_editor[SIZEOF_STR]      = "";
1204 static FILE *opt_tty                    = NULL;
1206 #define is_initial_commit()     (!get_ref_head())
1207 #define is_head_commit(rev)     (!strcmp((rev), "HEAD") || (get_ref_head() && !strcmp(rev, get_ref_head()->id)))
1210 /*
1211  * Line-oriented content detection.
1212  */
1214 #define LINE_INFO \
1215 LINE(DIFF_HEADER,  "diff --git ",       COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1216 LINE(DIFF_CHUNK,   "@@",                COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1217 LINE(DIFF_ADD,     "+",                 COLOR_GREEN,    COLOR_DEFAULT,  0), \
1218 LINE(DIFF_DEL,     "-",                 COLOR_RED,      COLOR_DEFAULT,  0), \
1219 LINE(DIFF_INDEX,        "index ",         COLOR_BLUE,   COLOR_DEFAULT,  0), \
1220 LINE(DIFF_OLDMODE,      "old file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
1221 LINE(DIFF_NEWMODE,      "new file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
1222 LINE(DIFF_COPY_FROM,    "copy from",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
1223 LINE(DIFF_COPY_TO,      "copy to",        COLOR_YELLOW, COLOR_DEFAULT,  0), \
1224 LINE(DIFF_RENAME_FROM,  "rename from",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
1225 LINE(DIFF_RENAME_TO,    "rename to",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
1226 LINE(DIFF_SIMILARITY,   "similarity ",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
1227 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
1228 LINE(DIFF_TREE,         "diff-tree ",     COLOR_BLUE,   COLOR_DEFAULT,  0), \
1229 LINE(PP_AUTHOR,    "Author: ",          COLOR_CYAN,     COLOR_DEFAULT,  0), \
1230 LINE(PP_COMMIT,    "Commit: ",          COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1231 LINE(PP_MERGE,     "Merge: ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
1232 LINE(PP_DATE,      "Date:   ",          COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1233 LINE(PP_ADATE,     "AuthorDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1234 LINE(PP_CDATE,     "CommitDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1235 LINE(PP_REFS,      "Refs: ",            COLOR_RED,      COLOR_DEFAULT,  0), \
1236 LINE(COMMIT,       "commit ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
1237 LINE(PARENT,       "parent ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
1238 LINE(TREE,         "tree ",             COLOR_BLUE,     COLOR_DEFAULT,  0), \
1239 LINE(AUTHOR,       "author ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
1240 LINE(COMMITTER,    "committer ",        COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1241 LINE(SIGNOFF,      "    Signed-off-by", COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1242 LINE(ACKED,        "    Acked-by",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1243 LINE(TESTED,       "    Tested-by",     COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1244 LINE(REVIEWED,     "    Reviewed-by",   COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1245 LINE(DEFAULT,      "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
1246 LINE(CURSOR,       "",                  COLOR_WHITE,    COLOR_GREEN,    A_BOLD), \
1247 LINE(STATUS,       "",                  COLOR_GREEN,    COLOR_DEFAULT,  0), \
1248 LINE(DELIMITER,    "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1249 LINE(DATE,         "",                  COLOR_BLUE,     COLOR_DEFAULT,  0), \
1250 LINE(MODE,         "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1251 LINE(LINE_NUMBER,  "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1252 LINE(TITLE_BLUR,   "",                  COLOR_WHITE,    COLOR_BLUE,     0), \
1253 LINE(TITLE_FOCUS,  "",                  COLOR_WHITE,    COLOR_BLUE,     A_BOLD), \
1254 LINE(MAIN_COMMIT,  "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
1255 LINE(MAIN_TAG,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  A_BOLD), \
1256 LINE(MAIN_LOCAL_TAG,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1257 LINE(MAIN_REMOTE,  "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1258 LINE(MAIN_TRACKED, "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_BOLD), \
1259 LINE(MAIN_REF,     "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1260 LINE(MAIN_HEAD,    "",                  COLOR_CYAN,     COLOR_DEFAULT,  A_BOLD), \
1261 LINE(MAIN_REVGRAPH,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1262 LINE(TREE_HEAD,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_BOLD), \
1263 LINE(TREE_DIR,     "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_NORMAL), \
1264 LINE(TREE_FILE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
1265 LINE(STAT_HEAD,    "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1266 LINE(STAT_SECTION, "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1267 LINE(STAT_NONE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
1268 LINE(STAT_STAGED,  "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1269 LINE(STAT_UNSTAGED,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1270 LINE(STAT_UNTRACKED,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1271 LINE(HELP_KEYMAP,  "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1272 LINE(HELP_GROUP,   "",                  COLOR_BLUE,     COLOR_DEFAULT,  0), \
1273 LINE(BLAME_ID,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0)
1275 enum line_type {
1276 #define LINE(type, line, fg, bg, attr) \
1277         LINE_##type
1278         LINE_INFO,
1279         LINE_NONE
1280 #undef  LINE
1281 };
1283 struct line_info {
1284         const char *name;       /* Option name. */
1285         int namelen;            /* Size of option name. */
1286         const char *line;       /* The start of line to match. */
1287         int linelen;            /* Size of string to match. */
1288         int fg, bg, attr;       /* Color and text attributes for the lines. */
1289 };
1291 static struct line_info line_info[] = {
1292 #define LINE(type, line, fg, bg, attr) \
1293         { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
1294         LINE_INFO
1295 #undef  LINE
1296 };
1298 static enum line_type
1299 get_line_type(const char *line)
1301         int linelen = strlen(line);
1302         enum line_type type;
1304         for (type = 0; type < ARRAY_SIZE(line_info); type++)
1305                 /* Case insensitive search matches Signed-off-by lines better. */
1306                 if (linelen >= line_info[type].linelen &&
1307                     !strncasecmp(line_info[type].line, line, line_info[type].linelen))
1308                         return type;
1310         return LINE_DEFAULT;
1313 static inline int
1314 get_line_attr(enum line_type type)
1316         assert(type < ARRAY_SIZE(line_info));
1317         return COLOR_PAIR(type) | line_info[type].attr;
1320 static struct line_info *
1321 get_line_info(const char *name)
1323         size_t namelen = strlen(name);
1324         enum line_type type;
1326         for (type = 0; type < ARRAY_SIZE(line_info); type++)
1327                 if (enum_equals(line_info[type], name, namelen))
1328                         return &line_info[type];
1330         return NULL;
1333 static void
1334 init_colors(void)
1336         int default_bg = line_info[LINE_DEFAULT].bg;
1337         int default_fg = line_info[LINE_DEFAULT].fg;
1338         enum line_type type;
1340         start_color();
1342         if (assume_default_colors(default_fg, default_bg) == ERR) {
1343                 default_bg = COLOR_BLACK;
1344                 default_fg = COLOR_WHITE;
1345         }
1347         for (type = 0; type < ARRAY_SIZE(line_info); type++) {
1348                 struct line_info *info = &line_info[type];
1349                 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
1350                 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
1352                 init_pair(type, fg, bg);
1353         }
1356 struct line {
1357         enum line_type type;
1359         /* State flags */
1360         unsigned int selected:1;
1361         unsigned int dirty:1;
1362         unsigned int cleareol:1;
1363         unsigned int other:16;
1365         void *data;             /* User data */
1366 };
1369 /*
1370  * Keys
1371  */
1373 struct keybinding {
1374         int alias;
1375         enum request request;
1376 };
1378 static struct keybinding default_keybindings[] = {
1379         /* View switching */
1380         { 'm',          REQ_VIEW_MAIN },
1381         { 'd',          REQ_VIEW_DIFF },
1382         { 'l',          REQ_VIEW_LOG },
1383         { 't',          REQ_VIEW_TREE },
1384         { 'f',          REQ_VIEW_BLOB },
1385         { 'B',          REQ_VIEW_BLAME },
1386         { 'H',          REQ_VIEW_BRANCH },
1387         { 'p',          REQ_VIEW_PAGER },
1388         { 'h',          REQ_VIEW_HELP },
1389         { 'S',          REQ_VIEW_STATUS },
1390         { 'c',          REQ_VIEW_STAGE },
1392         /* View manipulation */
1393         { 'q',          REQ_VIEW_CLOSE },
1394         { KEY_TAB,      REQ_VIEW_NEXT },
1395         { KEY_RETURN,   REQ_ENTER },
1396         { KEY_UP,       REQ_PREVIOUS },
1397         { KEY_DOWN,     REQ_NEXT },
1398         { 'R',          REQ_REFRESH },
1399         { KEY_F(5),     REQ_REFRESH },
1400         { 'O',          REQ_MAXIMIZE },
1402         /* Cursor navigation */
1403         { 'k',          REQ_MOVE_UP },
1404         { 'j',          REQ_MOVE_DOWN },
1405         { KEY_HOME,     REQ_MOVE_FIRST_LINE },
1406         { KEY_END,      REQ_MOVE_LAST_LINE },
1407         { KEY_NPAGE,    REQ_MOVE_PAGE_DOWN },
1408         { ' ',          REQ_MOVE_PAGE_DOWN },
1409         { KEY_PPAGE,    REQ_MOVE_PAGE_UP },
1410         { 'b',          REQ_MOVE_PAGE_UP },
1411         { '-',          REQ_MOVE_PAGE_UP },
1413         /* Scrolling */
1414         { KEY_LEFT,     REQ_SCROLL_LEFT },
1415         { KEY_RIGHT,    REQ_SCROLL_RIGHT },
1416         { KEY_IC,       REQ_SCROLL_LINE_UP },
1417         { KEY_DC,       REQ_SCROLL_LINE_DOWN },
1418         { 'w',          REQ_SCROLL_PAGE_UP },
1419         { 's',          REQ_SCROLL_PAGE_DOWN },
1421         /* Searching */
1422         { '/',          REQ_SEARCH },
1423         { '?',          REQ_SEARCH_BACK },
1424         { 'n',          REQ_FIND_NEXT },
1425         { 'N',          REQ_FIND_PREV },
1427         /* Misc */
1428         { 'Q',          REQ_QUIT },
1429         { 'z',          REQ_STOP_LOADING },
1430         { 'v',          REQ_SHOW_VERSION },
1431         { 'r',          REQ_SCREEN_REDRAW },
1432         { 'o',          REQ_OPTIONS },
1433         { '.',          REQ_TOGGLE_LINENO },
1434         { 'D',          REQ_TOGGLE_DATE },
1435         { 'A',          REQ_TOGGLE_AUTHOR },
1436         { 'g',          REQ_TOGGLE_REV_GRAPH },
1437         { 'F',          REQ_TOGGLE_REFS },
1438         { 'I',          REQ_TOGGLE_SORT_ORDER },
1439         { 'i',          REQ_TOGGLE_SORT_FIELD },
1440         { ':',          REQ_PROMPT },
1441         { 'u',          REQ_STATUS_UPDATE },
1442         { '!',          REQ_STATUS_REVERT },
1443         { 'M',          REQ_STATUS_MERGE },
1444         { '@',          REQ_STAGE_NEXT },
1445         { ',',          REQ_PARENT },
1446         { 'e',          REQ_EDIT },
1447 };
1449 #define KEYMAP_INFO \
1450         KEYMAP_(GENERIC), \
1451         KEYMAP_(MAIN), \
1452         KEYMAP_(DIFF), \
1453         KEYMAP_(LOG), \
1454         KEYMAP_(TREE), \
1455         KEYMAP_(BLOB), \
1456         KEYMAP_(BLAME), \
1457         KEYMAP_(BRANCH), \
1458         KEYMAP_(PAGER), \
1459         KEYMAP_(HELP), \
1460         KEYMAP_(STATUS), \
1461         KEYMAP_(STAGE)
1463 enum keymap {
1464 #define KEYMAP_(name) KEYMAP_##name
1465         KEYMAP_INFO
1466 #undef  KEYMAP_
1467 };
1469 static const struct enum_map keymap_table[] = {
1470 #define KEYMAP_(name) ENUM_MAP(#name, KEYMAP_##name)
1471         KEYMAP_INFO
1472 #undef  KEYMAP_
1473 };
1475 #define set_keymap(map, name) map_enum(map, keymap_table, name)
1477 struct keybinding_table {
1478         struct keybinding *data;
1479         size_t size;
1480 };
1482 static struct keybinding_table keybindings[ARRAY_SIZE(keymap_table)];
1484 static void
1485 add_keybinding(enum keymap keymap, enum request request, int key)
1487         struct keybinding_table *table = &keybindings[keymap];
1488         size_t i;
1490         for (i = 0; i < keybindings[keymap].size; i++) {
1491                 if (keybindings[keymap].data[i].alias == key) {
1492                         keybindings[keymap].data[i].request = request;
1493                         return;
1494                 }
1495         }
1497         table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data));
1498         if (!table->data)
1499                 die("Failed to allocate keybinding");
1500         table->data[table->size].alias = key;
1501         table->data[table->size++].request = request;
1503         if (request == REQ_NONE && keymap == KEYMAP_GENERIC) {
1504                 int i;
1506                 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1507                         if (default_keybindings[i].alias == key)
1508                                 default_keybindings[i].request = REQ_NONE;
1509         }
1512 /* Looks for a key binding first in the given map, then in the generic map, and
1513  * lastly in the default keybindings. */
1514 static enum request
1515 get_keybinding(enum keymap keymap, int key)
1517         size_t i;
1519         for (i = 0; i < keybindings[keymap].size; i++)
1520                 if (keybindings[keymap].data[i].alias == key)
1521                         return keybindings[keymap].data[i].request;
1523         for (i = 0; i < keybindings[KEYMAP_GENERIC].size; i++)
1524                 if (keybindings[KEYMAP_GENERIC].data[i].alias == key)
1525                         return keybindings[KEYMAP_GENERIC].data[i].request;
1527         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1528                 if (default_keybindings[i].alias == key)
1529                         return default_keybindings[i].request;
1531         return (enum request) key;
1535 struct key {
1536         const char *name;
1537         int value;
1538 };
1540 static const struct key key_table[] = {
1541         { "Enter",      KEY_RETURN },
1542         { "Space",      ' ' },
1543         { "Backspace",  KEY_BACKSPACE },
1544         { "Tab",        KEY_TAB },
1545         { "Escape",     KEY_ESC },
1546         { "Left",       KEY_LEFT },
1547         { "Right",      KEY_RIGHT },
1548         { "Up",         KEY_UP },
1549         { "Down",       KEY_DOWN },
1550         { "Insert",     KEY_IC },
1551         { "Delete",     KEY_DC },
1552         { "Hash",       '#' },
1553         { "Home",       KEY_HOME },
1554         { "End",        KEY_END },
1555         { "PageUp",     KEY_PPAGE },
1556         { "PageDown",   KEY_NPAGE },
1557         { "F1",         KEY_F(1) },
1558         { "F2",         KEY_F(2) },
1559         { "F3",         KEY_F(3) },
1560         { "F4",         KEY_F(4) },
1561         { "F5",         KEY_F(5) },
1562         { "F6",         KEY_F(6) },
1563         { "F7",         KEY_F(7) },
1564         { "F8",         KEY_F(8) },
1565         { "F9",         KEY_F(9) },
1566         { "F10",        KEY_F(10) },
1567         { "F11",        KEY_F(11) },
1568         { "F12",        KEY_F(12) },
1569 };
1571 static int
1572 get_key_value(const char *name)
1574         int i;
1576         for (i = 0; i < ARRAY_SIZE(key_table); i++)
1577                 if (!strcasecmp(key_table[i].name, name))
1578                         return key_table[i].value;
1580         if (strlen(name) == 1 && isprint(*name))
1581                 return (int) *name;
1583         return ERR;
1586 static const char *
1587 get_key_name(int key_value)
1589         static char key_char[] = "'X'";
1590         const char *seq = NULL;
1591         int key;
1593         for (key = 0; key < ARRAY_SIZE(key_table); key++)
1594                 if (key_table[key].value == key_value)
1595                         seq = key_table[key].name;
1597         if (seq == NULL &&
1598             key_value < 127 &&
1599             isprint(key_value)) {
1600                 key_char[1] = (char) key_value;
1601                 seq = key_char;
1602         }
1604         return seq ? seq : "(no key)";
1607 static bool
1608 append_key(char *buf, size_t *pos, const struct keybinding *keybinding)
1610         const char *sep = *pos > 0 ? ", " : "";
1611         const char *keyname = get_key_name(keybinding->alias);
1613         return string_nformat(buf, BUFSIZ, pos, "%s%s", sep, keyname);
1616 static bool
1617 append_keymap_request_keys(char *buf, size_t *pos, enum request request,
1618                            enum keymap keymap, bool all)
1620         int i;
1622         for (i = 0; i < keybindings[keymap].size; i++) {
1623                 if (keybindings[keymap].data[i].request == request) {
1624                         if (!append_key(buf, pos, &keybindings[keymap].data[i]))
1625                                 return FALSE;
1626                         if (!all)
1627                                 break;
1628                 }
1629         }
1631         return TRUE;
1634 #define get_key(keymap, request) get_keys(keymap, request, FALSE)
1636 static const char *
1637 get_keys(enum keymap keymap, enum request request, bool all)
1639         static char buf[BUFSIZ];
1640         size_t pos = 0;
1641         int i;
1643         buf[pos] = 0;
1645         if (!append_keymap_request_keys(buf, &pos, request, keymap, all))
1646                 return "Too many keybindings!";
1647         if (pos > 0 && !all)
1648                 return buf;
1650         if (keymap != KEYMAP_GENERIC) {
1651                 /* Only the generic keymap includes the default keybindings when
1652                  * listing all keys. */
1653                 if (all)
1654                         return buf;
1656                 if (!append_keymap_request_keys(buf, &pos, request, KEYMAP_GENERIC, all))
1657                         return "Too many keybindings!";
1658                 if (pos)
1659                         return buf;
1660         }
1662         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1663                 if (default_keybindings[i].request == request) {
1664                         if (!append_key(buf, &pos, &default_keybindings[i]))
1665                                 return "Too many keybindings!";
1666                         if (!all)
1667                                 return buf;
1668                 }
1669         }
1671         return buf;
1674 struct run_request {
1675         enum keymap keymap;
1676         int key;
1677         const char *argv[SIZEOF_ARG];
1678 };
1680 static struct run_request *run_request;
1681 static size_t run_requests;
1683 DEFINE_ALLOCATOR(realloc_run_requests, struct run_request, 8)
1685 static enum request
1686 add_run_request(enum keymap keymap, int key, int argc, const char **argv)
1688         struct run_request *req;
1690         if (argc >= ARRAY_SIZE(req->argv) - 1)
1691                 return REQ_NONE;
1693         if (!realloc_run_requests(&run_request, run_requests, 1))
1694                 return REQ_NONE;
1696         req = &run_request[run_requests];
1697         req->keymap = keymap;
1698         req->key = key;
1699         req->argv[0] = NULL;
1701         if (!argv_copy(req->argv, argv))
1702                 return REQ_NONE;
1704         return REQ_NONE + ++run_requests;
1707 static struct run_request *
1708 get_run_request(enum request request)
1710         if (request <= REQ_NONE)
1711                 return NULL;
1712         return &run_request[request - REQ_NONE - 1];
1715 static void
1716 add_builtin_run_requests(void)
1718         const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1719         const char *checkout[] = { "git", "checkout", "%(branch)", NULL };
1720         const char *commit[] = { "git", "commit", NULL };
1721         const char *gc[] = { "git", "gc", NULL };
1722         struct {
1723                 enum keymap keymap;
1724                 int key;
1725                 int argc;
1726                 const char **argv;
1727         } reqs[] = {
1728                 { KEYMAP_MAIN,    'C', ARRAY_SIZE(cherry_pick) - 1, cherry_pick },
1729                 { KEYMAP_STATUS,  'C', ARRAY_SIZE(commit) - 1, commit },
1730                 { KEYMAP_BRANCH,  'C', ARRAY_SIZE(checkout) - 1, checkout },
1731                 { KEYMAP_GENERIC, 'G', ARRAY_SIZE(gc) - 1, gc },
1732         };
1733         int i;
1735         for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1736                 enum request req = get_keybinding(reqs[i].keymap, reqs[i].key);
1738                 if (req != reqs[i].key)
1739                         continue;
1740                 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argc, reqs[i].argv);
1741                 if (req != REQ_NONE)
1742                         add_keybinding(reqs[i].keymap, req, reqs[i].key);
1743         }
1746 /*
1747  * User config file handling.
1748  */
1750 static int   config_lineno;
1751 static bool  config_errors;
1752 static const char *config_msg;
1754 static const struct enum_map color_map[] = {
1755 #define COLOR_MAP(name) ENUM_MAP(#name, COLOR_##name)
1756         COLOR_MAP(DEFAULT),
1757         COLOR_MAP(BLACK),
1758         COLOR_MAP(BLUE),
1759         COLOR_MAP(CYAN),
1760         COLOR_MAP(GREEN),
1761         COLOR_MAP(MAGENTA),
1762         COLOR_MAP(RED),
1763         COLOR_MAP(WHITE),
1764         COLOR_MAP(YELLOW),
1765 };
1767 static const struct enum_map attr_map[] = {
1768 #define ATTR_MAP(name) ENUM_MAP(#name, A_##name)
1769         ATTR_MAP(NORMAL),
1770         ATTR_MAP(BLINK),
1771         ATTR_MAP(BOLD),
1772         ATTR_MAP(DIM),
1773         ATTR_MAP(REVERSE),
1774         ATTR_MAP(STANDOUT),
1775         ATTR_MAP(UNDERLINE),
1776 };
1778 #define set_attribute(attr, name)       map_enum(attr, attr_map, name)
1780 static int parse_step(double *opt, const char *arg)
1782         *opt = atoi(arg);
1783         if (!strchr(arg, '%'))
1784                 return OK;
1786         /* "Shift down" so 100% and 1 does not conflict. */
1787         *opt = (*opt - 1) / 100;
1788         if (*opt >= 1.0) {
1789                 *opt = 0.99;
1790                 config_msg = "Step value larger than 100%";
1791                 return ERR;
1792         }
1793         if (*opt < 0.0) {
1794                 *opt = 1;
1795                 config_msg = "Invalid step value";
1796                 return ERR;
1797         }
1798         return OK;
1801 static int
1802 parse_int(int *opt, const char *arg, int min, int max)
1804         int value = atoi(arg);
1806         if (min <= value && value <= max) {
1807                 *opt = value;
1808                 return OK;
1809         }
1811         config_msg = "Integer value out of bound";
1812         return ERR;
1815 static bool
1816 set_color(int *color, const char *name)
1818         if (map_enum(color, color_map, name))
1819                 return TRUE;
1820         if (!prefixcmp(name, "color"))
1821                 return parse_int(color, name + 5, 0, 255) == OK;
1822         return FALSE;
1825 /* Wants: object fgcolor bgcolor [attribute] */
1826 static int
1827 option_color_command(int argc, const char *argv[])
1829         struct line_info *info;
1831         if (argc < 3) {
1832                 config_msg = "Wrong number of arguments given to color command";
1833                 return ERR;
1834         }
1836         info = get_line_info(argv[0]);
1837         if (!info) {
1838                 static const struct enum_map obsolete[] = {
1839                         ENUM_MAP("main-delim",  LINE_DELIMITER),
1840                         ENUM_MAP("main-date",   LINE_DATE),
1841                         ENUM_MAP("main-author", LINE_AUTHOR),
1842                 };
1843                 int index;
1845                 if (!map_enum(&index, obsolete, argv[0])) {
1846                         config_msg = "Unknown color name";
1847                         return ERR;
1848                 }
1849                 info = &line_info[index];
1850         }
1852         if (!set_color(&info->fg, argv[1]) ||
1853             !set_color(&info->bg, argv[2])) {
1854                 config_msg = "Unknown color";
1855                 return ERR;
1856         }
1858         info->attr = 0;
1859         while (argc-- > 3) {
1860                 int attr;
1862                 if (!set_attribute(&attr, argv[argc])) {
1863                         config_msg = "Unknown attribute";
1864                         return ERR;
1865                 }
1866                 info->attr |= attr;
1867         }
1869         return OK;
1872 static int parse_bool(bool *opt, const char *arg)
1874         *opt = (!strcmp(arg, "1") || !strcmp(arg, "true") || !strcmp(arg, "yes"))
1875                 ? TRUE : FALSE;
1876         return OK;
1879 static int parse_enum_do(unsigned int *opt, const char *arg,
1880                          const struct enum_map *map, size_t map_size)
1882         bool is_true;
1884         assert(map_size > 1);
1886         if (map_enum_do(map, map_size, (int *) opt, arg))
1887                 return OK;
1889         if (parse_bool(&is_true, arg) != OK)
1890                 return ERR;
1892         *opt = is_true ? map[1].value : map[0].value;
1893         return OK;
1896 #define parse_enum(opt, arg, map) \
1897         parse_enum_do(opt, arg, map, ARRAY_SIZE(map))
1899 static int
1900 parse_string(char *opt, const char *arg, size_t optsize)
1902         int arglen = strlen(arg);
1904         switch (arg[0]) {
1905         case '\"':
1906         case '\'':
1907                 if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1908                         config_msg = "Unmatched quotation";
1909                         return ERR;
1910                 }
1911                 arg += 1; arglen -= 2;
1912         default:
1913                 string_ncopy_do(opt, optsize, arg, arglen);
1914                 return OK;
1915         }
1918 /* Wants: name = value */
1919 static int
1920 option_set_command(int argc, const char *argv[])
1922         if (argc != 3) {
1923                 config_msg = "Wrong number of arguments given to set command";
1924                 return ERR;
1925         }
1927         if (strcmp(argv[1], "=")) {
1928                 config_msg = "No value assigned";
1929                 return ERR;
1930         }
1932         if (!strcmp(argv[0], "show-author"))
1933                 return parse_enum(&opt_author, argv[2], author_map);
1935         if (!strcmp(argv[0], "show-date"))
1936                 return parse_enum(&opt_date, argv[2], date_map);
1938         if (!strcmp(argv[0], "show-rev-graph"))
1939                 return parse_bool(&opt_rev_graph, argv[2]);
1941         if (!strcmp(argv[0], "show-refs"))
1942                 return parse_bool(&opt_show_refs, argv[2]);
1944         if (!strcmp(argv[0], "show-line-numbers"))
1945                 return parse_bool(&opt_line_number, argv[2]);
1947         if (!strcmp(argv[0], "line-graphics"))
1948                 return parse_bool(&opt_line_graphics, argv[2]);
1950         if (!strcmp(argv[0], "line-number-interval"))
1951                 return parse_int(&opt_num_interval, argv[2], 1, 1024);
1953         if (!strcmp(argv[0], "author-width"))
1954                 return parse_int(&opt_author_cols, argv[2], 0, 1024);
1956         if (!strcmp(argv[0], "horizontal-scroll"))
1957                 return parse_step(&opt_hscroll, argv[2]);
1959         if (!strcmp(argv[0], "split-view-height"))
1960                 return parse_step(&opt_scale_split_view, argv[2]);
1962         if (!strcmp(argv[0], "tab-size"))
1963                 return parse_int(&opt_tab_size, argv[2], 1, 1024);
1965         if (!strcmp(argv[0], "commit-encoding"))
1966                 return parse_string(opt_encoding, argv[2], sizeof(opt_encoding));
1968         config_msg = "Unknown variable name";
1969         return ERR;
1972 /* Wants: mode request key */
1973 static int
1974 option_bind_command(int argc, const char *argv[])
1976         enum request request;
1977         int keymap = -1;
1978         int key;
1980         if (argc < 3) {
1981                 config_msg = "Wrong number of arguments given to bind command";
1982                 return ERR;
1983         }
1985         if (!set_keymap(&keymap, argv[0])) {
1986                 config_msg = "Unknown key map";
1987                 return ERR;
1988         }
1990         key = get_key_value(argv[1]);
1991         if (key == ERR) {
1992                 config_msg = "Unknown key";
1993                 return ERR;
1994         }
1996         request = get_request(argv[2]);
1997         if (request == REQ_UNKNOWN) {
1998                 static const struct enum_map obsolete[] = {
1999                         ENUM_MAP("cherry-pick",         REQ_NONE),
2000                         ENUM_MAP("screen-resize",       REQ_NONE),
2001                         ENUM_MAP("tree-parent",         REQ_PARENT),
2002                 };
2003                 int alias;
2005                 if (map_enum(&alias, obsolete, argv[2])) {
2006                         if (alias != REQ_NONE)
2007                                 add_keybinding(keymap, alias, key);
2008                         config_msg = "Obsolete request name";
2009                         return ERR;
2010                 }
2011         }
2012         if (request == REQ_UNKNOWN && *argv[2]++ == '!')
2013                 request = add_run_request(keymap, key, argc - 2, argv + 2);
2014         if (request == REQ_UNKNOWN) {
2015                 config_msg = "Unknown request name";
2016                 return ERR;
2017         }
2019         add_keybinding(keymap, request, key);
2021         return OK;
2024 static int
2025 set_option(const char *opt, char *value)
2027         const char *argv[SIZEOF_ARG];
2028         int argc = 0;
2030         if (!argv_from_string(argv, &argc, value)) {
2031                 config_msg = "Too many option arguments";
2032                 return ERR;
2033         }
2035         if (!strcmp(opt, "color"))
2036                 return option_color_command(argc, argv);
2038         if (!strcmp(opt, "set"))
2039                 return option_set_command(argc, argv);
2041         if (!strcmp(opt, "bind"))
2042                 return option_bind_command(argc, argv);
2044         config_msg = "Unknown option command";
2045         return ERR;
2048 static int
2049 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
2051         int status = OK;
2053         config_lineno++;
2054         config_msg = "Internal error";
2056         /* Check for comment markers, since read_properties() will
2057          * only ensure opt and value are split at first " \t". */
2058         optlen = strcspn(opt, "#");
2059         if (optlen == 0)
2060                 return OK;
2062         if (opt[optlen] != 0) {
2063                 config_msg = "No option value";
2064                 status = ERR;
2066         }  else {
2067                 /* Look for comment endings in the value. */
2068                 size_t len = strcspn(value, "#");
2070                 if (len < valuelen) {
2071                         valuelen = len;
2072                         value[valuelen] = 0;
2073                 }
2075                 status = set_option(opt, value);
2076         }
2078         if (status == ERR) {
2079                 warn("Error on line %d, near '%.*s': %s",
2080                      config_lineno, (int) optlen, opt, config_msg);
2081                 config_errors = TRUE;
2082         }
2084         /* Always keep going if errors are encountered. */
2085         return OK;
2088 static void
2089 load_option_file(const char *path)
2091         struct io io;
2093         /* It's OK that the file doesn't exist. */
2094         if (!io_open(&io, "%s", path))
2095                 return;
2097         config_lineno = 0;
2098         config_errors = FALSE;
2100         if (io_load(&io, " \t", read_option) == ERR ||
2101             config_errors == TRUE)
2102                 warn("Errors while loading %s.", path);
2105 static int
2106 load_options(void)
2108         const char *home = getenv("HOME");
2109         const char *tigrc_user = getenv("TIGRC_USER");
2110         const char *tigrc_system = getenv("TIGRC_SYSTEM");
2111         char buf[SIZEOF_STR];
2113         if (!tigrc_system)
2114                 tigrc_system = SYSCONFDIR "/tigrc";
2115         load_option_file(tigrc_system);
2117         if (!tigrc_user) {
2118                 if (!home || !string_format(buf, "%s/.tigrc", home))
2119                         return ERR;
2120                 tigrc_user = buf;
2121         }
2122         load_option_file(tigrc_user);
2124         /* Add _after_ loading config files to avoid adding run requests
2125          * that conflict with keybindings. */
2126         add_builtin_run_requests();
2128         return OK;
2132 /*
2133  * The viewer
2134  */
2136 struct view;
2137 struct view_ops;
2139 /* The display array of active views and the index of the current view. */
2140 static struct view *display[2];
2141 static unsigned int current_view;
2143 #define foreach_displayed_view(view, i) \
2144         for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
2146 #define displayed_views()       (display[1] != NULL ? 2 : 1)
2148 /* Current head and commit ID */
2149 static char ref_blob[SIZEOF_REF]        = "";
2150 static char ref_commit[SIZEOF_REF]      = "HEAD";
2151 static char ref_head[SIZEOF_REF]        = "HEAD";
2152 static char ref_branch[SIZEOF_REF]      = "";
2154 enum view_type {
2155         VIEW_MAIN,
2156         VIEW_DIFF,
2157         VIEW_LOG,
2158         VIEW_TREE,
2159         VIEW_BLOB,
2160         VIEW_BLAME,
2161         VIEW_BRANCH,
2162         VIEW_HELP,
2163         VIEW_PAGER,
2164         VIEW_STATUS,
2165         VIEW_STAGE,
2166 };
2168 struct view {
2169         enum view_type type;    /* View type */
2170         const char *name;       /* View name */
2171         const char *cmd_env;    /* Command line set via environment */
2172         const char *id;         /* Points to either of ref_{head,commit,blob} */
2174         struct view_ops *ops;   /* View operations */
2176         enum keymap keymap;     /* What keymap does this view have */
2177         bool git_dir;           /* Whether the view requires a git directory. */
2179         char ref[SIZEOF_REF];   /* Hovered commit reference */
2180         char vid[SIZEOF_REF];   /* View ID. Set to id member when updating. */
2182         int height, width;      /* The width and height of the main window */
2183         WINDOW *win;            /* The main window */
2184         WINDOW *title;          /* The title window living below the main window */
2186         /* Navigation */
2187         unsigned long offset;   /* Offset of the window top */
2188         unsigned long yoffset;  /* Offset from the window side. */
2189         unsigned long lineno;   /* Current line number */
2190         unsigned long p_offset; /* Previous offset of the window top */
2191         unsigned long p_yoffset;/* Previous offset from the window side */
2192         unsigned long p_lineno; /* Previous current line number */
2193         bool p_restore;         /* Should the previous position be restored. */
2195         /* Searching */
2196         char grep[SIZEOF_STR];  /* Search string */
2197         regex_t *regex;         /* Pre-compiled regexp */
2199         /* If non-NULL, points to the view that opened this view. If this view
2200          * is closed tig will switch back to the parent view. */
2201         struct view *parent;
2202         struct view *prev;
2204         /* Buffering */
2205         size_t lines;           /* Total number of lines */
2206         struct line *line;      /* Line index */
2207         unsigned int digits;    /* Number of digits in the lines member. */
2209         /* Drawing */
2210         struct line *curline;   /* Line currently being drawn. */
2211         enum line_type curtype; /* Attribute currently used for drawing. */
2212         unsigned long col;      /* Column when drawing. */
2213         bool has_scrolled;      /* View was scrolled. */
2215         /* Loading */
2216         const char *argv[SIZEOF_ARG];   /* Shell command arguments. */
2217         const char *dir;        /* Directory from which to execute. */
2218         struct io io;
2219         struct io *pipe;
2220         time_t start_time;
2221         time_t update_secs;
2222 };
2224 struct view_ops {
2225         /* What type of content being displayed. Used in the title bar. */
2226         const char *type;
2227         /* Default command arguments. */
2228         const char **argv;
2229         /* Open and reads in all view content. */
2230         bool (*open)(struct view *view);
2231         /* Read one line; updates view->line. */
2232         bool (*read)(struct view *view, char *data);
2233         /* Draw one line; @lineno must be < view->height. */
2234         bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
2235         /* Depending on view handle a special requests. */
2236         enum request (*request)(struct view *view, enum request request, struct line *line);
2237         /* Search for regexp in a line. */
2238         bool (*grep)(struct view *view, struct line *line);
2239         /* Select line */
2240         void (*select)(struct view *view, struct line *line);
2241         /* Prepare view for loading */
2242         bool (*prepare)(struct view *view);
2243 };
2245 static struct view_ops blame_ops;
2246 static struct view_ops blob_ops;
2247 static struct view_ops diff_ops;
2248 static struct view_ops help_ops;
2249 static struct view_ops log_ops;
2250 static struct view_ops main_ops;
2251 static struct view_ops pager_ops;
2252 static struct view_ops stage_ops;
2253 static struct view_ops status_ops;
2254 static struct view_ops tree_ops;
2255 static struct view_ops branch_ops;
2257 #define VIEW_STR(type, name, env, ref, ops, map, git) \
2258         { type, name, #env, ref, ops, map, git }
2260 #define VIEW_(id, name, ops, git, ref) \
2261         VIEW_STR(VIEW_##id, name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
2263 static struct view views[] = {
2264         VIEW_(MAIN,   "main",   &main_ops,   TRUE,  ref_head),
2265         VIEW_(DIFF,   "diff",   &diff_ops,   TRUE,  ref_commit),
2266         VIEW_(LOG,    "log",    &log_ops,    TRUE,  ref_head),
2267         VIEW_(TREE,   "tree",   &tree_ops,   TRUE,  ref_commit),
2268         VIEW_(BLOB,   "blob",   &blob_ops,   TRUE,  ref_blob),
2269         VIEW_(BLAME,  "blame",  &blame_ops,  TRUE,  ref_commit),
2270         VIEW_(BRANCH, "branch", &branch_ops, TRUE,  ref_head),
2271         VIEW_(HELP,   "help",   &help_ops,   FALSE, ""),
2272         VIEW_(PAGER,  "pager",  &pager_ops,  FALSE, "stdin"),
2273         VIEW_(STATUS, "status", &status_ops, TRUE,  ""),
2274         VIEW_(STAGE,  "stage",  &stage_ops,  TRUE,  ""),
2275 };
2277 #define VIEW(req)       (&views[(req) - REQ_OFFSET - 1])
2279 #define foreach_view(view, i) \
2280         for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
2282 #define view_is_displayed(view) \
2283         (view == display[0] || view == display[1])
2285 static enum request
2286 view_request(struct view *view, enum request request)
2288         if (!view || !view->lines)
2289                 return request;
2290         return view->ops->request(view, request, &view->line[view->lineno]);
2294 /*
2295  * View drawing.
2296  */
2298 static inline void
2299 set_view_attr(struct view *view, enum line_type type)
2301         if (!view->curline->selected && view->curtype != type) {
2302                 (void) wattrset(view->win, get_line_attr(type));
2303                 wchgat(view->win, -1, 0, type, NULL);
2304                 view->curtype = type;
2305         }
2308 static int
2309 draw_chars(struct view *view, enum line_type type, const char *string,
2310            int max_len, bool use_tilde)
2312         static char out_buffer[BUFSIZ * 2];
2313         int len = 0;
2314         int col = 0;
2315         int trimmed = FALSE;
2316         size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2318         if (max_len <= 0)
2319                 return 0;
2321         len = utf8_length(&string, skip, &col, max_len, &trimmed, use_tilde, opt_tab_size);
2323         set_view_attr(view, type);
2324         if (len > 0) {
2325                 if (opt_iconv_out != ICONV_NONE) {
2326                         ICONV_CONST char *inbuf = (ICONV_CONST char *) string;
2327                         size_t inlen = len + 1;
2329                         char *outbuf = out_buffer;
2330                         size_t outlen = sizeof(out_buffer);
2332                         size_t ret;
2334                         ret = iconv(opt_iconv_out, &inbuf, &inlen, &outbuf, &outlen);
2335                         if (ret != (size_t) -1) {
2336                                 string = out_buffer;
2337                                 len = sizeof(out_buffer) - outlen;
2338                         }
2339                 }
2341                 waddnstr(view->win, string, len);
2342         }
2343         if (trimmed && use_tilde) {
2344                 set_view_attr(view, LINE_DELIMITER);
2345                 waddch(view->win, '~');
2346                 col++;
2347         }
2349         return col;
2352 static int
2353 draw_space(struct view *view, enum line_type type, int max, int spaces)
2355         static char space[] = "                    ";
2356         int col = 0;
2358         spaces = MIN(max, spaces);
2360         while (spaces > 0) {
2361                 int len = MIN(spaces, sizeof(space) - 1);
2363                 col += draw_chars(view, type, space, len, FALSE);
2364                 spaces -= len;
2365         }
2367         return col;
2370 static bool
2371 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
2373         view->col += draw_chars(view, type, string, view->width + view->yoffset - view->col, trim);
2374         return view->width + view->yoffset <= view->col;
2377 static bool
2378 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
2380         size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2381         int max = view->width + view->yoffset - view->col;
2382         int i;
2384         if (max < size)
2385                 size = max;
2387         set_view_attr(view, type);
2388         /* Using waddch() instead of waddnstr() ensures that
2389          * they'll be rendered correctly for the cursor line. */
2390         for (i = skip; i < size; i++)
2391                 waddch(view->win, graphic[i]);
2393         view->col += size;
2394         if (size < max && skip <= size)
2395                 waddch(view->win, ' ');
2396         view->col++;
2398         return view->width + view->yoffset <= view->col;
2401 static bool
2402 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
2404         int max = MIN(view->width + view->yoffset - view->col, len);
2405         int col;
2407         if (text)
2408                 col = draw_chars(view, type, text, max - 1, trim);
2409         else
2410                 col = draw_space(view, type, max - 1, max - 1);
2412         view->col += col;
2413         view->col += draw_space(view, LINE_DEFAULT, max - col, max - col);
2414         return view->width + view->yoffset <= view->col;
2417 static bool
2418 draw_date(struct view *view, struct time *time)
2420         const char *date = mkdate(time, opt_date);
2421         int cols = opt_date == DATE_SHORT ? DATE_SHORT_COLS : DATE_COLS;
2423         return draw_field(view, LINE_DATE, date, cols, FALSE);
2426 static bool
2427 draw_author(struct view *view, const char *author)
2429         bool trim = opt_author_cols == 0 || opt_author_cols > 5;
2430         bool abbreviate = opt_author == AUTHOR_ABBREVIATED || !trim;
2432         if (abbreviate && author)
2433                 author = get_author_initials(author);
2435         return draw_field(view, LINE_AUTHOR, author, opt_author_cols, trim);
2438 static bool
2439 draw_mode(struct view *view, mode_t mode)
2441         const char *str;
2443         if (S_ISDIR(mode))
2444                 str = "drwxr-xr-x";
2445         else if (S_ISLNK(mode))
2446                 str = "lrwxrwxrwx";
2447         else if (S_ISGITLINK(mode))
2448                 str = "m---------";
2449         else if (S_ISREG(mode) && mode & S_IXUSR)
2450                 str = "-rwxr-xr-x";
2451         else if (S_ISREG(mode))
2452                 str = "-rw-r--r--";
2453         else
2454                 str = "----------";
2456         return draw_field(view, LINE_MODE, str, STRING_SIZE("-rw-r--r-- "), FALSE);
2459 static bool
2460 draw_lineno(struct view *view, unsigned int lineno)
2462         char number[10];
2463         int digits3 = view->digits < 3 ? 3 : view->digits;
2464         int max = MIN(view->width + view->yoffset - view->col, digits3);
2465         char *text = NULL;
2466         chtype separator = opt_line_graphics ? ACS_VLINE : '|';
2468         lineno += view->offset + 1;
2469         if (lineno == 1 || (lineno % opt_num_interval) == 0) {
2470                 static char fmt[] = "%1ld";
2472                 fmt[1] = '0' + (view->digits <= 9 ? digits3 : 1);
2473                 if (string_format(number, fmt, lineno))
2474                         text = number;
2475         }
2476         if (text)
2477                 view->col += draw_chars(view, LINE_LINE_NUMBER, text, max, TRUE);
2478         else
2479                 view->col += draw_space(view, LINE_LINE_NUMBER, max, digits3);
2480         return draw_graphic(view, LINE_DEFAULT, &separator, 1);
2483 static bool
2484 draw_view_line(struct view *view, unsigned int lineno)
2486         struct line *line;
2487         bool selected = (view->offset + lineno == view->lineno);
2489         assert(view_is_displayed(view));
2491         if (view->offset + lineno >= view->lines)
2492                 return FALSE;
2494         line = &view->line[view->offset + lineno];
2496         wmove(view->win, lineno, 0);
2497         if (line->cleareol)
2498                 wclrtoeol(view->win);
2499         view->col = 0;
2500         view->curline = line;
2501         view->curtype = LINE_NONE;
2502         line->selected = FALSE;
2503         line->dirty = line->cleareol = 0;
2505         if (selected) {
2506                 set_view_attr(view, LINE_CURSOR);
2507                 line->selected = TRUE;
2508                 view->ops->select(view, line);
2509         }
2511         return view->ops->draw(view, line, lineno);
2514 static void
2515 redraw_view_dirty(struct view *view)
2517         bool dirty = FALSE;
2518         int lineno;
2520         for (lineno = 0; lineno < view->height; lineno++) {
2521                 if (view->offset + lineno >= view->lines)
2522                         break;
2523                 if (!view->line[view->offset + lineno].dirty)
2524                         continue;
2525                 dirty = TRUE;
2526                 if (!draw_view_line(view, lineno))
2527                         break;
2528         }
2530         if (!dirty)
2531                 return;
2532         wnoutrefresh(view->win);
2535 static void
2536 redraw_view_from(struct view *view, int lineno)
2538         assert(0 <= lineno && lineno < view->height);
2540         for (; lineno < view->height; lineno++) {
2541                 if (!draw_view_line(view, lineno))
2542                         break;
2543         }
2545         wnoutrefresh(view->win);
2548 static void
2549 redraw_view(struct view *view)
2551         werase(view->win);
2552         redraw_view_from(view, 0);
2556 static void
2557 update_view_title(struct view *view)
2559         char buf[SIZEOF_STR];
2560         char state[SIZEOF_STR];
2561         size_t bufpos = 0, statelen = 0;
2563         assert(view_is_displayed(view));
2565         if (view->type != VIEW_STATUS && view->lines) {
2566                 unsigned int view_lines = view->offset + view->height;
2567                 unsigned int lines = view->lines
2568                                    ? MIN(view_lines, view->lines) * 100 / view->lines
2569                                    : 0;
2571                 string_format_from(state, &statelen, " - %s %d of %d (%d%%)",
2572                                    view->ops->type,
2573                                    view->lineno + 1,
2574                                    view->lines,
2575                                    lines);
2577         }
2579         if (view->pipe) {
2580                 time_t secs = time(NULL) - view->start_time;
2582                 /* Three git seconds are a long time ... */
2583                 if (secs > 2)
2584                         string_format_from(state, &statelen, " loading %lds", secs);
2585         }
2587         string_format_from(buf, &bufpos, "[%s]", view->name);
2588         if (*view->ref && bufpos < view->width) {
2589                 size_t refsize = strlen(view->ref);
2590                 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2592                 if (minsize < view->width)
2593                         refsize = view->width - minsize + 7;
2594                 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2595         }
2597         if (statelen && bufpos < view->width) {
2598                 string_format_from(buf, &bufpos, "%s", state);
2599         }
2601         if (view == display[current_view])
2602                 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2603         else
2604                 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2606         mvwaddnstr(view->title, 0, 0, buf, bufpos);
2607         wclrtoeol(view->title);
2608         wnoutrefresh(view->title);
2611 static int
2612 apply_step(double step, int value)
2614         if (step >= 1)
2615                 return (int) step;
2616         value *= step + 0.01;
2617         return value ? value : 1;
2620 static void
2621 resize_display(void)
2623         int offset, i;
2624         struct view *base = display[0];
2625         struct view *view = display[1] ? display[1] : display[0];
2627         /* Setup window dimensions */
2629         getmaxyx(stdscr, base->height, base->width);
2631         /* Make room for the status window. */
2632         base->height -= 1;
2634         if (view != base) {
2635                 /* Horizontal split. */
2636                 view->width   = base->width;
2637                 view->height  = apply_step(opt_scale_split_view, base->height);
2638                 view->height  = MAX(view->height, MIN_VIEW_HEIGHT);
2639                 view->height  = MIN(view->height, base->height - MIN_VIEW_HEIGHT);
2640                 base->height -= view->height;
2642                 /* Make room for the title bar. */
2643                 view->height -= 1;
2644         }
2646         /* Make room for the title bar. */
2647         base->height -= 1;
2649         offset = 0;
2651         foreach_displayed_view (view, i) {
2652                 if (!view->win) {
2653                         view->win = newwin(view->height, 0, offset, 0);
2654                         if (!view->win)
2655                                 die("Failed to create %s view", view->name);
2657                         scrollok(view->win, FALSE);
2659                         view->title = newwin(1, 0, offset + view->height, 0);
2660                         if (!view->title)
2661                                 die("Failed to create title window");
2663                 } else {
2664                         wresize(view->win, view->height, view->width);
2665                         mvwin(view->win,   offset, 0);
2666                         mvwin(view->title, offset + view->height, 0);
2667                 }
2669                 offset += view->height + 1;
2670         }
2673 static void
2674 redraw_display(bool clear)
2676         struct view *view;
2677         int i;
2679         foreach_displayed_view (view, i) {
2680                 if (clear)
2681                         wclear(view->win);
2682                 redraw_view(view);
2683                 update_view_title(view);
2684         }
2688 /*
2689  * Option management
2690  */
2692 static void
2693 toggle_enum_option_do(unsigned int *opt, const char *help,
2694                       const struct enum_map *map, size_t size)
2696         *opt = (*opt + 1) % size;
2697         redraw_display(FALSE);
2698         report("Displaying %s %s", enum_name(map[*opt]), help);
2701 #define toggle_enum_option(opt, help, map) \
2702         toggle_enum_option_do(opt, help, map, ARRAY_SIZE(map))
2704 #define toggle_date() toggle_enum_option(&opt_date, "dates", date_map)
2705 #define toggle_author() toggle_enum_option(&opt_author, "author names", author_map)
2707 static void
2708 toggle_view_option(bool *option, const char *help)
2710         *option = !*option;
2711         redraw_display(FALSE);
2712         report("%sabling %s", *option ? "En" : "Dis", help);
2715 static void
2716 open_option_menu(void)
2718         const struct menu_item menu[] = {
2719                 { '.', "line numbers", &opt_line_number },
2720                 { 'D', "date display", &opt_date },
2721                 { 'A', "author display", &opt_author },
2722                 { 'g', "revision graph display", &opt_rev_graph },
2723                 { 'F', "reference display", &opt_show_refs },
2724                 { 0 }
2725         };
2726         int selected = 0;
2728         if (prompt_menu("Toggle option", menu, &selected)) {
2729                 if (menu[selected].data == &opt_date)
2730                         toggle_date();
2731                 else if (menu[selected].data == &opt_author)
2732                         toggle_author();
2733                 else
2734                         toggle_view_option(menu[selected].data, menu[selected].text);
2735         }
2738 static void
2739 maximize_view(struct view *view)
2741         memset(display, 0, sizeof(display));
2742         current_view = 0;
2743         display[current_view] = view;
2744         resize_display();
2745         redraw_display(FALSE);
2746         report("");
2750 /*
2751  * Navigation
2752  */
2754 static bool
2755 goto_view_line(struct view *view, unsigned long offset, unsigned long lineno)
2757         if (lineno >= view->lines)
2758                 lineno = view->lines > 0 ? view->lines - 1 : 0;
2760         if (offset > lineno || offset + view->height <= lineno) {
2761                 unsigned long half = view->height / 2;
2763                 if (lineno > half)
2764                         offset = lineno - half;
2765                 else
2766                         offset = 0;
2767         }
2769         if (offset != view->offset || lineno != view->lineno) {
2770                 view->offset = offset;
2771                 view->lineno = lineno;
2772                 return TRUE;
2773         }
2775         return FALSE;
2778 /* Scrolling backend */
2779 static void
2780 do_scroll_view(struct view *view, int lines)
2782         bool redraw_current_line = FALSE;
2784         /* The rendering expects the new offset. */
2785         view->offset += lines;
2787         assert(0 <= view->offset && view->offset < view->lines);
2788         assert(lines);
2790         /* Move current line into the view. */
2791         if (view->lineno < view->offset) {
2792                 view->lineno = view->offset;
2793                 redraw_current_line = TRUE;
2794         } else if (view->lineno >= view->offset + view->height) {
2795                 view->lineno = view->offset + view->height - 1;
2796                 redraw_current_line = TRUE;
2797         }
2799         assert(view->offset <= view->lineno && view->lineno < view->lines);
2801         /* Redraw the whole screen if scrolling is pointless. */
2802         if (view->height < ABS(lines)) {
2803                 redraw_view(view);
2805         } else {
2806                 int line = lines > 0 ? view->height - lines : 0;
2807                 int end = line + ABS(lines);
2809                 scrollok(view->win, TRUE);
2810                 wscrl(view->win, lines);
2811                 scrollok(view->win, FALSE);
2813                 while (line < end && draw_view_line(view, line))
2814                         line++;
2816                 if (redraw_current_line)
2817                         draw_view_line(view, view->lineno - view->offset);
2818                 wnoutrefresh(view->win);
2819         }
2821         view->has_scrolled = TRUE;
2822         report("");
2825 /* Scroll frontend */
2826 static void
2827 scroll_view(struct view *view, enum request request)
2829         int lines = 1;
2831         assert(view_is_displayed(view));
2833         switch (request) {
2834         case REQ_SCROLL_LEFT:
2835                 if (view->yoffset == 0) {
2836                         report("Cannot scroll beyond the first column");
2837                         return;
2838                 }
2839                 if (view->yoffset <= apply_step(opt_hscroll, view->width))
2840                         view->yoffset = 0;
2841                 else
2842                         view->yoffset -= apply_step(opt_hscroll, view->width);
2843                 redraw_view_from(view, 0);
2844                 report("");
2845                 return;
2846         case REQ_SCROLL_RIGHT:
2847                 view->yoffset += apply_step(opt_hscroll, view->width);
2848                 redraw_view(view);
2849                 report("");
2850                 return;
2851         case REQ_SCROLL_PAGE_DOWN:
2852                 lines = view->height;
2853         case REQ_SCROLL_LINE_DOWN:
2854                 if (view->offset + lines > view->lines)
2855                         lines = view->lines - view->offset;
2857                 if (lines == 0 || view->offset + view->height >= view->lines) {
2858                         report("Cannot scroll beyond the last line");
2859                         return;
2860                 }
2861                 break;
2863         case REQ_SCROLL_PAGE_UP:
2864                 lines = view->height;
2865         case REQ_SCROLL_LINE_UP:
2866                 if (lines > view->offset)
2867                         lines = view->offset;
2869                 if (lines == 0) {
2870                         report("Cannot scroll beyond the first line");
2871                         return;
2872                 }
2874                 lines = -lines;
2875                 break;
2877         default:
2878                 die("request %d not handled in switch", request);
2879         }
2881         do_scroll_view(view, lines);
2884 /* Cursor moving */
2885 static void
2886 move_view(struct view *view, enum request request)
2888         int scroll_steps = 0;
2889         int steps;
2891         switch (request) {
2892         case REQ_MOVE_FIRST_LINE:
2893                 steps = -view->lineno;
2894                 break;
2896         case REQ_MOVE_LAST_LINE:
2897                 steps = view->lines - view->lineno - 1;
2898                 break;
2900         case REQ_MOVE_PAGE_UP:
2901                 steps = view->height > view->lineno
2902                       ? -view->lineno : -view->height;
2903                 break;
2905         case REQ_MOVE_PAGE_DOWN:
2906                 steps = view->lineno + view->height >= view->lines
2907                       ? view->lines - view->lineno - 1 : view->height;
2908                 break;
2910         case REQ_MOVE_UP:
2911                 steps = -1;
2912                 break;
2914         case REQ_MOVE_DOWN:
2915                 steps = 1;
2916                 break;
2918         default:
2919                 die("request %d not handled in switch", request);
2920         }
2922         if (steps <= 0 && view->lineno == 0) {
2923                 report("Cannot move beyond the first line");
2924                 return;
2926         } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2927                 report("Cannot move beyond the last line");
2928                 return;
2929         }
2931         /* Move the current line */
2932         view->lineno += steps;
2933         assert(0 <= view->lineno && view->lineno < view->lines);
2935         /* Check whether the view needs to be scrolled */
2936         if (view->lineno < view->offset ||
2937             view->lineno >= view->offset + view->height) {
2938                 scroll_steps = steps;
2939                 if (steps < 0 && -steps > view->offset) {
2940                         scroll_steps = -view->offset;
2942                 } else if (steps > 0) {
2943                         if (view->lineno == view->lines - 1 &&
2944                             view->lines > view->height) {
2945                                 scroll_steps = view->lines - view->offset - 1;
2946                                 if (scroll_steps >= view->height)
2947                                         scroll_steps -= view->height - 1;
2948                         }
2949                 }
2950         }
2952         if (!view_is_displayed(view)) {
2953                 view->offset += scroll_steps;
2954                 assert(0 <= view->offset && view->offset < view->lines);
2955                 view->ops->select(view, &view->line[view->lineno]);
2956                 return;
2957         }
2959         /* Repaint the old "current" line if we be scrolling */
2960         if (ABS(steps) < view->height)
2961                 draw_view_line(view, view->lineno - steps - view->offset);
2963         if (scroll_steps) {
2964                 do_scroll_view(view, scroll_steps);
2965                 return;
2966         }
2968         /* Draw the current line */
2969         draw_view_line(view, view->lineno - view->offset);
2971         wnoutrefresh(view->win);
2972         report("");
2976 /*
2977  * Searching
2978  */
2980 static void search_view(struct view *view, enum request request);
2982 static bool
2983 grep_text(struct view *view, const char *text[])
2985         regmatch_t pmatch;
2986         size_t i;
2988         for (i = 0; text[i]; i++)
2989                 if (*text[i] &&
2990                     regexec(view->regex, text[i], 1, &pmatch, 0) != REG_NOMATCH)
2991                         return TRUE;
2992         return FALSE;
2995 static void
2996 select_view_line(struct view *view, unsigned long lineno)
2998         unsigned long old_lineno = view->lineno;
2999         unsigned long old_offset = view->offset;
3001         if (goto_view_line(view, view->offset, lineno)) {
3002                 if (view_is_displayed(view)) {
3003                         if (old_offset != view->offset) {
3004                                 redraw_view(view);
3005                         } else {
3006                                 draw_view_line(view, old_lineno - view->offset);
3007                                 draw_view_line(view, view->lineno - view->offset);
3008                                 wnoutrefresh(view->win);
3009                         }
3010                 } else {
3011                         view->ops->select(view, &view->line[view->lineno]);
3012                 }
3013         }
3016 static void
3017 find_next(struct view *view, enum request request)
3019         unsigned long lineno = view->lineno;
3020         int direction;
3022         if (!*view->grep) {
3023                 if (!*opt_search)
3024                         report("No previous search");
3025                 else
3026                         search_view(view, request);
3027                 return;
3028         }
3030         switch (request) {
3031         case REQ_SEARCH:
3032         case REQ_FIND_NEXT:
3033                 direction = 1;
3034                 break;
3036         case REQ_SEARCH_BACK:
3037         case REQ_FIND_PREV:
3038                 direction = -1;
3039                 break;
3041         default:
3042                 return;
3043         }
3045         if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
3046                 lineno += direction;
3048         /* Note, lineno is unsigned long so will wrap around in which case it
3049          * will become bigger than view->lines. */
3050         for (; lineno < view->lines; lineno += direction) {
3051                 if (view->ops->grep(view, &view->line[lineno])) {
3052                         select_view_line(view, lineno);
3053                         report("Line %ld matches '%s'", lineno + 1, view->grep);
3054                         return;
3055                 }
3056         }
3058         report("No match found for '%s'", view->grep);
3061 static void
3062 search_view(struct view *view, enum request request)
3064         int regex_err;
3066         if (view->regex) {
3067                 regfree(view->regex);
3068                 *view->grep = 0;
3069         } else {
3070                 view->regex = calloc(1, sizeof(*view->regex));
3071                 if (!view->regex)
3072                         return;
3073         }
3075         regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
3076         if (regex_err != 0) {
3077                 char buf[SIZEOF_STR] = "unknown error";
3079                 regerror(regex_err, view->regex, buf, sizeof(buf));
3080                 report("Search failed: %s", buf);
3081                 return;
3082         }
3084         string_copy(view->grep, opt_search);
3086         find_next(view, request);
3089 /*
3090  * Incremental updating
3091  */
3093 static void
3094 reset_view(struct view *view)
3096         int i;
3098         for (i = 0; i < view->lines; i++)
3099                 free(view->line[i].data);
3100         free(view->line);
3102         view->p_offset = view->offset;
3103         view->p_yoffset = view->yoffset;
3104         view->p_lineno = view->lineno;
3106         view->line = NULL;
3107         view->offset = 0;
3108         view->yoffset = 0;
3109         view->lines  = 0;
3110         view->lineno = 0;
3111         view->vid[0] = 0;
3112         view->update_secs = 0;
3115 static const char *
3116 format_arg(const char *name)
3118         static struct {
3119                 const char *name;
3120                 size_t namelen;
3121                 const char *value;
3122                 const char *value_if_empty;
3123         } vars[] = {
3124 #define FORMAT_VAR(name, value, value_if_empty) \
3125         { name, STRING_SIZE(name), value, value_if_empty }
3126                 FORMAT_VAR("%(directory)",      opt_path,       ""),
3127                 FORMAT_VAR("%(file)",           opt_file,       ""),
3128                 FORMAT_VAR("%(ref)",            opt_ref,        "HEAD"),
3129                 FORMAT_VAR("%(head)",           ref_head,       ""),
3130                 FORMAT_VAR("%(commit)",         ref_commit,     ""),
3131                 FORMAT_VAR("%(blob)",           ref_blob,       ""),
3132                 FORMAT_VAR("%(branch)",         ref_branch,     ""),
3133         };
3134         int i;
3136         for (i = 0; i < ARRAY_SIZE(vars); i++)
3137                 if (!strncmp(name, vars[i].name, vars[i].namelen))
3138                         return *vars[i].value ? vars[i].value : vars[i].value_if_empty;
3140         report("Unknown replacement: `%s`", name);
3141         return NULL;
3144 static bool
3145 format_argv(const char *dst_argv[], const char *src_argv[], bool replace)
3147         char buf[SIZEOF_STR];
3148         int argc;
3150         argv_free(dst_argv);
3152         for (argc = 0; src_argv[argc]; argc++) {
3153                 const char *arg = src_argv[argc];
3154                 size_t bufpos = 0;
3156                 while (arg) {
3157                         char *next = strstr(arg, "%(");
3158                         int len = next - arg;
3159                         const char *value;
3161                         if (!next || !replace) {
3162                                 len = strlen(arg);
3163                                 value = "";
3165                         } else {
3166                                 value = format_arg(next);
3168                                 if (!value) {
3169                                         return FALSE;
3170                                 }
3171                         }
3173                         if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
3174                                 return FALSE;
3176                         arg = next && replace ? strchr(next, ')') + 1 : NULL;
3177                 }
3179                 dst_argv[argc] = strdup(buf);
3180                 if (!dst_argv[argc])
3181                         break;
3182         }
3184         dst_argv[argc] = NULL;
3186         return src_argv[argc] == NULL;
3189 static bool
3190 restore_view_position(struct view *view)
3192         if (!view->p_restore || (view->pipe && view->lines <= view->p_lineno))
3193                 return FALSE;
3195         /* Changing the view position cancels the restoring. */
3196         /* FIXME: Changing back to the first line is not detected. */
3197         if (view->offset != 0 || view->lineno != 0) {
3198                 view->p_restore = FALSE;
3199                 return FALSE;
3200         }
3202         if (goto_view_line(view, view->p_offset, view->p_lineno) &&
3203             view_is_displayed(view))
3204                 werase(view->win);
3206         view->yoffset = view->p_yoffset;
3207         view->p_restore = FALSE;
3209         return TRUE;
3212 static void
3213 end_update(struct view *view, bool force)
3215         if (!view->pipe)
3216                 return;
3217         while (!view->ops->read(view, NULL))
3218                 if (!force)
3219                         return;
3220         if (force)
3221                 io_kill(view->pipe);
3222         io_done(view->pipe);
3223         view->pipe = NULL;
3226 static void
3227 setup_update(struct view *view, const char *vid)
3229         reset_view(view);
3230         string_copy_rev(view->vid, vid);
3231         view->pipe = &view->io;
3232         view->start_time = time(NULL);
3235 static bool
3236 prepare_io(struct view *view, const char *dir, const char *argv[], bool replace)
3238         view->dir = dir;
3239         return format_argv(view->argv, argv, replace);
3242 static bool
3243 prepare_update(struct view *view, const char *argv[], const char *dir)
3245         if (view->pipe)
3246                 end_update(view, TRUE);
3247         return prepare_io(view, dir, argv, FALSE);
3250 static bool
3251 start_update(struct view *view, const char **argv, const char *dir)
3253         if (view->pipe)
3254                 io_done(view->pipe);
3255         return prepare_io(view, dir, argv, FALSE) &&
3256                io_run(&view->io, IO_RD, dir, view->argv);
3259 static bool
3260 prepare_update_file(struct view *view, const char *name)
3262         if (view->pipe)
3263                 end_update(view, TRUE);
3264         argv_free(view->argv);
3265         return io_open(&view->io, "%s/%s", opt_cdup[0] ? opt_cdup : ".", name);
3268 static bool
3269 begin_update(struct view *view, bool refresh)
3271         if (view->pipe)
3272                 end_update(view, TRUE);
3274         if (!refresh) {
3275                 if (view->ops->prepare) {
3276                         if (!view->ops->prepare(view))
3277                                 return FALSE;
3278                 } else if (!prepare_io(view, NULL, view->ops->argv, TRUE)) {
3279                         return FALSE;
3280                 }
3282                 /* Put the current ref_* value to the view title ref
3283                  * member. This is needed by the blob view. Most other
3284                  * views sets it automatically after loading because the
3285                  * first line is a commit line. */
3286                 string_copy_rev(view->ref, view->id);
3287         }
3289         if (view->argv[0] && !io_run(&view->io, IO_RD, view->dir, view->argv))
3290                 return FALSE;
3292         setup_update(view, view->id);
3294         return TRUE;
3297 static bool
3298 update_view(struct view *view)
3300         char out_buffer[BUFSIZ * 2];
3301         char *line;
3302         /* Clear the view and redraw everything since the tree sorting
3303          * might have rearranged things. */
3304         bool redraw = view->lines == 0;
3305         bool can_read = TRUE;
3307         if (!view->pipe)
3308                 return TRUE;
3310         if (!io_can_read(view->pipe)) {
3311                 if (view->lines == 0 && view_is_displayed(view)) {
3312                         time_t secs = time(NULL) - view->start_time;
3314                         if (secs > 1 && secs > view->update_secs) {
3315                                 if (view->update_secs == 0)
3316                                         redraw_view(view);
3317                                 update_view_title(view);
3318                                 view->update_secs = secs;
3319                         }
3320                 }
3321                 return TRUE;
3322         }
3324         for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
3325                 if (opt_iconv_in != ICONV_NONE) {
3326                         ICONV_CONST char *inbuf = line;
3327                         size_t inlen = strlen(line) + 1;
3329                         char *outbuf = out_buffer;
3330                         size_t outlen = sizeof(out_buffer);
3332                         size_t ret;
3334                         ret = iconv(opt_iconv_in, &inbuf, &inlen, &outbuf, &outlen);
3335                         if (ret != (size_t) -1)
3336                                 line = out_buffer;
3337                 }
3339                 if (!view->ops->read(view, line)) {
3340                         report("Allocation failure");
3341                         end_update(view, TRUE);
3342                         return FALSE;
3343                 }
3344         }
3346         {
3347                 unsigned long lines = view->lines;
3348                 int digits;
3350                 for (digits = 0; lines; digits++)
3351                         lines /= 10;
3353                 /* Keep the displayed view in sync with line number scaling. */
3354                 if (digits != view->digits) {
3355                         view->digits = digits;
3356                         if (opt_line_number || view->type == VIEW_BLAME)
3357                                 redraw = TRUE;
3358                 }
3359         }
3361         if (io_error(view->pipe)) {
3362                 report("Failed to read: %s", io_strerror(view->pipe));
3363                 end_update(view, TRUE);
3365         } else if (io_eof(view->pipe)) {
3366                 if (view_is_displayed(view))
3367                         report("");
3368                 end_update(view, FALSE);
3369         }
3371         if (restore_view_position(view))
3372                 redraw = TRUE;
3374         if (!view_is_displayed(view))
3375                 return TRUE;
3377         if (redraw)
3378                 redraw_view_from(view, 0);
3379         else
3380                 redraw_view_dirty(view);
3382         /* Update the title _after_ the redraw so that if the redraw picks up a
3383          * commit reference in view->ref it'll be available here. */
3384         update_view_title(view);
3385         return TRUE;
3388 DEFINE_ALLOCATOR(realloc_lines, struct line, 256)
3390 static struct line *
3391 add_line_data(struct view *view, void *data, enum line_type type)
3393         struct line *line;
3395         if (!realloc_lines(&view->line, view->lines, 1))
3396                 return NULL;
3398         line = &view->line[view->lines++];
3399         memset(line, 0, sizeof(*line));
3400         line->type = type;
3401         line->data = data;
3402         line->dirty = 1;
3404         return line;
3407 static struct line *
3408 add_line_text(struct view *view, const char *text, enum line_type type)
3410         char *data = text ? strdup(text) : NULL;
3412         return data ? add_line_data(view, data, type) : NULL;
3415 static struct line *
3416 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
3418         char buf[SIZEOF_STR];
3419         va_list args;
3421         va_start(args, fmt);
3422         if (vsnprintf(buf, sizeof(buf), fmt, args) >= sizeof(buf))
3423                 buf[0] = 0;
3424         va_end(args);
3426         return buf[0] ? add_line_text(view, buf, type) : NULL;
3429 /*
3430  * View opening
3431  */
3433 enum open_flags {
3434         OPEN_DEFAULT = 0,       /* Use default view switching. */
3435         OPEN_SPLIT = 1,         /* Split current view. */
3436         OPEN_RELOAD = 4,        /* Reload view even if it is the current. */
3437         OPEN_REFRESH = 16,      /* Refresh view using previous command. */
3438         OPEN_PREPARED = 32,     /* Open already prepared command. */
3439 };
3441 static void
3442 open_view(struct view *prev, enum request request, enum open_flags flags)
3444         bool split = !!(flags & OPEN_SPLIT);
3445         bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
3446         bool nomaximize = !!(flags & OPEN_REFRESH);
3447         struct view *view = VIEW(request);
3448         int nviews = displayed_views();
3449         struct view *base_view = display[0];
3451         if (view == prev && nviews == 1 && !reload) {
3452                 report("Already in %s view", view->name);
3453                 return;
3454         }
3456         if (view->git_dir && !opt_git_dir[0]) {
3457                 report("The %s view is disabled in pager view", view->name);
3458                 return;
3459         }
3461         if (split) {
3462                 display[1] = view;
3463                 current_view = 1;
3464                 view->parent = prev;
3465         } else if (!nomaximize) {
3466                 /* Maximize the current view. */
3467                 memset(display, 0, sizeof(display));
3468                 current_view = 0;
3469                 display[current_view] = view;
3470         }
3472         /* No prev signals that this is the first loaded view. */
3473         if (prev && view != prev) {
3474                 view->prev = prev;
3475         }
3477         /* Resize the view when switching between split- and full-screen,
3478          * or when switching between two different full-screen views. */
3479         if (nviews != displayed_views() ||
3480             (nviews == 1 && base_view != display[0]))
3481                 resize_display();
3483         if (view->ops->open) {
3484                 if (view->pipe)
3485                         end_update(view, TRUE);
3486                 if (!view->ops->open(view)) {
3487                         report("Failed to load %s view", view->name);
3488                         return;
3489                 }
3490                 restore_view_position(view);
3492         } else if ((reload || strcmp(view->vid, view->id)) &&
3493                    !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
3494                 report("Failed to load %s view", view->name);
3495                 return;
3496         }
3498         if (split && prev->lineno - prev->offset >= prev->height) {
3499                 /* Take the title line into account. */
3500                 int lines = prev->lineno - prev->offset - prev->height + 1;
3502                 /* Scroll the view that was split if the current line is
3503                  * outside the new limited view. */
3504                 do_scroll_view(prev, lines);
3505         }
3507         if (prev && view != prev && split && view_is_displayed(prev)) {
3508                 /* "Blur" the previous view. */
3509                 update_view_title(prev);
3510         }
3512         if (view->pipe && view->lines == 0) {
3513                 /* Clear the old view and let the incremental updating refill
3514                  * the screen. */
3515                 werase(view->win);
3516                 view->p_restore = flags & (OPEN_RELOAD | OPEN_REFRESH);
3517                 report("");
3518         } else if (view_is_displayed(view)) {
3519                 redraw_view(view);
3520                 report("");
3521         }
3524 static void
3525 open_external_viewer(const char *argv[], const char *dir)
3527         def_prog_mode();           /* save current tty modes */
3528         endwin();                  /* restore original tty modes */
3529         io_run_fg(argv, dir);
3530         fprintf(stderr, "Press Enter to continue");
3531         getc(opt_tty);
3532         reset_prog_mode();
3533         redraw_display(TRUE);
3536 static void
3537 open_mergetool(const char *file)
3539         const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
3541         open_external_viewer(mergetool_argv, opt_cdup);
3544 static void
3545 open_editor(const char *file)
3547         const char *editor_argv[] = { "vi", file, NULL };
3548         const char *editor;
3550         editor = getenv("GIT_EDITOR");
3551         if (!editor && *opt_editor)
3552                 editor = opt_editor;
3553         if (!editor)
3554                 editor = getenv("VISUAL");
3555         if (!editor)
3556                 editor = getenv("EDITOR");
3557         if (!editor)
3558                 editor = "vi";
3560         editor_argv[0] = editor;
3561         open_external_viewer(editor_argv, opt_cdup);
3564 static void
3565 open_run_request(enum request request)
3567         struct run_request *req = get_run_request(request);
3568         const char *argv[ARRAY_SIZE(req->argv)] = { NULL };
3570         if (!req) {
3571                 report("Unknown run request");
3572                 return;
3573         }
3575         if (format_argv(argv, req->argv, TRUE))
3576                 open_external_viewer(argv, NULL);
3577         argv_free(argv);
3580 /*
3581  * User request switch noodle
3582  */
3584 static int
3585 view_driver(struct view *view, enum request request)
3587         int i;
3589         if (request == REQ_NONE)
3590                 return TRUE;
3592         if (request > REQ_NONE) {
3593                 open_run_request(request);
3594                 view_request(view, REQ_REFRESH);
3595                 return TRUE;
3596         }
3598         request = view_request(view, request);
3599         if (request == REQ_NONE)
3600                 return TRUE;
3602         switch (request) {
3603         case REQ_MOVE_UP:
3604         case REQ_MOVE_DOWN:
3605         case REQ_MOVE_PAGE_UP:
3606         case REQ_MOVE_PAGE_DOWN:
3607         case REQ_MOVE_FIRST_LINE:
3608         case REQ_MOVE_LAST_LINE:
3609                 move_view(view, request);
3610                 break;
3612         case REQ_SCROLL_LEFT:
3613         case REQ_SCROLL_RIGHT:
3614         case REQ_SCROLL_LINE_DOWN:
3615         case REQ_SCROLL_LINE_UP:
3616         case REQ_SCROLL_PAGE_DOWN:
3617         case REQ_SCROLL_PAGE_UP:
3618                 scroll_view(view, request);
3619                 break;
3621         case REQ_VIEW_BLAME:
3622                 if (!opt_file[0]) {
3623                         report("No file chosen, press %s to open tree view",
3624                                get_key(view->keymap, REQ_VIEW_TREE));
3625                         break;
3626                 }
3627                 open_view(view, request, OPEN_DEFAULT);
3628                 break;
3630         case REQ_VIEW_BLOB:
3631                 if (!ref_blob[0]) {
3632                         report("No file chosen, press %s to open tree view",
3633                                get_key(view->keymap, REQ_VIEW_TREE));
3634                         break;
3635                 }
3636                 open_view(view, request, OPEN_DEFAULT);
3637                 break;
3639         case REQ_VIEW_PAGER:
3640                 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3641                         report("No pager content, press %s to run command from prompt",
3642                                get_key(view->keymap, REQ_PROMPT));
3643                         break;
3644                 }
3645                 open_view(view, request, OPEN_DEFAULT);
3646                 break;
3648         case REQ_VIEW_STAGE:
3649                 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3650                         report("No stage content, press %s to open the status view and choose file",
3651                                get_key(view->keymap, REQ_VIEW_STATUS));
3652                         break;
3653                 }
3654                 open_view(view, request, OPEN_DEFAULT);
3655                 break;
3657         case REQ_VIEW_STATUS:
3658                 if (opt_is_inside_work_tree == FALSE) {
3659                         report("The status view requires a working tree");
3660                         break;
3661                 }
3662                 open_view(view, request, OPEN_DEFAULT);
3663                 break;
3665         case REQ_VIEW_MAIN:
3666         case REQ_VIEW_DIFF:
3667         case REQ_VIEW_LOG:
3668         case REQ_VIEW_TREE:
3669         case REQ_VIEW_HELP:
3670         case REQ_VIEW_BRANCH:
3671                 open_view(view, request, OPEN_DEFAULT);
3672                 break;
3674         case REQ_NEXT:
3675         case REQ_PREVIOUS:
3676                 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3678                 if (view->parent) {
3679                         int line;
3681                         view = view->parent;
3682                         line = view->lineno;
3683                         move_view(view, request);
3684                         if (view_is_displayed(view))
3685                                 update_view_title(view);
3686                         if (line != view->lineno)
3687                                 view_request(view, REQ_ENTER);
3688                 } else {
3689                         move_view(view, request);
3690                 }
3691                 break;
3693         case REQ_VIEW_NEXT:
3694         {
3695                 int nviews = displayed_views();
3696                 int next_view = (current_view + 1) % nviews;
3698                 if (next_view == current_view) {
3699                         report("Only one view is displayed");
3700                         break;
3701                 }
3703                 current_view = next_view;
3704                 /* Blur out the title of the previous view. */
3705                 update_view_title(view);
3706                 report("");
3707                 break;
3708         }
3709         case REQ_REFRESH:
3710                 report("Refreshing is not yet supported for the %s view", view->name);
3711                 break;
3713         case REQ_MAXIMIZE:
3714                 if (displayed_views() == 2)
3715                         maximize_view(view);
3716                 break;
3718         case REQ_OPTIONS:
3719                 open_option_menu();
3720                 break;
3722         case REQ_TOGGLE_LINENO:
3723                 toggle_view_option(&opt_line_number, "line numbers");
3724                 break;
3726         case REQ_TOGGLE_DATE:
3727                 toggle_date();
3728                 break;
3730         case REQ_TOGGLE_AUTHOR:
3731                 toggle_author();
3732                 break;
3734         case REQ_TOGGLE_REV_GRAPH:
3735                 toggle_view_option(&opt_rev_graph, "revision graph display");
3736                 break;
3738         case REQ_TOGGLE_REFS:
3739                 toggle_view_option(&opt_show_refs, "reference display");
3740                 break;
3742         case REQ_TOGGLE_SORT_FIELD:
3743         case REQ_TOGGLE_SORT_ORDER:
3744                 report("Sorting is not yet supported for the %s view", view->name);
3745                 break;
3747         case REQ_SEARCH:
3748         case REQ_SEARCH_BACK:
3749                 search_view(view, request);
3750                 break;
3752         case REQ_FIND_NEXT:
3753         case REQ_FIND_PREV:
3754                 find_next(view, request);
3755                 break;
3757         case REQ_STOP_LOADING:
3758                 foreach_view(view, i) {
3759                         if (view->pipe)
3760                                 report("Stopped loading the %s view", view->name),
3761                         end_update(view, TRUE);
3762                 }
3763                 break;
3765         case REQ_SHOW_VERSION:
3766                 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3767                 return TRUE;
3769         case REQ_SCREEN_REDRAW:
3770                 redraw_display(TRUE);
3771                 break;
3773         case REQ_EDIT:
3774                 report("Nothing to edit");
3775                 break;
3777         case REQ_ENTER:
3778                 report("Nothing to enter");
3779                 break;
3781         case REQ_VIEW_CLOSE:
3782                 /* XXX: Mark closed views by letting view->prev point to the
3783                  * view itself. Parents to closed view should never be
3784                  * followed. */
3785                 if (view->prev && view->prev != view) {
3786                         maximize_view(view->prev);
3787                         view->prev = view;
3788                         break;
3789                 }
3790                 /* Fall-through */
3791         case REQ_QUIT:
3792                 return FALSE;
3794         default:
3795                 report("Unknown key, press %s for help",
3796                        get_key(view->keymap, REQ_VIEW_HELP));
3797                 return TRUE;
3798         }
3800         return TRUE;
3804 /*
3805  * View backend utilities
3806  */
3808 enum sort_field {
3809         ORDERBY_NAME,
3810         ORDERBY_DATE,
3811         ORDERBY_AUTHOR,
3812 };
3814 struct sort_state {
3815         const enum sort_field *fields;
3816         size_t size, current;
3817         bool reverse;
3818 };
3820 #define SORT_STATE(fields) { fields, ARRAY_SIZE(fields), 0 }
3821 #define get_sort_field(state) ((state).fields[(state).current])
3822 #define sort_order(state, result) ((state).reverse ? -(result) : (result))
3824 static void
3825 sort_view(struct view *view, enum request request, struct sort_state *state,
3826           int (*compare)(const void *, const void *))
3828         switch (request) {
3829         case REQ_TOGGLE_SORT_FIELD:
3830                 state->current = (state->current + 1) % state->size;
3831                 break;
3833         case REQ_TOGGLE_SORT_ORDER:
3834                 state->reverse = !state->reverse;
3835                 break;
3836         default:
3837                 die("Not a sort request");
3838         }
3840         qsort(view->line, view->lines, sizeof(*view->line), compare);
3841         redraw_view(view);
3844 DEFINE_ALLOCATOR(realloc_authors, const char *, 256)
3846 /* Small author cache to reduce memory consumption. It uses binary
3847  * search to lookup or find place to position new entries. No entries
3848  * are ever freed. */
3849 static const char *
3850 get_author(const char *name)
3852         static const char **authors;
3853         static size_t authors_size;
3854         int from = 0, to = authors_size - 1;
3856         while (from <= to) {
3857                 size_t pos = (to + from) / 2;
3858                 int cmp = strcmp(name, authors[pos]);
3860                 if (!cmp)
3861                         return authors[pos];
3863                 if (cmp < 0)
3864                         to = pos - 1;
3865                 else
3866                         from = pos + 1;
3867         }
3869         if (!realloc_authors(&authors, authors_size, 1))
3870                 return NULL;
3871         name = strdup(name);
3872         if (!name)
3873                 return NULL;
3875         memmove(authors + from + 1, authors + from, (authors_size - from) * sizeof(*authors));
3876         authors[from] = name;
3877         authors_size++;
3879         return name;
3882 static void
3883 parse_timesec(struct time *time, const char *sec)
3885         time->sec = (time_t) atol(sec);
3888 static void
3889 parse_timezone(struct time *time, const char *zone)
3891         long tz;
3893         tz  = ('0' - zone[1]) * 60 * 60 * 10;
3894         tz += ('0' - zone[2]) * 60 * 60;
3895         tz += ('0' - zone[3]) * 60 * 10;
3896         tz += ('0' - zone[4]) * 60;
3898         if (zone[0] == '-')
3899                 tz = -tz;
3901         time->tz = tz;
3902         time->sec -= tz;
3905 /* Parse author lines where the name may be empty:
3906  *      author  <email@address.tld> 1138474660 +0100
3907  */
3908 static void
3909 parse_author_line(char *ident, const char **author, struct time *time)
3911         char *nameend = strchr(ident, '<');
3912         char *emailend = strchr(ident, '>');
3914         if (nameend && emailend)
3915                 *nameend = *emailend = 0;
3916         ident = chomp_string(ident);
3917         if (!*ident) {
3918                 if (nameend)
3919                         ident = chomp_string(nameend + 1);
3920                 if (!*ident)
3921                         ident = "Unknown";
3922         }
3924         *author = get_author(ident);
3926         /* Parse epoch and timezone */
3927         if (emailend && emailend[1] == ' ') {
3928                 char *secs = emailend + 2;
3929                 char *zone = strchr(secs, ' ');
3931                 parse_timesec(time, secs);
3933                 if (zone && strlen(zone) == STRING_SIZE(" +0700"))
3934                         parse_timezone(time, zone + 1);
3935         }
3938 static bool
3939 open_commit_parent_menu(char buf[SIZEOF_STR], int *parents)
3941         char rev[SIZEOF_REV];
3942         const char *revlist_argv[] = {
3943                 "git", "log", "--no-color", "-1", "--pretty=format:%s", rev, NULL
3944         };
3945         struct menu_item *items;
3946         char text[SIZEOF_STR];
3947         bool ok = TRUE;
3948         int i;
3950         items = calloc(*parents + 1, sizeof(*items));
3951         if (!items)
3952                 return FALSE;
3954         for (i = 0; i < *parents; i++) {
3955                 string_copy_rev(rev, &buf[SIZEOF_REV * i]);
3956                 if (!io_run_buf(revlist_argv, text, sizeof(text)) ||
3957                     !(items[i].text = strdup(text))) {
3958                         ok = FALSE;
3959                         break;
3960                 }
3961         }
3963         if (ok) {
3964                 *parents = 0;
3965                 ok = prompt_menu("Select parent", items, parents);
3966         }
3967         for (i = 0; items[i].text; i++)
3968                 free((char *) items[i].text);
3969         free(items);
3970         return ok;
3973 static bool
3974 select_commit_parent(const char *id, char rev[SIZEOF_REV], const char *path)
3976         char buf[SIZEOF_STR * 4];
3977         const char *revlist_argv[] = {
3978                 "git", "log", "--no-color", "-1",
3979                         "--pretty=format:%P", id, "--", path, NULL
3980         };
3981         int parents;
3983         if (!io_run_buf(revlist_argv, buf, sizeof(buf)) ||
3984             (parents = strlen(buf) / 40) < 0) {
3985                 report("Failed to get parent information");
3986                 return FALSE;
3988         } else if (parents == 0) {
3989                 if (path)
3990                         report("Path '%s' does not exist in the parent", path);
3991                 else
3992                         report("The selected commit has no parents");
3993                 return FALSE;
3994         }
3996         if (parents == 1)
3997                 parents = 0;
3998         else if (!open_commit_parent_menu(buf, &parents))
3999                 return FALSE;
4001         string_copy_rev(rev, &buf[41 * parents]);
4002         return TRUE;
4005 /*
4006  * Pager backend
4007  */
4009 static bool
4010 pager_draw(struct view *view, struct line *line, unsigned int lineno)
4012         char text[SIZEOF_STR];
4014         if (opt_line_number && draw_lineno(view, lineno))
4015                 return TRUE;
4017         string_expand(text, sizeof(text), line->data, opt_tab_size);
4018         draw_text(view, line->type, text, TRUE);
4019         return TRUE;
4022 static bool
4023 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
4025         const char *describe_argv[] = { "git", "describe", commit_id, NULL };
4026         char ref[SIZEOF_STR];
4028         if (!io_run_buf(describe_argv, ref, sizeof(ref)) || !*ref)
4029                 return TRUE;
4031         /* This is the only fatal call, since it can "corrupt" the buffer. */
4032         if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
4033                 return FALSE;
4035         return TRUE;
4038 static void
4039 add_pager_refs(struct view *view, struct line *line)
4041         char buf[SIZEOF_STR];
4042         char *commit_id = (char *)line->data + STRING_SIZE("commit ");
4043         struct ref_list *list;
4044         size_t bufpos = 0, i;
4045         const char *sep = "Refs: ";
4046         bool is_tag = FALSE;
4048         assert(line->type == LINE_COMMIT);
4050         list = get_ref_list(commit_id);
4051         if (!list) {
4052                 if (view->type == VIEW_DIFF)
4053                         goto try_add_describe_ref;
4054                 return;
4055         }
4057         for (i = 0; i < list->size; i++) {
4058                 struct ref *ref = list->refs[i];
4059                 const char *fmt = ref->tag    ? "%s[%s]" :
4060                                   ref->remote ? "%s<%s>" : "%s%s";
4062                 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
4063                         return;
4064                 sep = ", ";
4065                 if (ref->tag)
4066                         is_tag = TRUE;
4067         }
4069         if (!is_tag && view->type == VIEW_DIFF) {
4070 try_add_describe_ref:
4071                 /* Add <tag>-g<commit_id> "fake" reference. */
4072                 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
4073                         return;
4074         }
4076         if (bufpos == 0)
4077                 return;
4079         add_line_text(view, buf, LINE_PP_REFS);
4082 static bool
4083 pager_read(struct view *view, char *data)
4085         struct line *line;
4087         if (!data)
4088                 return TRUE;
4090         line = add_line_text(view, data, get_line_type(data));
4091         if (!line)
4092                 return FALSE;
4094         if (line->type == LINE_COMMIT &&
4095             (view->type == VIEW_DIFF ||
4096              view->type == VIEW_LOG))
4097                 add_pager_refs(view, line);
4099         return TRUE;
4102 static enum request
4103 pager_request(struct view *view, enum request request, struct line *line)
4105         int split = 0;
4107         if (request != REQ_ENTER)
4108                 return request;
4110         if (line->type == LINE_COMMIT &&
4111            (view->type == VIEW_LOG ||
4112             view->type == VIEW_PAGER)) {
4113                 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
4114                 split = 1;
4115         }
4117         /* Always scroll the view even if it was split. That way
4118          * you can use Enter to scroll through the log view and
4119          * split open each commit diff. */
4120         scroll_view(view, REQ_SCROLL_LINE_DOWN);
4122         /* FIXME: A minor workaround. Scrolling the view will call report("")
4123          * but if we are scrolling a non-current view this won't properly
4124          * update the view title. */
4125         if (split)
4126                 update_view_title(view);
4128         return REQ_NONE;
4131 static bool
4132 pager_grep(struct view *view, struct line *line)
4134         const char *text[] = { line->data, NULL };
4136         return grep_text(view, text);
4139 static void
4140 pager_select(struct view *view, struct line *line)
4142         if (line->type == LINE_COMMIT) {
4143                 char *text = (char *)line->data + STRING_SIZE("commit ");
4145                 if (view->type != VIEW_PAGER)
4146                         string_copy_rev(view->ref, text);
4147                 string_copy_rev(ref_commit, text);
4148         }
4151 static struct view_ops pager_ops = {
4152         "line",
4153         NULL,
4154         NULL,
4155         pager_read,
4156         pager_draw,
4157         pager_request,
4158         pager_grep,
4159         pager_select,
4160 };
4162 static const char *log_argv[SIZEOF_ARG] = {
4163         "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
4164 };
4166 static enum request
4167 log_request(struct view *view, enum request request, struct line *line)
4169         switch (request) {
4170         case REQ_REFRESH:
4171                 load_refs();
4172                 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
4173                 return REQ_NONE;
4174         default:
4175                 return pager_request(view, request, line);
4176         }
4179 static struct view_ops log_ops = {
4180         "line",
4181         log_argv,
4182         NULL,
4183         pager_read,
4184         pager_draw,
4185         log_request,
4186         pager_grep,
4187         pager_select,
4188 };
4190 static const char *diff_argv[SIZEOF_ARG] = {
4191         "git", "show", "--pretty=fuller", "--no-color", "--root",
4192                 "--patch-with-stat", "--find-copies-harder", "-C", "%(commit)", NULL
4193 };
4195 static struct view_ops diff_ops = {
4196         "line",
4197         diff_argv,
4198         NULL,
4199         pager_read,
4200         pager_draw,
4201         pager_request,
4202         pager_grep,
4203         pager_select,
4204 };
4206 /*
4207  * Help backend
4208  */
4210 static bool help_keymap_hidden[ARRAY_SIZE(keymap_table)];
4212 static bool
4213 help_open_keymap_title(struct view *view, enum keymap keymap)
4215         struct line *line;
4217         line = add_line_format(view, LINE_HELP_KEYMAP, "[%c] %s bindings",
4218                                help_keymap_hidden[keymap] ? '+' : '-',
4219                                enum_name(keymap_table[keymap]));
4220         if (line)
4221                 line->other = keymap;
4223         return help_keymap_hidden[keymap];
4226 static void
4227 help_open_keymap(struct view *view, enum keymap keymap)
4229         const char *group = NULL;
4230         char buf[SIZEOF_STR];
4231         size_t bufpos;
4232         bool add_title = TRUE;
4233         int i;
4235         for (i = 0; i < ARRAY_SIZE(req_info); i++) {
4236                 const char *key = NULL;
4238                 if (req_info[i].request == REQ_NONE)
4239                         continue;
4241                 if (!req_info[i].request) {
4242                         group = req_info[i].help;
4243                         continue;
4244                 }
4246                 key = get_keys(keymap, req_info[i].request, TRUE);
4247                 if (!key || !*key)
4248                         continue;
4250                 if (add_title && help_open_keymap_title(view, keymap))
4251                         return;
4252                 add_title = FALSE;
4254                 if (group) {
4255                         add_line_text(view, group, LINE_HELP_GROUP);
4256                         group = NULL;
4257                 }
4259                 add_line_format(view, LINE_DEFAULT, "    %-25s %-20s %s", key,
4260                                 enum_name(req_info[i]), req_info[i].help);
4261         }
4263         group = "External commands:";
4265         for (i = 0; i < run_requests; i++) {
4266                 struct run_request *req = get_run_request(REQ_NONE + i + 1);
4267                 const char *key;
4268                 int argc;
4270                 if (!req || req->keymap != keymap)
4271                         continue;
4273                 key = get_key_name(req->key);
4274                 if (!*key)
4275                         key = "(no key defined)";
4277                 if (add_title && help_open_keymap_title(view, keymap))
4278                         return;
4279                 if (group) {
4280                         add_line_text(view, group, LINE_HELP_GROUP);
4281                         group = NULL;
4282                 }
4284                 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
4285                         if (!string_format_from(buf, &bufpos, "%s%s",
4286                                                 argc ? " " : "", req->argv[argc]))
4287                                 return;
4289                 add_line_format(view, LINE_DEFAULT, "    %-25s `%s`", key, buf);
4290         }
4293 static bool
4294 help_open(struct view *view)
4296         enum keymap keymap;
4298         reset_view(view);
4299         add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
4300         add_line_text(view, "", LINE_DEFAULT);
4302         for (keymap = 0; keymap < ARRAY_SIZE(keymap_table); keymap++)
4303                 help_open_keymap(view, keymap);
4305         return TRUE;
4308 static enum request
4309 help_request(struct view *view, enum request request, struct line *line)
4311         switch (request) {
4312         case REQ_ENTER:
4313                 if (line->type == LINE_HELP_KEYMAP) {
4314                         help_keymap_hidden[line->other] =
4315                                 !help_keymap_hidden[line->other];
4316                         view->p_restore = TRUE;
4317                         open_view(view, REQ_VIEW_HELP, OPEN_REFRESH);
4318                 }
4320                 return REQ_NONE;
4321         default:
4322                 return pager_request(view, request, line);
4323         }
4326 static struct view_ops help_ops = {
4327         "line",
4328         NULL,
4329         help_open,
4330         NULL,
4331         pager_draw,
4332         help_request,
4333         pager_grep,
4334         pager_select,
4335 };
4338 /*
4339  * Tree backend
4340  */
4342 struct tree_stack_entry {
4343         struct tree_stack_entry *prev;  /* Entry below this in the stack */
4344         unsigned long lineno;           /* Line number to restore */
4345         char *name;                     /* Position of name in opt_path */
4346 };
4348 /* The top of the path stack. */
4349 static struct tree_stack_entry *tree_stack = NULL;
4350 unsigned long tree_lineno = 0;
4352 static void
4353 pop_tree_stack_entry(void)
4355         struct tree_stack_entry *entry = tree_stack;
4357         tree_lineno = entry->lineno;
4358         entry->name[0] = 0;
4359         tree_stack = entry->prev;
4360         free(entry);
4363 static void
4364 push_tree_stack_entry(const char *name, unsigned long lineno)
4366         struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
4367         size_t pathlen = strlen(opt_path);
4369         if (!entry)
4370                 return;
4372         entry->prev = tree_stack;
4373         entry->name = opt_path + pathlen;
4374         tree_stack = entry;
4376         if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
4377                 pop_tree_stack_entry();
4378                 return;
4379         }
4381         /* Move the current line to the first tree entry. */
4382         tree_lineno = 1;
4383         entry->lineno = lineno;
4386 /* Parse output from git-ls-tree(1):
4387  *
4388  * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
4389  */
4391 #define SIZEOF_TREE_ATTR \
4392         STRING_SIZE("100644 blob f931e1d229c3e185caad4449bf5b66ed72462657\t")
4394 #define SIZEOF_TREE_MODE \
4395         STRING_SIZE("100644 ")
4397 #define TREE_ID_OFFSET \
4398         STRING_SIZE("100644 blob ")
4400 struct tree_entry {
4401         char id[SIZEOF_REV];
4402         mode_t mode;
4403         struct time time;               /* Date from the author ident. */
4404         const char *author;             /* Author of the commit. */
4405         char name[1];
4406 };
4408 static const char *
4409 tree_path(const struct line *line)
4411         return ((struct tree_entry *) line->data)->name;
4414 static int
4415 tree_compare_entry(const struct line *line1, const struct line *line2)
4417         if (line1->type != line2->type)
4418                 return line1->type == LINE_TREE_DIR ? -1 : 1;
4419         return strcmp(tree_path(line1), tree_path(line2));
4422 static const enum sort_field tree_sort_fields[] = {
4423         ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
4424 };
4425 static struct sort_state tree_sort_state = SORT_STATE(tree_sort_fields);
4427 static int
4428 tree_compare(const void *l1, const void *l2)
4430         const struct line *line1 = (const struct line *) l1;
4431         const struct line *line2 = (const struct line *) l2;
4432         const struct tree_entry *entry1 = ((const struct line *) l1)->data;
4433         const struct tree_entry *entry2 = ((const struct line *) l2)->data;
4435         if (line1->type == LINE_TREE_HEAD)
4436                 return -1;
4437         if (line2->type == LINE_TREE_HEAD)
4438                 return 1;
4440         switch (get_sort_field(tree_sort_state)) {
4441         case ORDERBY_DATE:
4442                 return sort_order(tree_sort_state, timecmp(&entry1->time, &entry2->time));
4444         case ORDERBY_AUTHOR:
4445                 return sort_order(tree_sort_state, strcmp(entry1->author, entry2->author));
4447         case ORDERBY_NAME:
4448         default:
4449                 return sort_order(tree_sort_state, tree_compare_entry(line1, line2));
4450         }
4454 static struct line *
4455 tree_entry(struct view *view, enum line_type type, const char *path,
4456            const char *mode, const char *id)
4458         struct tree_entry *entry = calloc(1, sizeof(*entry) + strlen(path));
4459         struct line *line = entry ? add_line_data(view, entry, type) : NULL;
4461         if (!entry || !line) {
4462                 free(entry);
4463                 return NULL;
4464         }
4466         strncpy(entry->name, path, strlen(path));
4467         if (mode)
4468                 entry->mode = strtoul(mode, NULL, 8);
4469         if (id)
4470                 string_copy_rev(entry->id, id);
4472         return line;
4475 static bool
4476 tree_read_date(struct view *view, char *text, bool *read_date)
4478         static const char *author_name;
4479         static struct time author_time;
4481         if (!text && *read_date) {
4482                 *read_date = FALSE;
4483                 return TRUE;
4485         } else if (!text) {
4486                 char *path = *opt_path ? opt_path : ".";
4487                 /* Find next entry to process */
4488                 const char *log_file[] = {
4489                         "git", "log", "--no-color", "--pretty=raw",
4490                                 "--cc", "--raw", view->id, "--", path, NULL
4491                 };
4493                 if (!view->lines) {
4494                         tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL);
4495                         report("Tree is empty");
4496                         return TRUE;
4497                 }
4499                 if (!start_update(view, log_file, opt_cdup)) {
4500                         report("Failed to load tree data");
4501                         return TRUE;
4502                 }
4504                 *read_date = TRUE;
4505                 return FALSE;
4507         } else if (*text == 'a' && get_line_type(text) == LINE_AUTHOR) {
4508                 parse_author_line(text + STRING_SIZE("author "),
4509                                   &author_name, &author_time);
4511         } else if (*text == ':') {
4512                 char *pos;
4513                 size_t annotated = 1;
4514                 size_t i;
4516                 pos = strchr(text, '\t');
4517                 if (!pos)
4518                         return TRUE;
4519                 text = pos + 1;
4520                 if (*opt_path && !strncmp(text, opt_path, strlen(opt_path)))
4521                         text += strlen(opt_path);
4522                 pos = strchr(text, '/');
4523                 if (pos)
4524                         *pos = 0;
4526                 for (i = 1; i < view->lines; i++) {
4527                         struct line *line = &view->line[i];
4528                         struct tree_entry *entry = line->data;
4530                         annotated += !!entry->author;
4531                         if (entry->author || strcmp(entry->name, text))
4532                                 continue;
4534                         entry->author = author_name;
4535                         entry->time = author_time;
4536                         line->dirty = 1;
4537                         break;
4538                 }
4540                 if (annotated == view->lines)
4541                         io_kill(view->pipe);
4542         }
4543         return TRUE;
4546 static bool
4547 tree_read(struct view *view, char *text)
4549         static bool read_date = FALSE;
4550         struct tree_entry *data;
4551         struct line *entry, *line;
4552         enum line_type type;
4553         size_t textlen = text ? strlen(text) : 0;
4554         char *path = text + SIZEOF_TREE_ATTR;
4556         if (read_date || !text)
4557                 return tree_read_date(view, text, &read_date);
4559         if (textlen <= SIZEOF_TREE_ATTR)
4560                 return FALSE;
4561         if (view->lines == 0 &&
4562             !tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL))
4563                 return FALSE;
4565         /* Strip the path part ... */
4566         if (*opt_path) {
4567                 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
4568                 size_t striplen = strlen(opt_path);
4570                 if (pathlen > striplen)
4571                         memmove(path, path + striplen,
4572                                 pathlen - striplen + 1);
4574                 /* Insert "link" to parent directory. */
4575                 if (view->lines == 1 &&
4576                     !tree_entry(view, LINE_TREE_DIR, "..", "040000", view->ref))
4577                         return FALSE;
4578         }
4580         type = text[SIZEOF_TREE_MODE] == 't' ? LINE_TREE_DIR : LINE_TREE_FILE;
4581         entry = tree_entry(view, type, path, text, text + TREE_ID_OFFSET);
4582         if (!entry)
4583                 return FALSE;
4584         data = entry->data;
4586         /* Skip "Directory ..." and ".." line. */
4587         for (line = &view->line[1 + !!*opt_path]; line < entry; line++) {
4588                 if (tree_compare_entry(line, entry) <= 0)
4589                         continue;
4591                 memmove(line + 1, line, (entry - line) * sizeof(*entry));
4593                 line->data = data;
4594                 line->type = type;
4595                 for (; line <= entry; line++)
4596                         line->dirty = line->cleareol = 1;
4597                 return TRUE;
4598         }
4600         if (tree_lineno > view->lineno) {
4601                 view->lineno = tree_lineno;
4602                 tree_lineno = 0;
4603         }
4605         return TRUE;
4608 static bool
4609 tree_draw(struct view *view, struct line *line, unsigned int lineno)
4611         struct tree_entry *entry = line->data;
4613         if (line->type == LINE_TREE_HEAD) {
4614                 if (draw_text(view, line->type, "Directory path /", TRUE))
4615                         return TRUE;
4616         } else {
4617                 if (draw_mode(view, entry->mode))
4618                         return TRUE;
4620                 if (opt_author && draw_author(view, entry->author))
4621                         return TRUE;
4623                 if (opt_date && draw_date(view, &entry->time))
4624                         return TRUE;
4625         }
4626         if (draw_text(view, line->type, entry->name, TRUE))
4627                 return TRUE;
4628         return TRUE;
4631 static void
4632 open_blob_editor(const char *id)
4634         const char *blob_argv[] = { "git", "cat-file", "blob", id, NULL };
4635         char file[SIZEOF_STR] = "/tmp/tigblob.XXXXXX";
4636         int fd = mkstemp(file);
4638         if (fd == -1)
4639                 report("Failed to create temporary file");
4640         else if (!io_run_append(blob_argv, fd))
4641                 report("Failed to save blob data to file");
4642         else
4643                 open_editor(file);
4644         if (fd != -1)
4645                 unlink(file);
4648 static enum request
4649 tree_request(struct view *view, enum request request, struct line *line)
4651         enum open_flags flags;
4652         struct tree_entry *entry = line->data;
4654         switch (request) {
4655         case REQ_VIEW_BLAME:
4656                 if (line->type != LINE_TREE_FILE) {
4657                         report("Blame only supported for files");
4658                         return REQ_NONE;
4659                 }
4661                 string_copy(opt_ref, view->vid);
4662                 return request;
4664         case REQ_EDIT:
4665                 if (line->type != LINE_TREE_FILE) {
4666                         report("Edit only supported for files");
4667                 } else if (!is_head_commit(view->vid)) {
4668                         open_blob_editor(entry->id);
4669                 } else {
4670                         open_editor(opt_file);
4671                 }
4672                 return REQ_NONE;
4674         case REQ_TOGGLE_SORT_FIELD:
4675         case REQ_TOGGLE_SORT_ORDER:
4676                 sort_view(view, request, &tree_sort_state, tree_compare);
4677                 return REQ_NONE;
4679         case REQ_PARENT:
4680                 if (!*opt_path) {
4681                         /* quit view if at top of tree */
4682                         return REQ_VIEW_CLOSE;
4683                 }
4684                 /* fake 'cd  ..' */
4685                 line = &view->line[1];
4686                 break;
4688         case REQ_ENTER:
4689                 break;
4691         default:
4692                 return request;
4693         }
4695         /* Cleanup the stack if the tree view is at a different tree. */
4696         while (!*opt_path && tree_stack)
4697                 pop_tree_stack_entry();
4699         switch (line->type) {
4700         case LINE_TREE_DIR:
4701                 /* Depending on whether it is a subdirectory or parent link
4702                  * mangle the path buffer. */
4703                 if (line == &view->line[1] && *opt_path) {
4704                         pop_tree_stack_entry();
4706                 } else {
4707                         const char *basename = tree_path(line);
4709                         push_tree_stack_entry(basename, view->lineno);
4710                 }
4712                 /* Trees and subtrees share the same ID, so they are not not
4713                  * unique like blobs. */
4714                 flags = OPEN_RELOAD;
4715                 request = REQ_VIEW_TREE;
4716                 break;
4718         case LINE_TREE_FILE:
4719                 flags = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
4720                 request = REQ_VIEW_BLOB;
4721                 break;
4723         default:
4724                 return REQ_NONE;
4725         }
4727         open_view(view, request, flags);
4728         if (request == REQ_VIEW_TREE)
4729                 view->lineno = tree_lineno;
4731         return REQ_NONE;
4734 static bool
4735 tree_grep(struct view *view, struct line *line)
4737         struct tree_entry *entry = line->data;
4738         const char *text[] = {
4739                 entry->name,
4740                 opt_author ? entry->author : "",
4741                 mkdate(&entry->time, opt_date),
4742                 NULL
4743         };
4745         return grep_text(view, text);
4748 static void
4749 tree_select(struct view *view, struct line *line)
4751         struct tree_entry *entry = line->data;
4753         if (line->type == LINE_TREE_FILE) {
4754                 string_copy_rev(ref_blob, entry->id);
4755                 string_format(opt_file, "%s%s", opt_path, tree_path(line));
4757         } else if (line->type != LINE_TREE_DIR) {
4758                 return;
4759         }
4761         string_copy_rev(view->ref, entry->id);
4764 static bool
4765 tree_prepare(struct view *view)
4767         if (view->lines == 0 && opt_prefix[0]) {
4768                 char *pos = opt_prefix;
4770                 while (pos && *pos) {
4771                         char *end = strchr(pos, '/');
4773                         if (end)
4774                                 *end = 0;
4775                         push_tree_stack_entry(pos, 0);
4776                         pos = end;
4777                         if (end) {
4778                                 *end = '/';
4779                                 pos++;
4780                         }
4781                 }
4783         } else if (strcmp(view->vid, view->id)) {
4784                 opt_path[0] = 0;
4785         }
4787         return prepare_io(view, opt_cdup, view->ops->argv, TRUE);
4790 static const char *tree_argv[SIZEOF_ARG] = {
4791         "git", "ls-tree", "%(commit)", "%(directory)", NULL
4792 };
4794 static struct view_ops tree_ops = {
4795         "file",
4796         tree_argv,
4797         NULL,
4798         tree_read,
4799         tree_draw,
4800         tree_request,
4801         tree_grep,
4802         tree_select,
4803         tree_prepare,
4804 };
4806 static bool
4807 blob_read(struct view *view, char *line)
4809         if (!line)
4810                 return TRUE;
4811         return add_line_text(view, line, LINE_DEFAULT) != NULL;
4814 static enum request
4815 blob_request(struct view *view, enum request request, struct line *line)
4817         switch (request) {
4818         case REQ_EDIT:
4819                 open_blob_editor(view->vid);
4820                 return REQ_NONE;
4821         default:
4822                 return pager_request(view, request, line);
4823         }
4826 static const char *blob_argv[SIZEOF_ARG] = {
4827         "git", "cat-file", "blob", "%(blob)", NULL
4828 };
4830 static struct view_ops blob_ops = {
4831         "line",
4832         blob_argv,
4833         NULL,
4834         blob_read,
4835         pager_draw,
4836         blob_request,
4837         pager_grep,
4838         pager_select,
4839 };
4841 /*
4842  * Blame backend
4843  *
4844  * Loading the blame view is a two phase job:
4845  *
4846  *  1. File content is read either using opt_file from the
4847  *     filesystem or using git-cat-file.
4848  *  2. Then blame information is incrementally added by
4849  *     reading output from git-blame.
4850  */
4852 struct blame_commit {
4853         char id[SIZEOF_REV];            /* SHA1 ID. */
4854         char title[128];                /* First line of the commit message. */
4855         const char *author;             /* Author of the commit. */
4856         struct time time;               /* Date from the author ident. */
4857         char filename[128];             /* Name of file. */
4858         bool has_previous;              /* Was a "previous" line detected. */
4859 };
4861 struct blame {
4862         struct blame_commit *commit;
4863         unsigned long lineno;
4864         char text[1];
4865 };
4867 static bool
4868 blame_open(struct view *view)
4870         char path[SIZEOF_STR];
4872         if (!view->prev && *opt_prefix) {
4873                 string_copy(path, opt_file);
4874                 if (!string_format(opt_file, "%s%s", opt_prefix, path))
4875                         return FALSE;
4876         }
4878         if (*opt_ref || !io_open(&view->io, "%s%s", opt_cdup, opt_file)) {
4879                 const char *blame_cat_file_argv[] = {
4880                         "git", "cat-file", "blob", path, NULL
4881                 };
4883                 if (!string_format(path, "%s:%s", opt_ref, opt_file) ||
4884                     !start_update(view, blame_cat_file_argv, opt_cdup))
4885                         return FALSE;
4886         }
4888         setup_update(view, opt_file);
4889         string_format(view->ref, "%s ...", opt_file);
4891         return TRUE;
4894 static struct blame_commit *
4895 get_blame_commit(struct view *view, const char *id)
4897         size_t i;
4899         for (i = 0; i < view->lines; i++) {
4900                 struct blame *blame = view->line[i].data;
4902                 if (!blame->commit)
4903                         continue;
4905                 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
4906                         return blame->commit;
4907         }
4909         {
4910                 struct blame_commit *commit = calloc(1, sizeof(*commit));
4912                 if (commit)
4913                         string_ncopy(commit->id, id, SIZEOF_REV);
4914                 return commit;
4915         }
4918 static bool
4919 parse_number(const char **posref, size_t *number, size_t min, size_t max)
4921         const char *pos = *posref;
4923         *posref = NULL;
4924         pos = strchr(pos + 1, ' ');
4925         if (!pos || !isdigit(pos[1]))
4926                 return FALSE;
4927         *number = atoi(pos + 1);
4928         if (*number < min || *number > max)
4929                 return FALSE;
4931         *posref = pos;
4932         return TRUE;
4935 static struct blame_commit *
4936 parse_blame_commit(struct view *view, const char *text, int *blamed)
4938         struct blame_commit *commit;
4939         struct blame *blame;
4940         const char *pos = text + SIZEOF_REV - 2;
4941         size_t orig_lineno = 0;
4942         size_t lineno;
4943         size_t group;
4945         if (strlen(text) <= SIZEOF_REV || pos[1] != ' ')
4946                 return NULL;
4948         if (!parse_number(&pos, &orig_lineno, 1, 9999999) ||
4949             !parse_number(&pos, &lineno, 1, view->lines) ||
4950             !parse_number(&pos, &group, 1, view->lines - lineno + 1))
4951                 return NULL;
4953         commit = get_blame_commit(view, text);
4954         if (!commit)
4955                 return NULL;
4957         *blamed += group;
4958         while (group--) {
4959                 struct line *line = &view->line[lineno + group - 1];
4961                 blame = line->data;
4962                 blame->commit = commit;
4963                 blame->lineno = orig_lineno + group - 1;
4964                 line->dirty = 1;
4965         }
4967         return commit;
4970 static bool
4971 blame_read_file(struct view *view, const char *line, bool *read_file)
4973         if (!line) {
4974                 const char *blame_argv[] = {
4975                         "git", "blame", "--incremental",
4976                                 *opt_ref ? opt_ref : "--incremental", "--", opt_file, NULL
4977                 };
4979                 if (view->lines == 0 && !view->prev)
4980                         die("No blame exist for %s", view->vid);
4982                 if (view->lines == 0 || !start_update(view, blame_argv, opt_cdup)) {
4983                         report("Failed to load blame data");
4984                         return TRUE;
4985                 }
4987                 *read_file = FALSE;
4988                 return FALSE;
4990         } else {
4991                 size_t linelen = strlen(line);
4992                 struct blame *blame = malloc(sizeof(*blame) + linelen);
4994                 if (!blame)
4995                         return FALSE;
4997                 blame->commit = NULL;
4998                 strncpy(blame->text, line, linelen);
4999                 blame->text[linelen] = 0;
5000                 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
5001         }
5004 static bool
5005 match_blame_header(const char *name, char **line)
5007         size_t namelen = strlen(name);
5008         bool matched = !strncmp(name, *line, namelen);
5010         if (matched)
5011                 *line += namelen;
5013         return matched;
5016 static bool
5017 blame_read(struct view *view, char *line)
5019         static struct blame_commit *commit = NULL;
5020         static int blamed = 0;
5021         static bool read_file = TRUE;
5023         if (read_file)
5024                 return blame_read_file(view, line, &read_file);
5026         if (!line) {
5027                 /* Reset all! */
5028                 commit = NULL;
5029                 blamed = 0;
5030                 read_file = TRUE;
5031                 string_format(view->ref, "%s", view->vid);
5032                 if (view_is_displayed(view)) {
5033                         update_view_title(view);
5034                         redraw_view_from(view, 0);
5035                 }
5036                 return TRUE;
5037         }
5039         if (!commit) {
5040                 commit = parse_blame_commit(view, line, &blamed);
5041                 string_format(view->ref, "%s %2d%%", view->vid,
5042                               view->lines ? blamed * 100 / view->lines : 0);
5044         } else if (match_blame_header("author ", &line)) {
5045                 commit->author = get_author(line);
5047         } else if (match_blame_header("author-time ", &line)) {
5048                 parse_timesec(&commit->time, line);
5050         } else if (match_blame_header("author-tz ", &line)) {
5051                 parse_timezone(&commit->time, line);
5053         } else if (match_blame_header("summary ", &line)) {
5054                 string_ncopy(commit->title, line, strlen(line));
5056         } else if (match_blame_header("previous ", &line)) {
5057                 commit->has_previous = TRUE;
5059         } else if (match_blame_header("filename ", &line)) {
5060                 string_ncopy(commit->filename, line, strlen(line));
5061                 commit = NULL;
5062         }
5064         return TRUE;
5067 static bool
5068 blame_draw(struct view *view, struct line *line, unsigned int lineno)
5070         struct blame *blame = line->data;
5071         struct time *time = NULL;
5072         const char *id = NULL, *author = NULL;
5073         char text[SIZEOF_STR];
5075         if (blame->commit && *blame->commit->filename) {
5076                 id = blame->commit->id;
5077                 author = blame->commit->author;
5078                 time = &blame->commit->time;
5079         }
5081         if (opt_date && draw_date(view, time))
5082                 return TRUE;
5084         if (opt_author && draw_author(view, author))
5085                 return TRUE;
5087         if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
5088                 return TRUE;
5090         if (draw_lineno(view, lineno))
5091                 return TRUE;
5093         string_expand(text, sizeof(text), blame->text, opt_tab_size);
5094         draw_text(view, LINE_DEFAULT, text, TRUE);
5095         return TRUE;
5098 static bool
5099 check_blame_commit(struct blame *blame, bool check_null_id)
5101         if (!blame->commit)
5102                 report("Commit data not loaded yet");
5103         else if (check_null_id && !strcmp(blame->commit->id, NULL_ID))
5104                 report("No commit exist for the selected line");
5105         else
5106                 return TRUE;
5107         return FALSE;
5110 static void
5111 setup_blame_parent_line(struct view *view, struct blame *blame)
5113         const char *diff_tree_argv[] = {
5114                 "git", "diff-tree", "-U0", blame->commit->id,
5115                         "--", blame->commit->filename, NULL
5116         };
5117         struct io io;
5118         int parent_lineno = -1;
5119         int blamed_lineno = -1;
5120         char *line;
5122         if (!io_run(&io, IO_RD, NULL, diff_tree_argv))
5123                 return;
5125         while ((line = io_get(&io, '\n', TRUE))) {
5126                 if (*line == '@') {
5127                         char *pos = strchr(line, '+');
5129                         parent_lineno = atoi(line + 4);
5130                         if (pos)
5131                                 blamed_lineno = atoi(pos + 1);
5133                 } else if (*line == '+' && parent_lineno != -1) {
5134                         if (blame->lineno == blamed_lineno - 1 &&
5135                             !strcmp(blame->text, line + 1)) {
5136                                 view->lineno = parent_lineno ? parent_lineno - 1 : 0;
5137                                 break;
5138                         }
5139                         blamed_lineno++;
5140                 }
5141         }
5143         io_done(&io);
5146 static enum request
5147 blame_request(struct view *view, enum request request, struct line *line)
5149         enum open_flags flags = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
5150         struct blame *blame = line->data;
5152         switch (request) {
5153         case REQ_VIEW_BLAME:
5154                 if (check_blame_commit(blame, TRUE)) {
5155                         string_copy(opt_ref, blame->commit->id);
5156                         string_copy(opt_file, blame->commit->filename);
5157                         if (blame->lineno)
5158                                 view->lineno = blame->lineno;
5159                         open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
5160                 }
5161                 break;
5163         case REQ_PARENT:
5164                 if (check_blame_commit(blame, TRUE) &&
5165                     select_commit_parent(blame->commit->id, opt_ref,
5166                                          blame->commit->filename)) {
5167                         string_copy(opt_file, blame->commit->filename);
5168                         setup_blame_parent_line(view, blame);
5169                         open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
5170                 }
5171                 break;
5173         case REQ_ENTER:
5174                 if (!check_blame_commit(blame, FALSE))
5175                         break;
5177                 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
5178                     !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
5179                         break;
5181                 if (!strcmp(blame->commit->id, NULL_ID)) {
5182                         struct view *diff = VIEW(REQ_VIEW_DIFF);
5183                         const char *diff_index_argv[] = {
5184                                 "git", "diff-index", "--root", "--patch-with-stat",
5185                                         "-C", "-M", "HEAD", "--", view->vid, NULL
5186                         };
5188                         if (!blame->commit->has_previous) {
5189                                 diff_index_argv[1] = "diff";
5190                                 diff_index_argv[2] = "--no-color";
5191                                 diff_index_argv[6] = "--";
5192                                 diff_index_argv[7] = "/dev/null";
5193                         }
5195                         if (!prepare_update(diff, diff_index_argv, NULL)) {
5196                                 report("Failed to allocate diff command");
5197                                 break;
5198                         }
5199                         flags |= OPEN_PREPARED;
5200                 }
5202                 open_view(view, REQ_VIEW_DIFF, flags);
5203                 if (VIEW(REQ_VIEW_DIFF)->pipe && !strcmp(blame->commit->id, NULL_ID))
5204                         string_copy_rev(VIEW(REQ_VIEW_DIFF)->ref, NULL_ID);
5205                 break;
5207         default:
5208                 return request;
5209         }
5211         return REQ_NONE;
5214 static bool
5215 blame_grep(struct view *view, struct line *line)
5217         struct blame *blame = line->data;
5218         struct blame_commit *commit = blame->commit;
5219         const char *text[] = {
5220                 blame->text,
5221                 commit ? commit->title : "",
5222                 commit ? commit->id : "",
5223                 commit && opt_author ? commit->author : "",
5224                 commit ? mkdate(&commit->time, opt_date) : "",
5225                 NULL
5226         };
5228         return grep_text(view, text);
5231 static void
5232 blame_select(struct view *view, struct line *line)
5234         struct blame *blame = line->data;
5235         struct blame_commit *commit = blame->commit;
5237         if (!commit)
5238                 return;
5240         if (!strcmp(commit->id, NULL_ID))
5241                 string_ncopy(ref_commit, "HEAD", 4);
5242         else
5243                 string_copy_rev(ref_commit, commit->id);
5246 static struct view_ops blame_ops = {
5247         "line",
5248         NULL,
5249         blame_open,
5250         blame_read,
5251         blame_draw,
5252         blame_request,
5253         blame_grep,
5254         blame_select,
5255 };
5257 /*
5258  * Branch backend
5259  */
5261 struct branch {
5262         const char *author;             /* Author of the last commit. */
5263         struct time time;               /* Date of the last activity. */
5264         const struct ref *ref;          /* Name and commit ID information. */
5265 };
5267 static const struct ref branch_all;
5269 static const enum sort_field branch_sort_fields[] = {
5270         ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
5271 };
5272 static struct sort_state branch_sort_state = SORT_STATE(branch_sort_fields);
5274 static int
5275 branch_compare(const void *l1, const void *l2)
5277         const struct branch *branch1 = ((const struct line *) l1)->data;
5278         const struct branch *branch2 = ((const struct line *) l2)->data;
5280         switch (get_sort_field(branch_sort_state)) {
5281         case ORDERBY_DATE:
5282                 return sort_order(branch_sort_state, timecmp(&branch1->time, &branch2->time));
5284         case ORDERBY_AUTHOR:
5285                 return sort_order(branch_sort_state, strcmp(branch1->author, branch2->author));
5287         case ORDERBY_NAME:
5288         default:
5289                 return sort_order(branch_sort_state, strcmp(branch1->ref->name, branch2->ref->name));
5290         }
5293 static bool
5294 branch_draw(struct view *view, struct line *line, unsigned int lineno)
5296         struct branch *branch = line->data;
5297         enum line_type type = branch->ref->head ? LINE_MAIN_HEAD : LINE_DEFAULT;
5299         if (opt_date && draw_date(view, &branch->time))
5300                 return TRUE;
5302         if (opt_author && draw_author(view, branch->author))
5303                 return TRUE;
5305         draw_text(view, type, branch->ref == &branch_all ? "All branches" : branch->ref->name, TRUE);
5306         return TRUE;
5309 static enum request
5310 branch_request(struct view *view, enum request request, struct line *line)
5312         struct branch *branch = line->data;
5314         switch (request) {
5315         case REQ_REFRESH:
5316                 load_refs();
5317                 open_view(view, REQ_VIEW_BRANCH, OPEN_REFRESH);
5318                 return REQ_NONE;
5320         case REQ_TOGGLE_SORT_FIELD:
5321         case REQ_TOGGLE_SORT_ORDER:
5322                 sort_view(view, request, &branch_sort_state, branch_compare);
5323                 return REQ_NONE;
5325         case REQ_ENTER:
5326                 if (branch->ref == &branch_all) {
5327                         const char *all_branches_argv[] = {
5328                                 "git", "log", "--no-color", "--pretty=raw", "--parents",
5329                                       "--topo-order", "--all", NULL
5330                         };
5331                         struct view *main_view = VIEW(REQ_VIEW_MAIN);
5333                         if (!prepare_update(main_view, all_branches_argv, NULL)) {
5334                                 report("Failed to load view of all branches");
5335                                 return REQ_NONE;
5336                         }
5337                         open_view(view, REQ_VIEW_MAIN, OPEN_PREPARED | OPEN_SPLIT);
5338                 } else {
5339                         open_view(view, REQ_VIEW_MAIN, OPEN_SPLIT);
5340                 }
5341                 return REQ_NONE;
5343         default:
5344                 return request;
5345         }
5348 static bool
5349 branch_read(struct view *view, char *line)
5351         static char id[SIZEOF_REV];
5352         struct branch *reference;
5353         size_t i;
5355         if (!line)
5356                 return TRUE;
5358         switch (get_line_type(line)) {
5359         case LINE_COMMIT:
5360                 string_copy_rev(id, line + STRING_SIZE("commit "));
5361                 return TRUE;
5363         case LINE_AUTHOR:
5364                 for (i = 0, reference = NULL; i < view->lines; i++) {
5365                         struct branch *branch = view->line[i].data;
5367                         if (strcmp(branch->ref->id, id))
5368                                 continue;
5370                         view->line[i].dirty = TRUE;
5371                         if (reference) {
5372                                 branch->author = reference->author;
5373                                 branch->time = reference->time;
5374                                 continue;
5375                         }
5377                         parse_author_line(line + STRING_SIZE("author "),
5378                                           &branch->author, &branch->time);
5379                         reference = branch;
5380                 }
5381                 return TRUE;
5383         default:
5384                 return TRUE;
5385         }
5389 static bool
5390 branch_open_visitor(void *data, const struct ref *ref)
5392         struct view *view = data;
5393         struct branch *branch;
5395         if (ref->tag || ref->ltag || ref->remote)
5396                 return TRUE;
5398         branch = calloc(1, sizeof(*branch));
5399         if (!branch)
5400                 return FALSE;
5402         branch->ref = ref;
5403         return !!add_line_data(view, branch, LINE_DEFAULT);
5406 static bool
5407 branch_open(struct view *view)
5409         const char *branch_log[] = {
5410                 "git", "log", "--no-color", "--pretty=raw",
5411                         "--simplify-by-decoration", "--all", NULL
5412         };
5414         if (!start_update(view, branch_log, NULL)) {
5415                 report("Failed to load branch data");
5416                 return TRUE;
5417         }
5419         setup_update(view, view->id);
5420         branch_open_visitor(view, &branch_all);
5421         foreach_ref(branch_open_visitor, view);
5422         view->p_restore = TRUE;
5424         return TRUE;
5427 static bool
5428 branch_grep(struct view *view, struct line *line)
5430         struct branch *branch = line->data;
5431         const char *text[] = {
5432                 branch->ref->name,
5433                 branch->author,
5434                 NULL
5435         };
5437         return grep_text(view, text);
5440 static void
5441 branch_select(struct view *view, struct line *line)
5443         struct branch *branch = line->data;
5445         string_copy_rev(view->ref, branch->ref->id);
5446         string_copy_rev(ref_commit, branch->ref->id);
5447         string_copy_rev(ref_head, branch->ref->id);
5448         string_copy_rev(ref_branch, branch->ref->name);
5451 static struct view_ops branch_ops = {
5452         "branch",
5453         NULL,
5454         branch_open,
5455         branch_read,
5456         branch_draw,
5457         branch_request,
5458         branch_grep,
5459         branch_select,
5460 };
5462 /*
5463  * Status backend
5464  */
5466 struct status {
5467         char status;
5468         struct {
5469                 mode_t mode;
5470                 char rev[SIZEOF_REV];
5471                 char name[SIZEOF_STR];
5472         } old;
5473         struct {
5474                 mode_t mode;
5475                 char rev[SIZEOF_REV];
5476                 char name[SIZEOF_STR];
5477         } new;
5478 };
5480 static char status_onbranch[SIZEOF_STR];
5481 static struct status stage_status;
5482 static enum line_type stage_line_type;
5483 static size_t stage_chunks;
5484 static int *stage_chunk;
5486 DEFINE_ALLOCATOR(realloc_ints, int, 32)
5488 /* This should work even for the "On branch" line. */
5489 static inline bool
5490 status_has_none(struct view *view, struct line *line)
5492         return line < view->line + view->lines && !line[1].data;
5495 /* Get fields from the diff line:
5496  * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
5497  */
5498 static inline bool
5499 status_get_diff(struct status *file, const char *buf, size_t bufsize)
5501         const char *old_mode = buf +  1;
5502         const char *new_mode = buf +  8;
5503         const char *old_rev  = buf + 15;
5504         const char *new_rev  = buf + 56;
5505         const char *status   = buf + 97;
5507         if (bufsize < 98 ||
5508             old_mode[-1] != ':' ||
5509             new_mode[-1] != ' ' ||
5510             old_rev[-1]  != ' ' ||
5511             new_rev[-1]  != ' ' ||
5512             status[-1]   != ' ')
5513                 return FALSE;
5515         file->status = *status;
5517         string_copy_rev(file->old.rev, old_rev);
5518         string_copy_rev(file->new.rev, new_rev);
5520         file->old.mode = strtoul(old_mode, NULL, 8);
5521         file->new.mode = strtoul(new_mode, NULL, 8);
5523         file->old.name[0] = file->new.name[0] = 0;
5525         return TRUE;
5528 static bool
5529 status_run(struct view *view, const char *argv[], char status, enum line_type type)
5531         struct status *unmerged = NULL;
5532         char *buf;
5533         struct io io;
5535         if (!io_run(&io, IO_RD, opt_cdup, argv))
5536                 return FALSE;
5538         add_line_data(view, NULL, type);
5540         while ((buf = io_get(&io, 0, TRUE))) {
5541                 struct status *file = unmerged;
5543                 if (!file) {
5544                         file = calloc(1, sizeof(*file));
5545                         if (!file || !add_line_data(view, file, type))
5546                                 goto error_out;
5547                 }
5549                 /* Parse diff info part. */
5550                 if (status) {
5551                         file->status = status;
5552                         if (status == 'A')
5553                                 string_copy(file->old.rev, NULL_ID);
5555                 } else if (!file->status || file == unmerged) {
5556                         if (!status_get_diff(file, buf, strlen(buf)))
5557                                 goto error_out;
5559                         buf = io_get(&io, 0, TRUE);
5560                         if (!buf)
5561                                 break;
5563                         /* Collapse all modified entries that follow an
5564                          * associated unmerged entry. */
5565                         if (unmerged == file) {
5566                                 unmerged->status = 'U';
5567                                 unmerged = NULL;
5568                         } else if (file->status == 'U') {
5569                                 unmerged = file;
5570                         }
5571                 }
5573                 /* Grab the old name for rename/copy. */
5574                 if (!*file->old.name &&
5575                     (file->status == 'R' || file->status == 'C')) {
5576                         string_ncopy(file->old.name, buf, strlen(buf));
5578                         buf = io_get(&io, 0, TRUE);
5579                         if (!buf)
5580                                 break;
5581                 }
5583                 /* git-ls-files just delivers a NUL separated list of
5584                  * file names similar to the second half of the
5585                  * git-diff-* output. */
5586                 string_ncopy(file->new.name, buf, strlen(buf));
5587                 if (!*file->old.name)
5588                         string_copy(file->old.name, file->new.name);
5589                 file = NULL;
5590         }
5592         if (io_error(&io)) {
5593 error_out:
5594                 io_done(&io);
5595                 return FALSE;
5596         }
5598         if (!view->line[view->lines - 1].data)
5599                 add_line_data(view, NULL, LINE_STAT_NONE);
5601         io_done(&io);
5602         return TRUE;
5605 /* Don't show unmerged entries in the staged section. */
5606 static const char *status_diff_index_argv[] = {
5607         "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
5608                              "--cached", "-M", "HEAD", NULL
5609 };
5611 static const char *status_diff_files_argv[] = {
5612         "git", "diff-files", "-z", NULL
5613 };
5615 static const char *status_list_other_argv[] = {
5616         "git", "ls-files", "-z", "--others", "--exclude-standard", opt_prefix, NULL
5617 };
5619 static const char *status_list_no_head_argv[] = {
5620         "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
5621 };
5623 static const char *update_index_argv[] = {
5624         "git", "update-index", "-q", "--unmerged", "--refresh", NULL
5625 };
5627 /* Restore the previous line number to stay in the context or select a
5628  * line with something that can be updated. */
5629 static void
5630 status_restore(struct view *view)
5632         if (view->p_lineno >= view->lines)
5633                 view->p_lineno = view->lines - 1;
5634         while (view->p_lineno < view->lines && !view->line[view->p_lineno].data)
5635                 view->p_lineno++;
5636         while (view->p_lineno > 0 && !view->line[view->p_lineno].data)
5637                 view->p_lineno--;
5639         /* If the above fails, always skip the "On branch" line. */
5640         if (view->p_lineno < view->lines)
5641                 view->lineno = view->p_lineno;
5642         else
5643                 view->lineno = 1;
5645         if (view->lineno < view->offset)
5646                 view->offset = view->lineno;
5647         else if (view->offset + view->height <= view->lineno)
5648                 view->offset = view->lineno - view->height + 1;
5650         view->p_restore = FALSE;
5653 static void
5654 status_update_onbranch(void)
5656         static const char *paths[][2] = {
5657                 { "rebase-apply/rebasing",      "Rebasing" },
5658                 { "rebase-apply/applying",      "Applying mailbox" },
5659                 { "rebase-apply/",              "Rebasing mailbox" },
5660                 { "rebase-merge/interactive",   "Interactive rebase" },
5661                 { "rebase-merge/",              "Rebase merge" },
5662                 { "MERGE_HEAD",                 "Merging" },
5663                 { "BISECT_LOG",                 "Bisecting" },
5664                 { "HEAD",                       "On branch" },
5665         };
5666         char buf[SIZEOF_STR];
5667         struct stat stat;
5668         int i;
5670         if (is_initial_commit()) {
5671                 string_copy(status_onbranch, "Initial commit");
5672                 return;
5673         }
5675         for (i = 0; i < ARRAY_SIZE(paths); i++) {
5676                 char *head = opt_head;
5678                 if (!string_format(buf, "%s/%s", opt_git_dir, paths[i][0]) ||
5679                     lstat(buf, &stat) < 0)
5680                         continue;
5682                 if (!*opt_head) {
5683                         struct io io;
5685                         if (io_open(&io, "%s/rebase-merge/head-name", opt_git_dir) &&
5686                             io_read_buf(&io, buf, sizeof(buf))) {
5687                                 head = buf;
5688                                 if (!prefixcmp(head, "refs/heads/"))
5689                                         head += STRING_SIZE("refs/heads/");
5690                         }
5691                 }
5693                 if (!string_format(status_onbranch, "%s %s", paths[i][1], head))
5694                         string_copy(status_onbranch, opt_head);
5695                 return;
5696         }
5698         string_copy(status_onbranch, "Not currently on any branch");
5701 /* First parse staged info using git-diff-index(1), then parse unstaged
5702  * info using git-diff-files(1), and finally untracked files using
5703  * git-ls-files(1). */
5704 static bool
5705 status_open(struct view *view)
5707         reset_view(view);
5709         add_line_data(view, NULL, LINE_STAT_HEAD);
5710         status_update_onbranch();
5712         io_run_bg(update_index_argv);
5714         if (is_initial_commit()) {
5715                 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
5716                         return FALSE;
5717         } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
5718                 return FALSE;
5719         }
5721         if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
5722             !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
5723                 return FALSE;
5725         /* Restore the exact position or use the specialized restore
5726          * mode? */
5727         if (!view->p_restore)
5728                 status_restore(view);
5729         return TRUE;
5732 static bool
5733 status_draw(struct view *view, struct line *line, unsigned int lineno)
5735         struct status *status = line->data;
5736         enum line_type type;
5737         const char *text;
5739         if (!status) {
5740                 switch (line->type) {
5741                 case LINE_STAT_STAGED:
5742                         type = LINE_STAT_SECTION;
5743                         text = "Changes to be committed:";
5744                         break;
5746                 case LINE_STAT_UNSTAGED:
5747                         type = LINE_STAT_SECTION;
5748                         text = "Changed but not updated:";
5749                         break;
5751                 case LINE_STAT_UNTRACKED:
5752                         type = LINE_STAT_SECTION;
5753                         text = "Untracked files:";
5754                         break;
5756                 case LINE_STAT_NONE:
5757                         type = LINE_DEFAULT;
5758                         text = "  (no files)";
5759                         break;
5761                 case LINE_STAT_HEAD:
5762                         type = LINE_STAT_HEAD;
5763                         text = status_onbranch;
5764                         break;
5766                 default:
5767                         return FALSE;
5768                 }
5769         } else {
5770                 static char buf[] = { '?', ' ', ' ', ' ', 0 };
5772                 buf[0] = status->status;
5773                 if (draw_text(view, line->type, buf, TRUE))
5774                         return TRUE;
5775                 type = LINE_DEFAULT;
5776                 text = status->new.name;
5777         }
5779         draw_text(view, type, text, TRUE);
5780         return TRUE;
5783 static enum request
5784 status_load_error(struct view *view, struct view *stage, const char *path)
5786         if (displayed_views() == 2 || display[current_view] != view)
5787                 maximize_view(view);
5788         report("Failed to load '%s': %s", path, io_strerror(&stage->io));
5789         return REQ_NONE;
5792 static enum request
5793 status_enter(struct view *view, struct line *line)
5795         struct status *status = line->data;
5796         const char *oldpath = status ? status->old.name : NULL;
5797         /* Diffs for unmerged entries are empty when passing the new
5798          * path, so leave it empty. */
5799         const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
5800         const char *info;
5801         enum open_flags split;
5802         struct view *stage = VIEW(REQ_VIEW_STAGE);
5804         if (line->type == LINE_STAT_NONE ||
5805             (!status && line[1].type == LINE_STAT_NONE)) {
5806                 report("No file to diff");
5807                 return REQ_NONE;
5808         }
5810         switch (line->type) {
5811         case LINE_STAT_STAGED:
5812                 if (is_initial_commit()) {
5813                         const char *no_head_diff_argv[] = {
5814                                 "git", "diff", "--no-color", "--patch-with-stat",
5815                                         "--", "/dev/null", newpath, NULL
5816                         };
5818                         if (!prepare_update(stage, no_head_diff_argv, opt_cdup))
5819                                 return status_load_error(view, stage, newpath);
5820                 } else {
5821                         const char *index_show_argv[] = {
5822                                 "git", "diff-index", "--root", "--patch-with-stat",
5823                                         "-C", "-M", "--cached", "HEAD", "--",
5824                                         oldpath, newpath, NULL
5825                         };
5827                         if (!prepare_update(stage, index_show_argv, opt_cdup))
5828                                 return status_load_error(view, stage, newpath);
5829                 }
5831                 if (status)
5832                         info = "Staged changes to %s";
5833                 else
5834                         info = "Staged changes";
5835                 break;
5837         case LINE_STAT_UNSTAGED:
5838         {
5839                 const char *files_show_argv[] = {
5840                         "git", "diff-files", "--root", "--patch-with-stat",
5841                                 "-C", "-M", "--", oldpath, newpath, NULL
5842                 };
5844                 if (!prepare_update(stage, files_show_argv, opt_cdup))
5845                         return status_load_error(view, stage, newpath);
5846                 if (status)
5847                         info = "Unstaged changes to %s";
5848                 else
5849                         info = "Unstaged changes";
5850                 break;
5851         }
5852         case LINE_STAT_UNTRACKED:
5853                 if (!newpath) {
5854                         report("No file to show");
5855                         return REQ_NONE;
5856                 }
5858                 if (!suffixcmp(status->new.name, -1, "/")) {
5859                         report("Cannot display a directory");
5860                         return REQ_NONE;
5861                 }
5863                 if (!prepare_update_file(stage, newpath))
5864                         return status_load_error(view, stage, newpath);
5865                 info = "Untracked file %s";
5866                 break;
5868         case LINE_STAT_HEAD:
5869                 return REQ_NONE;
5871         default:
5872                 die("line type %d not handled in switch", line->type);
5873         }
5875         split = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
5876         open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split);
5877         if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
5878                 if (status) {
5879                         stage_status = *status;
5880                 } else {
5881                         memset(&stage_status, 0, sizeof(stage_status));
5882                 }
5884                 stage_line_type = line->type;
5885                 stage_chunks = 0;
5886                 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
5887         }
5889         return REQ_NONE;
5892 static bool
5893 status_exists(struct status *status, enum line_type type)
5895         struct view *view = VIEW(REQ_VIEW_STATUS);
5896         unsigned long lineno;
5898         for (lineno = 0; lineno < view->lines; lineno++) {
5899                 struct line *line = &view->line[lineno];
5900                 struct status *pos = line->data;
5902                 if (line->type != type)
5903                         continue;
5904                 if (!pos && (!status || !status->status) && line[1].data) {
5905                         select_view_line(view, lineno);
5906                         return TRUE;
5907                 }
5908                 if (pos && !strcmp(status->new.name, pos->new.name)) {
5909                         select_view_line(view, lineno);
5910                         return TRUE;
5911                 }
5912         }
5914         return FALSE;
5918 static bool
5919 status_update_prepare(struct io *io, enum line_type type)
5921         const char *staged_argv[] = {
5922                 "git", "update-index", "-z", "--index-info", NULL
5923         };
5924         const char *others_argv[] = {
5925                 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
5926         };
5928         switch (type) {
5929         case LINE_STAT_STAGED:
5930                 return io_run(io, IO_WR, opt_cdup, staged_argv);
5932         case LINE_STAT_UNSTAGED:
5933         case LINE_STAT_UNTRACKED:
5934                 return io_run(io, IO_WR, opt_cdup, others_argv);
5936         default:
5937                 die("line type %d not handled in switch", type);
5938                 return FALSE;
5939         }
5942 static bool
5943 status_update_write(struct io *io, struct status *status, enum line_type type)
5945         char buf[SIZEOF_STR];
5946         size_t bufsize = 0;
5948         switch (type) {
5949         case LINE_STAT_STAGED:
5950                 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
5951                                         status->old.mode,
5952                                         status->old.rev,
5953                                         status->old.name, 0))
5954                         return FALSE;
5955                 break;
5957         case LINE_STAT_UNSTAGED:
5958         case LINE_STAT_UNTRACKED:
5959                 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
5960                         return FALSE;
5961                 break;
5963         default:
5964                 die("line type %d not handled in switch", type);
5965         }
5967         return io_write(io, buf, bufsize);
5970 static bool
5971 status_update_file(struct status *status, enum line_type type)
5973         struct io io;
5974         bool result;
5976         if (!status_update_prepare(&io, type))
5977                 return FALSE;
5979         result = status_update_write(&io, status, type);
5980         return io_done(&io) && result;
5983 static bool
5984 status_update_files(struct view *view, struct line *line)
5986         char buf[sizeof(view->ref)];
5987         struct io io;
5988         bool result = TRUE;
5989         struct line *pos = view->line + view->lines;
5990         int files = 0;
5991         int file, done;
5992         int cursor_y = -1, cursor_x = -1;
5994         if (!status_update_prepare(&io, line->type))
5995                 return FALSE;
5997         for (pos = line; pos < view->line + view->lines && pos->data; pos++)
5998                 files++;
6000         string_copy(buf, view->ref);
6001         getsyx(cursor_y, cursor_x);
6002         for (file = 0, done = 5; result && file < files; line++, file++) {
6003                 int almost_done = file * 100 / files;
6005                 if (almost_done > done) {
6006                         done = almost_done;
6007                         string_format(view->ref, "updating file %u of %u (%d%% done)",
6008                                       file, files, done);
6009                         update_view_title(view);
6010                         setsyx(cursor_y, cursor_x);
6011                         doupdate();
6012                 }
6013                 result = status_update_write(&io, line->data, line->type);
6014         }
6015         string_copy(view->ref, buf);
6017         return io_done(&io) && result;
6020 static bool
6021 status_update(struct view *view)
6023         struct line *line = &view->line[view->lineno];
6025         assert(view->lines);
6027         if (!line->data) {
6028                 /* This should work even for the "On branch" line. */
6029                 if (line < view->line + view->lines && !line[1].data) {
6030                         report("Nothing to update");
6031                         return FALSE;
6032                 }
6034                 if (!status_update_files(view, line + 1)) {
6035                         report("Failed to update file status");
6036                         return FALSE;
6037                 }
6039         } else if (!status_update_file(line->data, line->type)) {
6040                 report("Failed to update file status");
6041                 return FALSE;
6042         }
6044         return TRUE;
6047 static bool
6048 status_revert(struct status *status, enum line_type type, bool has_none)
6050         if (!status || type != LINE_STAT_UNSTAGED) {
6051                 if (type == LINE_STAT_STAGED) {
6052                         report("Cannot revert changes to staged files");
6053                 } else if (type == LINE_STAT_UNTRACKED) {
6054                         report("Cannot revert changes to untracked files");
6055                 } else if (has_none) {
6056                         report("Nothing to revert");
6057                 } else {
6058                         report("Cannot revert changes to multiple files");
6059                 }
6061         } else if (prompt_yesno("Are you sure you want to revert changes?")) {
6062                 char mode[10] = "100644";
6063                 const char *reset_argv[] = {
6064                         "git", "update-index", "--cacheinfo", mode,
6065                                 status->old.rev, status->old.name, NULL
6066                 };
6067                 const char *checkout_argv[] = {
6068                         "git", "checkout", "--", status->old.name, NULL
6069                 };
6071                 if (status->status == 'U') {
6072                         string_format(mode, "%5o", status->old.mode);
6074                         if (status->old.mode == 0 && status->new.mode == 0) {
6075                                 reset_argv[2] = "--force-remove";
6076                                 reset_argv[3] = status->old.name;
6077                                 reset_argv[4] = NULL;
6078                         }
6080                         if (!io_run_fg(reset_argv, opt_cdup))
6081                                 return FALSE;
6082                         if (status->old.mode == 0 && status->new.mode == 0)
6083                                 return TRUE;
6084                 }
6086                 return io_run_fg(checkout_argv, opt_cdup);
6087         }
6089         return FALSE;
6092 static enum request
6093 status_request(struct view *view, enum request request, struct line *line)
6095         struct status *status = line->data;
6097         switch (request) {
6098         case REQ_STATUS_UPDATE:
6099                 if (!status_update(view))
6100                         return REQ_NONE;
6101                 break;
6103         case REQ_STATUS_REVERT:
6104                 if (!status_revert(status, line->type, status_has_none(view, line)))
6105                         return REQ_NONE;
6106                 break;
6108         case REQ_STATUS_MERGE:
6109                 if (!status || status->status != 'U') {
6110                         report("Merging only possible for files with unmerged status ('U').");
6111                         return REQ_NONE;
6112                 }
6113                 open_mergetool(status->new.name);
6114                 break;
6116         case REQ_EDIT:
6117                 if (!status)
6118                         return request;
6119                 if (status->status == 'D') {
6120                         report("File has been deleted.");
6121                         return REQ_NONE;
6122                 }
6124                 open_editor(status->new.name);
6125                 break;
6127         case REQ_VIEW_BLAME:
6128                 if (status)
6129                         opt_ref[0] = 0;
6130                 return request;
6132         case REQ_ENTER:
6133                 /* After returning the status view has been split to
6134                  * show the stage view. No further reloading is
6135                  * necessary. */
6136                 return status_enter(view, line);
6138         case REQ_REFRESH:
6139                 /* Simply reload the view. */
6140                 break;
6142         default:
6143                 return request;
6144         }
6146         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
6148         return REQ_NONE;
6151 static void
6152 status_select(struct view *view, struct line *line)
6154         struct status *status = line->data;
6155         char file[SIZEOF_STR] = "all files";
6156         const char *text;
6157         const char *key;
6159         if (status && !string_format(file, "'%s'", status->new.name))
6160                 return;
6162         if (!status && line[1].type == LINE_STAT_NONE)
6163                 line++;
6165         switch (line->type) {
6166         case LINE_STAT_STAGED:
6167                 text = "Press %s to unstage %s for commit";
6168                 break;
6170         case LINE_STAT_UNSTAGED:
6171                 text = "Press %s to stage %s for commit";
6172                 break;
6174         case LINE_STAT_UNTRACKED:
6175                 text = "Press %s to stage %s for addition";
6176                 break;
6178         case LINE_STAT_HEAD:
6179         case LINE_STAT_NONE:
6180                 text = "Nothing to update";
6181                 break;
6183         default:
6184                 die("line type %d not handled in switch", line->type);
6185         }
6187         if (status && status->status == 'U') {
6188                 text = "Press %s to resolve conflict in %s";
6189                 key = get_key(KEYMAP_STATUS, REQ_STATUS_MERGE);
6191         } else {
6192                 key = get_key(KEYMAP_STATUS, REQ_STATUS_UPDATE);
6193         }
6195         string_format(view->ref, text, key, file);
6196         if (status)
6197                 string_copy(opt_file, status->new.name);
6200 static bool
6201 status_grep(struct view *view, struct line *line)
6203         struct status *status = line->data;
6205         if (status) {
6206                 const char buf[2] = { status->status, 0 };
6207                 const char *text[] = { status->new.name, buf, NULL };
6209                 return grep_text(view, text);
6210         }
6212         return FALSE;
6215 static struct view_ops status_ops = {
6216         "file",
6217         NULL,
6218         status_open,
6219         NULL,
6220         status_draw,
6221         status_request,
6222         status_grep,
6223         status_select,
6224 };
6227 static bool
6228 stage_diff_write(struct io *io, struct line *line, struct line *end)
6230         while (line < end) {
6231                 if (!io_write(io, line->data, strlen(line->data)) ||
6232                     !io_write(io, "\n", 1))
6233                         return FALSE;
6234                 line++;
6235                 if (line->type == LINE_DIFF_CHUNK ||
6236                     line->type == LINE_DIFF_HEADER)
6237                         break;
6238         }
6240         return TRUE;
6243 static struct line *
6244 stage_diff_find(struct view *view, struct line *line, enum line_type type)
6246         for (; view->line < line; line--)
6247                 if (line->type == type)
6248                         return line;
6250         return NULL;
6253 static bool
6254 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
6256         const char *apply_argv[SIZEOF_ARG] = {
6257                 "git", "apply", "--whitespace=nowarn", NULL
6258         };
6259         struct line *diff_hdr;
6260         struct io io;
6261         int argc = 3;
6263         diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
6264         if (!diff_hdr)
6265                 return FALSE;
6267         if (!revert)
6268                 apply_argv[argc++] = "--cached";
6269         if (revert || stage_line_type == LINE_STAT_STAGED)
6270                 apply_argv[argc++] = "-R";
6271         apply_argv[argc++] = "-";
6272         apply_argv[argc++] = NULL;
6273         if (!io_run(&io, IO_WR, opt_cdup, apply_argv))
6274                 return FALSE;
6276         if (!stage_diff_write(&io, diff_hdr, chunk) ||
6277             !stage_diff_write(&io, chunk, view->line + view->lines))
6278                 chunk = NULL;
6280         io_done(&io);
6281         io_run_bg(update_index_argv);
6283         return chunk ? TRUE : FALSE;
6286 static bool
6287 stage_update(struct view *view, struct line *line)
6289         struct line *chunk = NULL;
6291         if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
6292                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6294         if (chunk) {
6295                 if (!stage_apply_chunk(view, chunk, FALSE)) {
6296                         report("Failed to apply chunk");
6297                         return FALSE;
6298                 }
6300         } else if (!stage_status.status) {
6301                 view = VIEW(REQ_VIEW_STATUS);
6303                 for (line = view->line; line < view->line + view->lines; line++)
6304                         if (line->type == stage_line_type)
6305                                 break;
6307                 if (!status_update_files(view, line + 1)) {
6308                         report("Failed to update files");
6309                         return FALSE;
6310                 }
6312         } else if (!status_update_file(&stage_status, stage_line_type)) {
6313                 report("Failed to update file");
6314                 return FALSE;
6315         }
6317         return TRUE;
6320 static bool
6321 stage_revert(struct view *view, struct line *line)
6323         struct line *chunk = NULL;
6325         if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
6326                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6328         if (chunk) {
6329                 if (!prompt_yesno("Are you sure you want to revert changes?"))
6330                         return FALSE;
6332                 if (!stage_apply_chunk(view, chunk, TRUE)) {
6333                         report("Failed to revert chunk");
6334                         return FALSE;
6335                 }
6336                 return TRUE;
6338         } else {
6339                 return status_revert(stage_status.status ? &stage_status : NULL,
6340                                      stage_line_type, FALSE);
6341         }
6345 static void
6346 stage_next(struct view *view, struct line *line)
6348         int i;
6350         if (!stage_chunks) {
6351                 for (line = view->line; line < view->line + view->lines; line++) {
6352                         if (line->type != LINE_DIFF_CHUNK)
6353                                 continue;
6355                         if (!realloc_ints(&stage_chunk, stage_chunks, 1)) {
6356                                 report("Allocation failure");
6357                                 return;
6358                         }
6360                         stage_chunk[stage_chunks++] = line - view->line;
6361                 }
6362         }
6364         for (i = 0; i < stage_chunks; i++) {
6365                 if (stage_chunk[i] > view->lineno) {
6366                         do_scroll_view(view, stage_chunk[i] - view->lineno);
6367                         report("Chunk %d of %d", i + 1, stage_chunks);
6368                         return;
6369                 }
6370         }
6372         report("No next chunk found");
6375 static enum request
6376 stage_request(struct view *view, enum request request, struct line *line)
6378         switch (request) {
6379         case REQ_STATUS_UPDATE:
6380                 if (!stage_update(view, line))
6381                         return REQ_NONE;
6382                 break;
6384         case REQ_STATUS_REVERT:
6385                 if (!stage_revert(view, line))
6386                         return REQ_NONE;
6387                 break;
6389         case REQ_STAGE_NEXT:
6390                 if (stage_line_type == LINE_STAT_UNTRACKED) {
6391                         report("File is untracked; press %s to add",
6392                                get_key(KEYMAP_STAGE, REQ_STATUS_UPDATE));
6393                         return REQ_NONE;
6394                 }
6395                 stage_next(view, line);
6396                 return REQ_NONE;
6398         case REQ_EDIT:
6399                 if (!stage_status.new.name[0])
6400                         return request;
6401                 if (stage_status.status == 'D') {
6402                         report("File has been deleted.");
6403                         return REQ_NONE;
6404                 }
6406                 open_editor(stage_status.new.name);
6407                 break;
6409         case REQ_REFRESH:
6410                 /* Reload everything ... */
6411                 break;
6413         case REQ_VIEW_BLAME:
6414                 if (stage_status.new.name[0]) {
6415                         string_copy(opt_file, stage_status.new.name);
6416                         opt_ref[0] = 0;
6417                 }
6418                 return request;
6420         case REQ_ENTER:
6421                 return pager_request(view, request, line);
6423         default:
6424                 return request;
6425         }
6427         VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
6428         open_view(view, REQ_VIEW_STATUS, OPEN_REFRESH);
6430         /* Check whether the staged entry still exists, and close the
6431          * stage view if it doesn't. */
6432         if (!status_exists(&stage_status, stage_line_type)) {
6433                 status_restore(VIEW(REQ_VIEW_STATUS));
6434                 return REQ_VIEW_CLOSE;
6435         }
6437         if (stage_line_type == LINE_STAT_UNTRACKED) {
6438                 if (!suffixcmp(stage_status.new.name, -1, "/")) {
6439                         report("Cannot display a directory");
6440                         return REQ_NONE;
6441                 }
6443                 if (!prepare_update_file(view, stage_status.new.name)) {
6444                         report("Failed to open file: %s", strerror(errno));
6445                         return REQ_NONE;
6446                 }
6447         }
6448         open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
6450         return REQ_NONE;
6453 static struct view_ops stage_ops = {
6454         "line",
6455         NULL,
6456         NULL,
6457         pager_read,
6458         pager_draw,
6459         stage_request,
6460         pager_grep,
6461         pager_select,
6462 };
6465 /*
6466  * Revision graph
6467  */
6469 struct commit {
6470         char id[SIZEOF_REV];            /* SHA1 ID. */
6471         char title[128];                /* First line of the commit message. */
6472         const char *author;             /* Author of the commit. */
6473         struct time time;               /* Date from the author ident. */
6474         struct ref_list *refs;          /* Repository references. */
6475         chtype graph[SIZEOF_REVGRAPH];  /* Ancestry chain graphics. */
6476         size_t graph_size;              /* The width of the graph array. */
6477         bool has_parents;               /* Rewritten --parents seen. */
6478 };
6480 /* Size of rev graph with no  "padding" columns */
6481 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
6483 struct rev_graph {
6484         struct rev_graph *prev, *next, *parents;
6485         char rev[SIZEOF_REVITEMS][SIZEOF_REV];
6486         size_t size;
6487         struct commit *commit;
6488         size_t pos;
6489         unsigned int boundary:1;
6490 };
6492 /* Parents of the commit being visualized. */
6493 static struct rev_graph graph_parents[4];
6495 /* The current stack of revisions on the graph. */
6496 static struct rev_graph graph_stacks[4] = {
6497         { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
6498         { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
6499         { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
6500         { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
6501 };
6503 static inline bool
6504 graph_parent_is_merge(struct rev_graph *graph)
6506         return graph->parents->size > 1;
6509 static inline void
6510 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
6512         struct commit *commit = graph->commit;
6514         if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
6515                 commit->graph[commit->graph_size++] = symbol;
6518 static void
6519 clear_rev_graph(struct rev_graph *graph)
6521         graph->boundary = 0;
6522         graph->size = graph->pos = 0;
6523         graph->commit = NULL;
6524         memset(graph->parents, 0, sizeof(*graph->parents));
6527 static void
6528 done_rev_graph(struct rev_graph *graph)
6530         if (graph_parent_is_merge(graph) &&
6531             graph->pos < graph->size - 1 &&
6532             graph->next->size == graph->size + graph->parents->size - 1) {
6533                 size_t i = graph->pos + graph->parents->size - 1;
6535                 graph->commit->graph_size = i * 2;
6536                 while (i < graph->next->size - 1) {
6537                         append_to_rev_graph(graph, ' ');
6538                         append_to_rev_graph(graph, '\\');
6539                         i++;
6540                 }
6541         }
6543         clear_rev_graph(graph);
6546 static void
6547 push_rev_graph(struct rev_graph *graph, const char *parent)
6549         int i;
6551         /* "Collapse" duplicate parents lines.
6552          *
6553          * FIXME: This needs to also update update the drawn graph but
6554          * for now it just serves as a method for pruning graph lines. */
6555         for (i = 0; i < graph->size; i++)
6556                 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
6557                         return;
6559         if (graph->size < SIZEOF_REVITEMS) {
6560                 string_copy_rev(graph->rev[graph->size++], parent);
6561         }
6564 static chtype
6565 get_rev_graph_symbol(struct rev_graph *graph)
6567         chtype symbol;
6569         if (graph->boundary)
6570                 symbol = REVGRAPH_BOUND;
6571         else if (graph->parents->size == 0)
6572                 symbol = REVGRAPH_INIT;
6573         else if (graph_parent_is_merge(graph))
6574                 symbol = REVGRAPH_MERGE;
6575         else if (graph->pos >= graph->size)
6576                 symbol = REVGRAPH_BRANCH;
6577         else
6578                 symbol = REVGRAPH_COMMIT;
6580         return symbol;
6583 static void
6584 draw_rev_graph(struct rev_graph *graph)
6586         struct rev_filler {
6587                 chtype separator, line;
6588         };
6589         enum { DEFAULT, RSHARP, RDIAG, LDIAG };
6590         static struct rev_filler fillers[] = {
6591                 { ' ',  '|' },
6592                 { '`',  '.' },
6593                 { '\'', ' ' },
6594                 { '/',  ' ' },
6595         };
6596         chtype symbol = get_rev_graph_symbol(graph);
6597         struct rev_filler *filler;
6598         size_t i;
6600         fillers[DEFAULT].line = opt_line_graphics ? ACS_VLINE : '|';
6601         filler = &fillers[DEFAULT];
6603         for (i = 0; i < graph->pos; i++) {
6604                 append_to_rev_graph(graph, filler->line);
6605                 if (graph_parent_is_merge(graph->prev) &&
6606                     graph->prev->pos == i)
6607                         filler = &fillers[RSHARP];
6609                 append_to_rev_graph(graph, filler->separator);
6610         }
6612         /* Place the symbol for this revision. */
6613         append_to_rev_graph(graph, symbol);
6615         if (graph->prev->size > graph->size)
6616                 filler = &fillers[RDIAG];
6617         else
6618                 filler = &fillers[DEFAULT];
6620         i++;
6622         for (; i < graph->size; i++) {
6623                 append_to_rev_graph(graph, filler->separator);
6624                 append_to_rev_graph(graph, filler->line);
6625                 if (graph_parent_is_merge(graph->prev) &&
6626                     i < graph->prev->pos + graph->parents->size)
6627                         filler = &fillers[RSHARP];
6628                 if (graph->prev->size > graph->size)
6629                         filler = &fillers[LDIAG];
6630         }
6632         if (graph->prev->size > graph->size) {
6633                 append_to_rev_graph(graph, filler->separator);
6634                 if (filler->line != ' ')
6635                         append_to_rev_graph(graph, filler->line);
6636         }
6639 /* Prepare the next rev graph */
6640 static void
6641 prepare_rev_graph(struct rev_graph *graph)
6643         size_t i;
6645         /* First, traverse all lines of revisions up to the active one. */
6646         for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
6647                 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
6648                         break;
6650                 push_rev_graph(graph->next, graph->rev[graph->pos]);
6651         }
6653         /* Interleave the new revision parent(s). */
6654         for (i = 0; !graph->boundary && i < graph->parents->size; i++)
6655                 push_rev_graph(graph->next, graph->parents->rev[i]);
6657         /* Lastly, put any remaining revisions. */
6658         for (i = graph->pos + 1; i < graph->size; i++)
6659                 push_rev_graph(graph->next, graph->rev[i]);
6662 static void
6663 update_rev_graph(struct view *view, struct rev_graph *graph)
6665         /* If this is the finalizing update ... */
6666         if (graph->commit)
6667                 prepare_rev_graph(graph);
6669         /* Graph visualization needs a one rev look-ahead,
6670          * so the first update doesn't visualize anything. */
6671         if (!graph->prev->commit)
6672                 return;
6674         if (view->lines > 2)
6675                 view->line[view->lines - 3].dirty = 1;
6676         if (view->lines > 1)
6677                 view->line[view->lines - 2].dirty = 1;
6678         draw_rev_graph(graph->prev);
6679         done_rev_graph(graph->prev->prev);
6683 /*
6684  * Main view backend
6685  */
6687 static const char *main_argv[SIZEOF_ARG] = {
6688         "git", "log", "--no-color", "--pretty=raw", "--parents",
6689                       "--topo-order", "%(head)", NULL
6690 };
6692 static bool
6693 main_draw(struct view *view, struct line *line, unsigned int lineno)
6695         struct commit *commit = line->data;
6697         if (!commit->author)
6698                 return FALSE;
6700         if (opt_date && draw_date(view, &commit->time))
6701                 return TRUE;
6703         if (opt_author && draw_author(view, commit->author))
6704                 return TRUE;
6706         if (opt_rev_graph && commit->graph_size &&
6707             draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
6708                 return TRUE;
6710         if (opt_show_refs && commit->refs) {
6711                 size_t i;
6713                 for (i = 0; i < commit->refs->size; i++) {
6714                         struct ref *ref = commit->refs->refs[i];
6715                         enum line_type type;
6717                         if (ref->head)
6718                                 type = LINE_MAIN_HEAD;
6719                         else if (ref->ltag)
6720                                 type = LINE_MAIN_LOCAL_TAG;
6721                         else if (ref->tag)
6722                                 type = LINE_MAIN_TAG;
6723                         else if (ref->tracked)
6724                                 type = LINE_MAIN_TRACKED;
6725                         else if (ref->remote)
6726                                 type = LINE_MAIN_REMOTE;
6727                         else
6728                                 type = LINE_MAIN_REF;
6730                         if (draw_text(view, type, "[", TRUE) ||
6731                             draw_text(view, type, ref->name, TRUE) ||
6732                             draw_text(view, type, "]", TRUE))
6733                                 return TRUE;
6735                         if (draw_text(view, LINE_DEFAULT, " ", TRUE))
6736                                 return TRUE;
6737                 }
6738         }
6740         draw_text(view, LINE_DEFAULT, commit->title, TRUE);
6741         return TRUE;
6744 /* Reads git log --pretty=raw output and parses it into the commit struct. */
6745 static bool
6746 main_read(struct view *view, char *line)
6748         static struct rev_graph *graph = graph_stacks;
6749         enum line_type type;
6750         struct commit *commit;
6752         if (!line) {
6753                 int i;
6755                 if (!view->lines && !view->prev)
6756                         die("No revisions match the given arguments.");
6757                 if (view->lines > 0) {
6758                         commit = view->line[view->lines - 1].data;
6759                         view->line[view->lines - 1].dirty = 1;
6760                         if (!commit->author) {
6761                                 view->lines--;
6762                                 free(commit);
6763                                 graph->commit = NULL;
6764                         }
6765                 }
6766                 update_rev_graph(view, graph);
6768                 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
6769                         clear_rev_graph(&graph_stacks[i]);
6770                 return TRUE;
6771         }
6773         type = get_line_type(line);
6774         if (type == LINE_COMMIT) {
6775                 commit = calloc(1, sizeof(struct commit));
6776                 if (!commit)
6777                         return FALSE;
6779                 line += STRING_SIZE("commit ");
6780                 if (*line == '-') {
6781                         graph->boundary = 1;
6782                         line++;
6783                 }
6785                 string_copy_rev(commit->id, line);
6786                 commit->refs = get_ref_list(commit->id);
6787                 graph->commit = commit;
6788                 add_line_data(view, commit, LINE_MAIN_COMMIT);
6790                 while ((line = strchr(line, ' '))) {
6791                         line++;
6792                         push_rev_graph(graph->parents, line);
6793                         commit->has_parents = TRUE;
6794                 }
6795                 return TRUE;
6796         }
6798         if (!view->lines)
6799                 return TRUE;
6800         commit = view->line[view->lines - 1].data;
6802         switch (type) {
6803         case LINE_PARENT:
6804                 if (commit->has_parents)
6805                         break;
6806                 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
6807                 break;
6809         case LINE_AUTHOR:
6810                 parse_author_line(line + STRING_SIZE("author "),
6811                                   &commit->author, &commit->time);
6812                 update_rev_graph(view, graph);
6813                 graph = graph->next;
6814                 break;
6816         default:
6817                 /* Fill in the commit title if it has not already been set. */
6818                 if (commit->title[0])
6819                         break;
6821                 /* Require titles to start with a non-space character at the
6822                  * offset used by git log. */
6823                 if (strncmp(line, "    ", 4))
6824                         break;
6825                 line += 4;
6826                 /* Well, if the title starts with a whitespace character,
6827                  * try to be forgiving.  Otherwise we end up with no title. */
6828                 while (isspace(*line))
6829                         line++;
6830                 if (*line == '\0')
6831                         break;
6832                 /* FIXME: More graceful handling of titles; append "..." to
6833                  * shortened titles, etc. */
6835                 string_expand(commit->title, sizeof(commit->title), line, 1);
6836                 view->line[view->lines - 1].dirty = 1;
6837         }
6839         return TRUE;
6842 static enum request
6843 main_request(struct view *view, enum request request, struct line *line)
6845         enum open_flags flags = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
6847         switch (request) {
6848         case REQ_ENTER:
6849                 open_view(view, REQ_VIEW_DIFF, flags);
6850                 break;
6851         case REQ_REFRESH:
6852                 load_refs();
6853                 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
6854                 break;
6855         default:
6856                 return request;
6857         }
6859         return REQ_NONE;
6862 static bool
6863 grep_refs(struct ref_list *list, regex_t *regex)
6865         regmatch_t pmatch;
6866         size_t i;
6868         if (!opt_show_refs || !list)
6869                 return FALSE;
6871         for (i = 0; i < list->size; i++) {
6872                 if (regexec(regex, list->refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
6873                         return TRUE;
6874         }
6876         return FALSE;
6879 static bool
6880 main_grep(struct view *view, struct line *line)
6882         struct commit *commit = line->data;
6883         const char *text[] = {
6884                 commit->title,
6885                 opt_author ? commit->author : "",
6886                 mkdate(&commit->time, opt_date),
6887                 NULL
6888         };
6890         return grep_text(view, text) || grep_refs(commit->refs, view->regex);
6893 static void
6894 main_select(struct view *view, struct line *line)
6896         struct commit *commit = line->data;
6898         string_copy_rev(view->ref, commit->id);
6899         string_copy_rev(ref_commit, view->ref);
6902 static struct view_ops main_ops = {
6903         "commit",
6904         main_argv,
6905         NULL,
6906         main_read,
6907         main_draw,
6908         main_request,
6909         main_grep,
6910         main_select,
6911 };
6914 /*
6915  * Status management
6916  */
6918 /* Whether or not the curses interface has been initialized. */
6919 static bool cursed = FALSE;
6921 /* Terminal hacks and workarounds. */
6922 static bool use_scroll_redrawwin;
6923 static bool use_scroll_status_wclear;
6925 /* The status window is used for polling keystrokes. */
6926 static WINDOW *status_win;
6928 /* Reading from the prompt? */
6929 static bool input_mode = FALSE;
6931 static bool status_empty = FALSE;
6933 /* Update status and title window. */
6934 static void
6935 report(const char *msg, ...)
6937         struct view *view = display[current_view];
6939         if (input_mode)
6940                 return;
6942         if (!view) {
6943                 char buf[SIZEOF_STR];
6944                 va_list args;
6946                 va_start(args, msg);
6947                 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
6948                         buf[sizeof(buf) - 1] = 0;
6949                         buf[sizeof(buf) - 2] = '.';
6950                         buf[sizeof(buf) - 3] = '.';
6951                         buf[sizeof(buf) - 4] = '.';
6952                 }
6953                 va_end(args);
6954                 die("%s", buf);
6955         }
6957         if (!status_empty || *msg) {
6958                 va_list args;
6960                 va_start(args, msg);
6962                 wmove(status_win, 0, 0);
6963                 if (view->has_scrolled && use_scroll_status_wclear)
6964                         wclear(status_win);
6965                 if (*msg) {
6966                         vwprintw(status_win, msg, args);
6967                         status_empty = FALSE;
6968                 } else {
6969                         status_empty = TRUE;
6970                 }
6971                 wclrtoeol(status_win);
6972                 wnoutrefresh(status_win);
6974                 va_end(args);
6975         }
6977         update_view_title(view);
6980 static void
6981 init_display(void)
6983         const char *term;
6984         int x, y;
6986         /* Initialize the curses library */
6987         if (isatty(STDIN_FILENO)) {
6988                 cursed = !!initscr();
6989                 opt_tty = stdin;
6990         } else {
6991                 /* Leave stdin and stdout alone when acting as a pager. */
6992                 opt_tty = fopen("/dev/tty", "r+");
6993                 if (!opt_tty)
6994                         die("Failed to open /dev/tty");
6995                 cursed = !!newterm(NULL, opt_tty, opt_tty);
6996         }
6998         if (!cursed)
6999                 die("Failed to initialize curses");
7001         nonl();         /* Disable conversion and detect newlines from input. */
7002         cbreak();       /* Take input chars one at a time, no wait for \n */
7003         noecho();       /* Don't echo input */
7004         leaveok(stdscr, FALSE);
7006         if (has_colors())
7007                 init_colors();
7009         getmaxyx(stdscr, y, x);
7010         status_win = newwin(1, 0, y - 1, 0);
7011         if (!status_win)
7012                 die("Failed to create status window");
7014         /* Enable keyboard mapping */
7015         keypad(status_win, TRUE);
7016         wbkgdset(status_win, get_line_attr(LINE_STATUS));
7018         TABSIZE = opt_tab_size;
7020         term = getenv("XTERM_VERSION") ? NULL : getenv("COLORTERM");
7021         if (term && !strcmp(term, "gnome-terminal")) {
7022                 /* In the gnome-terminal-emulator, the message from
7023                  * scrolling up one line when impossible followed by
7024                  * scrolling down one line causes corruption of the
7025                  * status line. This is fixed by calling wclear. */
7026                 use_scroll_status_wclear = TRUE;
7027                 use_scroll_redrawwin = FALSE;
7029         } else if (term && !strcmp(term, "xrvt-xpm")) {
7030                 /* No problems with full optimizations in xrvt-(unicode)
7031                  * and aterm. */
7032                 use_scroll_status_wclear = use_scroll_redrawwin = FALSE;
7034         } else {
7035                 /* When scrolling in (u)xterm the last line in the
7036                  * scrolling direction will update slowly. */
7037                 use_scroll_redrawwin = TRUE;
7038                 use_scroll_status_wclear = FALSE;
7039         }
7042 static int
7043 get_input(int prompt_position)
7045         struct view *view;
7046         int i, key, cursor_y, cursor_x;
7047         bool loading = FALSE;
7049         if (prompt_position)
7050                 input_mode = TRUE;
7052         while (TRUE) {
7053                 foreach_view (view, i) {
7054                         update_view(view);
7055                         if (view_is_displayed(view) && view->has_scrolled &&
7056                             use_scroll_redrawwin)
7057                                 redrawwin(view->win);
7058                         view->has_scrolled = FALSE;
7059                         if (view->pipe)
7060                                 loading = TRUE;
7061                 }
7063                 /* Update the cursor position. */
7064                 if (prompt_position) {
7065                         getbegyx(status_win, cursor_y, cursor_x);
7066                         cursor_x = prompt_position;
7067                 } else {
7068                         view = display[current_view];
7069                         getbegyx(view->win, cursor_y, cursor_x);
7070                         cursor_x = view->width - 1;
7071                         cursor_y += view->lineno - view->offset;
7072                 }
7073                 setsyx(cursor_y, cursor_x);
7075                 /* Refresh, accept single keystroke of input */
7076                 doupdate();
7077                 nodelay(status_win, loading);
7078                 key = wgetch(status_win);
7080                 /* wgetch() with nodelay() enabled returns ERR when
7081                  * there's no input. */
7082                 if (key == ERR) {
7084                 } else if (key == KEY_RESIZE) {
7085                         int height, width;
7087                         getmaxyx(stdscr, height, width);
7089                         wresize(status_win, 1, width);
7090                         mvwin(status_win, height - 1, 0);
7091                         wnoutrefresh(status_win);
7092                         resize_display();
7093                         redraw_display(TRUE);
7095                 } else {
7096                         input_mode = FALSE;
7097                         return key;
7098                 }
7099         }
7102 static char *
7103 prompt_input(const char *prompt, input_handler handler, void *data)
7105         enum input_status status = INPUT_OK;
7106         static char buf[SIZEOF_STR];
7107         size_t pos = 0;
7109         buf[pos] = 0;
7111         while (status == INPUT_OK || status == INPUT_SKIP) {
7112                 int key;
7114                 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
7115                 wclrtoeol(status_win);
7117                 key = get_input(pos + 1);
7118                 switch (key) {
7119                 case KEY_RETURN:
7120                 case KEY_ENTER:
7121                 case '\n':
7122                         status = pos ? INPUT_STOP : INPUT_CANCEL;
7123                         break;
7125                 case KEY_BACKSPACE:
7126                         if (pos > 0)
7127                                 buf[--pos] = 0;
7128                         else
7129                                 status = INPUT_CANCEL;
7130                         break;
7132                 case KEY_ESC:
7133                         status = INPUT_CANCEL;
7134                         break;
7136                 default:
7137                         if (pos >= sizeof(buf)) {
7138                                 report("Input string too long");
7139                                 return NULL;
7140                         }
7142                         status = handler(data, buf, key);
7143                         if (status == INPUT_OK)
7144                                 buf[pos++] = (char) key;
7145                 }
7146         }
7148         /* Clear the status window */
7149         status_empty = FALSE;
7150         report("");
7152         if (status == INPUT_CANCEL)
7153                 return NULL;
7155         buf[pos++] = 0;
7157         return buf;
7160 static enum input_status
7161 prompt_yesno_handler(void *data, char *buf, int c)
7163         if (c == 'y' || c == 'Y')
7164                 return INPUT_STOP;
7165         if (c == 'n' || c == 'N')
7166                 return INPUT_CANCEL;
7167         return INPUT_SKIP;
7170 static bool
7171 prompt_yesno(const char *prompt)
7173         char prompt2[SIZEOF_STR];
7175         if (!string_format(prompt2, "%s [Yy/Nn]", prompt))
7176                 return FALSE;
7178         return !!prompt_input(prompt2, prompt_yesno_handler, NULL);
7181 static enum input_status
7182 read_prompt_handler(void *data, char *buf, int c)
7184         return isprint(c) ? INPUT_OK : INPUT_SKIP;
7187 static char *
7188 read_prompt(const char *prompt)
7190         return prompt_input(prompt, read_prompt_handler, NULL);
7193 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected)
7195         enum input_status status = INPUT_OK;
7196         int size = 0;
7198         while (items[size].text)
7199                 size++;
7201         while (status == INPUT_OK) {
7202                 const struct menu_item *item = &items[*selected];
7203                 int key;
7204                 int i;
7206                 mvwprintw(status_win, 0, 0, "%s (%d of %d) ",
7207                           prompt, *selected + 1, size);
7208                 if (item->hotkey)
7209                         wprintw(status_win, "[%c] ", (char) item->hotkey);
7210                 wprintw(status_win, "%s", item->text);
7211                 wclrtoeol(status_win);
7213                 key = get_input(COLS - 1);
7214                 switch (key) {
7215                 case KEY_RETURN:
7216                 case KEY_ENTER:
7217                 case '\n':
7218                         status = INPUT_STOP;
7219                         break;
7221                 case KEY_LEFT:
7222                 case KEY_UP:
7223                         *selected = *selected - 1;
7224                         if (*selected < 0)
7225                                 *selected = size - 1;
7226                         break;
7228                 case KEY_RIGHT:
7229                 case KEY_DOWN:
7230                         *selected = (*selected + 1) % size;
7231                         break;
7233                 case KEY_ESC:
7234                         status = INPUT_CANCEL;
7235                         break;
7237                 default:
7238                         for (i = 0; items[i].text; i++)
7239                                 if (items[i].hotkey == key) {
7240                                         *selected = i;
7241                                         status = INPUT_STOP;
7242                                         break;
7243                                 }
7244                 }
7245         }
7247         /* Clear the status window */
7248         status_empty = FALSE;
7249         report("");
7251         return status != INPUT_CANCEL;
7254 /*
7255  * Repository properties
7256  */
7258 static struct ref **refs = NULL;
7259 static size_t refs_size = 0;
7260 static struct ref *refs_head = NULL;
7262 static struct ref_list **ref_lists = NULL;
7263 static size_t ref_lists_size = 0;
7265 DEFINE_ALLOCATOR(realloc_refs, struct ref *, 256)
7266 DEFINE_ALLOCATOR(realloc_refs_list, struct ref *, 8)
7267 DEFINE_ALLOCATOR(realloc_ref_lists, struct ref_list *, 8)
7269 static int
7270 compare_refs(const void *ref1_, const void *ref2_)
7272         const struct ref *ref1 = *(const struct ref **)ref1_;
7273         const struct ref *ref2 = *(const struct ref **)ref2_;
7275         if (ref1->tag != ref2->tag)
7276                 return ref2->tag - ref1->tag;
7277         if (ref1->ltag != ref2->ltag)
7278                 return ref2->ltag - ref2->ltag;
7279         if (ref1->head != ref2->head)
7280                 return ref2->head - ref1->head;
7281         if (ref1->tracked != ref2->tracked)
7282                 return ref2->tracked - ref1->tracked;
7283         if (ref1->remote != ref2->remote)
7284                 return ref2->remote - ref1->remote;
7285         return strcmp(ref1->name, ref2->name);
7288 static void
7289 foreach_ref(bool (*visitor)(void *data, const struct ref *ref), void *data)
7291         size_t i;
7293         for (i = 0; i < refs_size; i++)
7294                 if (!visitor(data, refs[i]))
7295                         break;
7298 static struct ref *
7299 get_ref_head()
7301         return refs_head;
7304 static struct ref_list *
7305 get_ref_list(const char *id)
7307         struct ref_list *list;
7308         size_t i;
7310         for (i = 0; i < ref_lists_size; i++)
7311                 if (!strcmp(id, ref_lists[i]->id))
7312                         return ref_lists[i];
7314         if (!realloc_ref_lists(&ref_lists, ref_lists_size, 1))
7315                 return NULL;
7316         list = calloc(1, sizeof(*list));
7317         if (!list)
7318                 return NULL;
7320         for (i = 0; i < refs_size; i++) {
7321                 if (!strcmp(id, refs[i]->id) &&
7322                     realloc_refs_list(&list->refs, list->size, 1))
7323                         list->refs[list->size++] = refs[i];
7324         }
7326         if (!list->refs) {
7327                 free(list);
7328                 return NULL;
7329         }
7331         qsort(list->refs, list->size, sizeof(*list->refs), compare_refs);
7332         ref_lists[ref_lists_size++] = list;
7333         return list;
7336 static int
7337 read_ref(char *id, size_t idlen, char *name, size_t namelen)
7339         struct ref *ref = NULL;
7340         bool tag = FALSE;
7341         bool ltag = FALSE;
7342         bool remote = FALSE;
7343         bool tracked = FALSE;
7344         bool head = FALSE;
7345         int from = 0, to = refs_size - 1;
7347         if (!prefixcmp(name, "refs/tags/")) {
7348                 if (!suffixcmp(name, namelen, "^{}")) {
7349                         namelen -= 3;
7350                         name[namelen] = 0;
7351                 } else {
7352                         ltag = TRUE;
7353                 }
7355                 tag = TRUE;
7356                 namelen -= STRING_SIZE("refs/tags/");
7357                 name    += STRING_SIZE("refs/tags/");
7359         } else if (!prefixcmp(name, "refs/remotes/")) {
7360                 remote = TRUE;
7361                 namelen -= STRING_SIZE("refs/remotes/");
7362                 name    += STRING_SIZE("refs/remotes/");
7363                 tracked  = !strcmp(opt_remote, name);
7365         } else if (!prefixcmp(name, "refs/heads/")) {
7366                 namelen -= STRING_SIZE("refs/heads/");
7367                 name    += STRING_SIZE("refs/heads/");
7368                 if (!strncmp(opt_head, name, namelen))
7369                         return OK;
7371         } else if (!strcmp(name, "HEAD")) {
7372                 head     = TRUE;
7373                 if (*opt_head) {
7374                         namelen  = strlen(opt_head);
7375                         name     = opt_head;
7376                 }
7377         }
7379         /* If we are reloading or it's an annotated tag, replace the
7380          * previous SHA1 with the resolved commit id; relies on the fact
7381          * git-ls-remote lists the commit id of an annotated tag right
7382          * before the commit id it points to. */
7383         while (from <= to) {
7384                 size_t pos = (to + from) / 2;
7385                 int cmp = strcmp(name, refs[pos]->name);
7387                 if (!cmp) {
7388                         ref = refs[pos];
7389                         break;
7390                 }
7392                 if (cmp < 0)
7393                         to = pos - 1;
7394                 else
7395                         from = pos + 1;
7396         }
7398         if (!ref) {
7399                 if (!realloc_refs(&refs, refs_size, 1))
7400                         return ERR;
7401                 ref = calloc(1, sizeof(*ref) + namelen);
7402                 if (!ref)
7403                         return ERR;
7404                 memmove(refs + from + 1, refs + from,
7405                         (refs_size - from) * sizeof(*refs));
7406                 refs[from] = ref;
7407                 strncpy(ref->name, name, namelen);
7408                 refs_size++;
7409         }
7411         ref->head = head;
7412         ref->tag = tag;
7413         ref->ltag = ltag;
7414         ref->remote = remote;
7415         ref->tracked = tracked;
7416         string_copy_rev(ref->id, id);
7418         if (head)
7419                 refs_head = ref;
7420         return OK;
7423 static int
7424 load_refs(void)
7426         const char *head_argv[] = {
7427                 "git", "symbolic-ref", "HEAD", NULL
7428         };
7429         static const char *ls_remote_argv[SIZEOF_ARG] = {
7430                 "git", "ls-remote", opt_git_dir, NULL
7431         };
7432         static bool init = FALSE;
7433         size_t i;
7435         if (!init) {
7436                 if (!argv_from_env(ls_remote_argv, "TIG_LS_REMOTE"))
7437                         die("TIG_LS_REMOTE contains too many arguments");
7438                 init = TRUE;
7439         }
7441         if (!*opt_git_dir)
7442                 return OK;
7444         if (io_run_buf(head_argv, opt_head, sizeof(opt_head)) &&
7445             !prefixcmp(opt_head, "refs/heads/")) {
7446                 char *offset = opt_head + STRING_SIZE("refs/heads/");
7448                 memmove(opt_head, offset, strlen(offset) + 1);
7449         }
7451         refs_head = NULL;
7452         for (i = 0; i < refs_size; i++)
7453                 refs[i]->id[0] = 0;
7455         if (io_run_load(ls_remote_argv, "\t", read_ref) == ERR)
7456                 return ERR;
7458         /* Update the ref lists to reflect changes. */
7459         for (i = 0; i < ref_lists_size; i++) {
7460                 struct ref_list *list = ref_lists[i];
7461                 size_t old, new;
7463                 for (old = new = 0; old < list->size; old++)
7464                         if (!strcmp(list->id, list->refs[old]->id))
7465                                 list->refs[new++] = list->refs[old];
7466                 list->size = new;
7467         }
7469         return OK;
7472 static void
7473 set_remote_branch(const char *name, const char *value, size_t valuelen)
7475         if (!strcmp(name, ".remote")) {
7476                 string_ncopy(opt_remote, value, valuelen);
7478         } else if (*opt_remote && !strcmp(name, ".merge")) {
7479                 size_t from = strlen(opt_remote);
7481                 if (!prefixcmp(value, "refs/heads/"))
7482                         value += STRING_SIZE("refs/heads/");
7484                 if (!string_format_from(opt_remote, &from, "/%s", value))
7485                         opt_remote[0] = 0;
7486         }
7489 static void
7490 set_repo_config_option(char *name, char *value, int (*cmd)(int, const char **))
7492         const char *argv[SIZEOF_ARG] = { name, "=" };
7493         int argc = 1 + (cmd == option_set_command);
7494         int error = ERR;
7496         if (!argv_from_string(argv, &argc, value))
7497                 config_msg = "Too many option arguments";
7498         else
7499                 error = cmd(argc, argv);
7501         if (error == ERR)
7502                 warn("Option 'tig.%s': %s", name, config_msg);
7505 static bool
7506 set_environment_variable(const char *name, const char *value)
7508         size_t len = strlen(name) + 1 + strlen(value) + 1;
7509         char *env = malloc(len);
7511         if (env &&
7512             string_nformat(env, len, NULL, "%s=%s", name, value) &&
7513             putenv(env) == 0)
7514                 return TRUE;
7515         free(env);
7516         return FALSE;
7519 static void
7520 set_work_tree(const char *value)
7522         char cwd[SIZEOF_STR];
7524         if (!getcwd(cwd, sizeof(cwd)))
7525                 die("Failed to get cwd path: %s", strerror(errno));
7526         if (chdir(opt_git_dir) < 0)
7527                 die("Failed to chdir(%s): %s", strerror(errno));
7528         if (!getcwd(opt_git_dir, sizeof(opt_git_dir)))
7529                 die("Failed to get git path: %s", strerror(errno));
7530         if (chdir(cwd) < 0)
7531                 die("Failed to chdir(%s): %s", cwd, strerror(errno));
7532         if (chdir(value) < 0)
7533                 die("Failed to chdir(%s): %s", value, strerror(errno));
7534         if (!getcwd(cwd, sizeof(cwd)))
7535                 die("Failed to get cwd path: %s", strerror(errno));
7536         if (!set_environment_variable("GIT_WORK_TREE", cwd))
7537                 die("Failed to set GIT_WORK_TREE to '%s'", cwd);
7538         if (!set_environment_variable("GIT_DIR", opt_git_dir))
7539                 die("Failed to set GIT_DIR to '%s'", opt_git_dir);
7540         opt_is_inside_work_tree = TRUE;
7543 static int
7544 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
7546         if (!strcmp(name, "i18n.commitencoding"))
7547                 string_ncopy(opt_encoding, value, valuelen);
7549         else if (!strcmp(name, "core.editor"))
7550                 string_ncopy(opt_editor, value, valuelen);
7552         else if (!strcmp(name, "core.worktree"))
7553                 set_work_tree(value);
7555         else if (!prefixcmp(name, "tig.color."))
7556                 set_repo_config_option(name + 10, value, option_color_command);
7558         else if (!prefixcmp(name, "tig.bind."))
7559                 set_repo_config_option(name + 9, value, option_bind_command);
7561         else if (!prefixcmp(name, "tig."))
7562                 set_repo_config_option(name + 4, value, option_set_command);
7564         else if (*opt_head && !prefixcmp(name, "branch.") &&
7565                  !strncmp(name + 7, opt_head, strlen(opt_head)))
7566                 set_remote_branch(name + 7 + strlen(opt_head), value, valuelen);
7568         return OK;
7571 static int
7572 load_git_config(void)
7574         const char *config_list_argv[] = { "git", "config", "--list", NULL };
7576         return io_run_load(config_list_argv, "=", read_repo_config_option);
7579 static int
7580 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
7582         if (!opt_git_dir[0]) {
7583                 string_ncopy(opt_git_dir, name, namelen);
7585         } else if (opt_is_inside_work_tree == -1) {
7586                 /* This can be 3 different values depending on the
7587                  * version of git being used. If git-rev-parse does not
7588                  * understand --is-inside-work-tree it will simply echo
7589                  * the option else either "true" or "false" is printed.
7590                  * Default to true for the unknown case. */
7591                 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
7593         } else if (*name == '.') {
7594                 string_ncopy(opt_cdup, name, namelen);
7596         } else {
7597                 string_ncopy(opt_prefix, name, namelen);
7598         }
7600         return OK;
7603 static int
7604 load_repo_info(void)
7606         const char *rev_parse_argv[] = {
7607                 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
7608                         "--show-cdup", "--show-prefix", NULL
7609         };
7611         return io_run_load(rev_parse_argv, "=", read_repo_info);
7615 /*
7616  * Main
7617  */
7619 static const char usage[] =
7620 "tig " TIG_VERSION " (" __DATE__ ")\n"
7621 "\n"
7622 "Usage: tig        [options] [revs] [--] [paths]\n"
7623 "   or: tig show   [options] [revs] [--] [paths]\n"
7624 "   or: tig blame  [rev] path\n"
7625 "   or: tig status\n"
7626 "   or: tig <      [git command output]\n"
7627 "\n"
7628 "Options:\n"
7629 "  -v, --version   Show version and exit\n"
7630 "  -h, --help      Show help message and exit";
7632 static void __NORETURN
7633 quit(int sig)
7635         /* XXX: Restore tty modes and let the OS cleanup the rest! */
7636         if (cursed)
7637                 endwin();
7638         exit(0);
7641 static void __NORETURN
7642 die(const char *err, ...)
7644         va_list args;
7646         endwin();
7648         va_start(args, err);
7649         fputs("tig: ", stderr);
7650         vfprintf(stderr, err, args);
7651         fputs("\n", stderr);
7652         va_end(args);
7654         exit(1);
7657 static void
7658 warn(const char *msg, ...)
7660         va_list args;
7662         va_start(args, msg);
7663         fputs("tig warning: ", stderr);
7664         vfprintf(stderr, msg, args);
7665         fputs("\n", stderr);
7666         va_end(args);
7669 static enum request
7670 parse_options(int argc, const char *argv[])
7672         enum request request = REQ_VIEW_MAIN;
7673         const char *subcommand;
7674         bool seen_dashdash = FALSE;
7675         /* XXX: This is vulnerable to the user overriding options
7676          * required for the main view parser. */
7677         const char *custom_argv[SIZEOF_ARG] = {
7678                 "git", "log", "--no-color", "--pretty=raw", "--parents",
7679                         "--topo-order", NULL
7680         };
7681         int i, j = 6;
7683         if (!isatty(STDIN_FILENO)) {
7684                 io_open(&VIEW(REQ_VIEW_PAGER)->io, "");
7685                 return REQ_VIEW_PAGER;
7686         }
7688         if (argc <= 1)
7689                 return REQ_NONE;
7691         subcommand = argv[1];
7692         if (!strcmp(subcommand, "status")) {
7693                 if (argc > 2)
7694                         warn("ignoring arguments after `%s'", subcommand);
7695                 return REQ_VIEW_STATUS;
7697         } else if (!strcmp(subcommand, "blame")) {
7698                 if (argc <= 2 || argc > 4)
7699                         die("invalid number of options to blame\n\n%s", usage);
7701                 i = 2;
7702                 if (argc == 4) {
7703                         string_ncopy(opt_ref, argv[i], strlen(argv[i]));
7704                         i++;
7705                 }
7707                 string_ncopy(opt_file, argv[i], strlen(argv[i]));
7708                 return REQ_VIEW_BLAME;
7710         } else if (!strcmp(subcommand, "show")) {
7711                 request = REQ_VIEW_DIFF;
7713         } else {
7714                 subcommand = NULL;
7715         }
7717         if (subcommand) {
7718                 custom_argv[1] = subcommand;
7719                 j = 2;
7720         }
7722         for (i = 1 + !!subcommand; i < argc; i++) {
7723                 const char *opt = argv[i];
7725                 if (seen_dashdash || !strcmp(opt, "--")) {
7726                         seen_dashdash = TRUE;
7728                 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
7729                         printf("tig version %s\n", TIG_VERSION);
7730                         quit(0);
7732                 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
7733                         printf("%s\n", usage);
7734                         quit(0);
7735                 }
7737                 custom_argv[j++] = opt;
7738                 if (j >= ARRAY_SIZE(custom_argv))
7739                         die("command too long");
7740         }
7742         if (!prepare_update(VIEW(request), custom_argv, NULL))
7743                 die("Failed to format arguments");
7745         return request;
7748 int
7749 main(int argc, const char *argv[])
7751         const char *codeset = "UTF-8";
7752         enum request request = parse_options(argc, argv);
7753         struct view *view;
7754         size_t i;
7756         signal(SIGINT, quit);
7757         signal(SIGPIPE, SIG_IGN);
7759         if (setlocale(LC_ALL, "")) {
7760                 codeset = nl_langinfo(CODESET);
7761         }
7763         if (load_repo_info() == ERR)
7764                 die("Failed to load repo info.");
7766         if (load_options() == ERR)
7767                 die("Failed to load user config.");
7769         if (load_git_config() == ERR)
7770                 die("Failed to load repo config.");
7772         /* Require a git repository unless when running in pager mode. */
7773         if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
7774                 die("Not a git repository");
7776         if (*opt_encoding && strcmp(codeset, "UTF-8")) {
7777                 opt_iconv_in = iconv_open("UTF-8", opt_encoding);
7778                 if (opt_iconv_in == ICONV_NONE)
7779                         die("Failed to initialize character set conversion");
7780         }
7782         if (codeset && strcmp(codeset, "UTF-8")) {
7783                 opt_iconv_out = iconv_open(codeset, "UTF-8");
7784                 if (opt_iconv_out == ICONV_NONE)
7785                         die("Failed to initialize character set conversion");
7786         }
7788         if (load_refs() == ERR)
7789                 die("Failed to load refs.");
7791         foreach_view (view, i)
7792                 if (!argv_from_env(view->ops->argv, view->cmd_env))
7793                         die("Too many arguments in the `%s` environment variable",
7794                             view->cmd_env);
7796         init_display();
7798         if (request != REQ_NONE)
7799                 open_view(NULL, request, OPEN_PREPARED);
7800         request = request == REQ_NONE ? REQ_VIEW_MAIN : REQ_NONE;
7802         while (view_driver(display[current_view], request)) {
7803                 int key = get_input(0);
7805                 view = display[current_view];
7806                 request = get_keybinding(view->keymap, key);
7808                 /* Some low-level request handling. This keeps access to
7809                  * status_win restricted. */
7810                 switch (request) {
7811                 case REQ_NONE:
7812                         report("Unknown key, press %s for help",
7813                                get_key(view->keymap, REQ_VIEW_HELP));
7814                         break;
7815                 case REQ_PROMPT:
7816                 {
7817                         char *cmd = read_prompt(":");
7819                         if (cmd && isdigit(*cmd)) {
7820                                 int lineno = view->lineno + 1;
7822                                 if (parse_int(&lineno, cmd, 1, view->lines + 1) == OK) {
7823                                         select_view_line(view, lineno - 1);
7824                                         report("");
7825                                 } else {
7826                                         report("Unable to parse '%s' as a line number", cmd);
7827                                 }
7829                         } else if (cmd) {
7830                                 struct view *next = VIEW(REQ_VIEW_PAGER);
7831                                 const char *argv[SIZEOF_ARG] = { "git" };
7832                                 int argc = 1;
7834                                 /* When running random commands, initially show the
7835                                  * command in the title. However, it maybe later be
7836                                  * overwritten if a commit line is selected. */
7837                                 string_ncopy(next->ref, cmd, strlen(cmd));
7839                                 if (!argv_from_string(argv, &argc, cmd)) {
7840                                         report("Too many arguments");
7841                                 } else if (!prepare_update(next, argv, NULL)) {
7842                                         report("Failed to format command");
7843                                 } else {
7844                                         open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
7845                                 }
7846                         }
7848                         request = REQ_NONE;
7849                         break;
7850                 }
7851                 case REQ_SEARCH:
7852                 case REQ_SEARCH_BACK:
7853                 {
7854                         const char *prompt = request == REQ_SEARCH ? "/" : "?";
7855                         char *search = read_prompt(prompt);
7857                         if (search)
7858                                 string_ncopy(opt_search, search, strlen(search));
7859                         else if (*opt_search)
7860                                 request = request == REQ_SEARCH ?
7861                                         REQ_FIND_NEXT :
7862                                         REQ_FIND_PREV;
7863                         else
7864                                 request = REQ_NONE;
7865                         break;
7866                 }
7867                 default:
7868                         break;
7869                 }
7870         }
7872         quit(0);
7874         return 0;