Code

Cleanup initialization of built-in run requests
[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         if (!argv)
680                 return;
681         for (argc = 0; argv[argc]; argc++)
682                 free((void *) argv[argc]);
683         argv[0] = NULL;
686 DEFINE_ALLOCATOR(argv_realloc, const char *, SIZEOF_ARG)
688 static bool
689 argv_append(const char ***argv, const char *arg)
691         int argc = 0;
693         while (*argv && (*argv)[argc])
694                 argc++;
696         if (!argv_realloc(argv, argc, 2))
697                 return FALSE;
699         (*argv)[argc++] = strdup(arg);
700         (*argv)[argc] = NULL;
701         return TRUE;
704 static bool
705 argv_copy(const char ***dst, const char *src[])
707         int argc;
709         for (argc = 0; src[argc]; argc++)
710                 if (!argv_append(dst, src[argc]))
711                         return FALSE;
712         return TRUE;
716 /*
717  * Executing external commands.
718  */
720 enum io_type {
721         IO_FD,                  /* File descriptor based IO. */
722         IO_BG,                  /* Execute command in the background. */
723         IO_FG,                  /* Execute command with same std{in,out,err}. */
724         IO_RD,                  /* Read only fork+exec IO. */
725         IO_WR,                  /* Write only fork+exec IO. */
726         IO_AP,                  /* Append fork+exec output to file. */
727 };
729 struct io {
730         int pipe;               /* Pipe end for reading or writing. */
731         pid_t pid;              /* PID of spawned process. */
732         int error;              /* Error status. */
733         char *buf;              /* Read buffer. */
734         size_t bufalloc;        /* Allocated buffer size. */
735         size_t bufsize;         /* Buffer content size. */
736         char *bufpos;           /* Current buffer position. */
737         unsigned int eof:1;     /* Has end of file been reached. */
738 };
740 static void
741 io_init(struct io *io)
743         memset(io, 0, sizeof(*io));
744         io->pipe = -1;
747 static bool
748 io_open(struct io *io, const char *fmt, ...)
750         char name[SIZEOF_STR] = "";
751         bool fits;
752         va_list args;
754         io_init(io);
756         va_start(args, fmt);
757         fits = vsnprintf(name, sizeof(name), fmt, args) < sizeof(name);
758         va_end(args);
760         if (!fits) {
761                 io->error = ENAMETOOLONG;
762                 return FALSE;
763         }
764         io->pipe = *name ? open(name, O_RDONLY) : STDIN_FILENO;
765         if (io->pipe == -1)
766                 io->error = errno;
767         return io->pipe != -1;
770 static bool
771 io_kill(struct io *io)
773         return io->pid == 0 || kill(io->pid, SIGKILL) != -1;
776 static bool
777 io_done(struct io *io)
779         pid_t pid = io->pid;
781         if (io->pipe != -1)
782                 close(io->pipe);
783         free(io->buf);
784         io_init(io);
786         while (pid > 0) {
787                 int status;
788                 pid_t waiting = waitpid(pid, &status, 0);
790                 if (waiting < 0) {
791                         if (errno == EINTR)
792                                 continue;
793                         io->error = errno;
794                         return FALSE;
795                 }
797                 return waiting == pid &&
798                        !WIFSIGNALED(status) &&
799                        WIFEXITED(status) &&
800                        !WEXITSTATUS(status);
801         }
803         return TRUE;
806 static bool
807 io_run(struct io *io, enum io_type type, const char *dir, const char *argv[], ...)
809         int pipefds[2] = { -1, -1 };
810         va_list args;
812         io_init(io);
814         if ((type == IO_RD || type == IO_WR) && pipe(pipefds) < 0) {
815                 io->error = errno;
816                 return FALSE;
817         } else if (type == IO_AP) {
818                 va_start(args, argv);
819                 pipefds[1] = va_arg(args, int);
820                 va_end(args);
821         }
823         if ((io->pid = fork())) {
824                 if (io->pid == -1)
825                         io->error = errno;
826                 if (pipefds[!(type == IO_WR)] != -1)
827                         close(pipefds[!(type == IO_WR)]);
828                 if (io->pid != -1) {
829                         io->pipe = pipefds[!!(type == IO_WR)];
830                         return TRUE;
831                 }
833         } else {
834                 if (type != IO_FG) {
835                         int devnull = open("/dev/null", O_RDWR);
836                         int readfd  = type == IO_WR ? pipefds[0] : devnull;
837                         int writefd = (type == IO_RD || type == IO_AP)
838                                                         ? pipefds[1] : devnull;
840                         dup2(readfd,  STDIN_FILENO);
841                         dup2(writefd, STDOUT_FILENO);
842                         dup2(devnull, STDERR_FILENO);
844                         close(devnull);
845                         if (pipefds[0] != -1)
846                                 close(pipefds[0]);
847                         if (pipefds[1] != -1)
848                                 close(pipefds[1]);
849                 }
851                 if (dir && *dir && chdir(dir) == -1)
852                         exit(errno);
854                 execvp(argv[0], (char *const*) argv);
855                 exit(errno);
856         }
858         if (pipefds[!!(type == IO_WR)] != -1)
859                 close(pipefds[!!(type == IO_WR)]);
860         return FALSE;
863 static bool
864 io_complete(enum io_type type, const char **argv, const char *dir, int fd)
866         struct io io;
868         return io_run(&io, type, dir, argv, fd) && io_done(&io);
871 static bool
872 io_run_bg(const char **argv)
874         return io_complete(IO_BG, argv, NULL, -1);
877 static bool
878 io_run_fg(const char **argv, const char *dir)
880         return io_complete(IO_FG, argv, dir, -1);
883 static bool
884 io_run_append(const char **argv, int fd)
886         return io_complete(IO_AP, argv, NULL, fd);
889 static bool
890 io_eof(struct io *io)
892         return io->eof;
895 static int
896 io_error(struct io *io)
898         return io->error;
901 static char *
902 io_strerror(struct io *io)
904         return strerror(io->error);
907 static bool
908 io_can_read(struct io *io)
910         struct timeval tv = { 0, 500 };
911         fd_set fds;
913         FD_ZERO(&fds);
914         FD_SET(io->pipe, &fds);
916         return select(io->pipe + 1, &fds, NULL, NULL, &tv) > 0;
919 static ssize_t
920 io_read(struct io *io, void *buf, size_t bufsize)
922         do {
923                 ssize_t readsize = read(io->pipe, buf, bufsize);
925                 if (readsize < 0 && (errno == EAGAIN || errno == EINTR))
926                         continue;
927                 else if (readsize == -1)
928                         io->error = errno;
929                 else if (readsize == 0)
930                         io->eof = 1;
931                 return readsize;
932         } while (1);
935 DEFINE_ALLOCATOR(io_realloc_buf, char, BUFSIZ)
937 static char *
938 io_get(struct io *io, int c, bool can_read)
940         char *eol;
941         ssize_t readsize;
943         while (TRUE) {
944                 if (io->bufsize > 0) {
945                         eol = memchr(io->bufpos, c, io->bufsize);
946                         if (eol) {
947                                 char *line = io->bufpos;
949                                 *eol = 0;
950                                 io->bufpos = eol + 1;
951                                 io->bufsize -= io->bufpos - line;
952                                 return line;
953                         }
954                 }
956                 if (io_eof(io)) {
957                         if (io->bufsize) {
958                                 io->bufpos[io->bufsize] = 0;
959                                 io->bufsize = 0;
960                                 return io->bufpos;
961                         }
962                         return NULL;
963                 }
965                 if (!can_read)
966                         return NULL;
968                 if (io->bufsize > 0 && io->bufpos > io->buf)
969                         memmove(io->buf, io->bufpos, io->bufsize);
971                 if (io->bufalloc == io->bufsize) {
972                         if (!io_realloc_buf(&io->buf, io->bufalloc, BUFSIZ))
973                                 return NULL;
974                         io->bufalloc += BUFSIZ;
975                 }
977                 io->bufpos = io->buf;
978                 readsize = io_read(io, io->buf + io->bufsize, io->bufalloc - io->bufsize);
979                 if (io_error(io))
980                         return NULL;
981                 io->bufsize += readsize;
982         }
985 static bool
986 io_write(struct io *io, const void *buf, size_t bufsize)
988         size_t written = 0;
990         while (!io_error(io) && written < bufsize) {
991                 ssize_t size;
993                 size = write(io->pipe, buf + written, bufsize - written);
994                 if (size < 0 && (errno == EAGAIN || errno == EINTR))
995                         continue;
996                 else if (size == -1)
997                         io->error = errno;
998                 else
999                         written += size;
1000         }
1002         return written == bufsize;
1005 static bool
1006 io_read_buf(struct io *io, char buf[], size_t bufsize)
1008         char *result = io_get(io, '\n', TRUE);
1010         if (result) {
1011                 result = chomp_string(result);
1012                 string_ncopy_do(buf, bufsize, result, strlen(result));
1013         }
1015         return io_done(io) && result;
1018 static bool
1019 io_run_buf(const char **argv, char buf[], size_t bufsize)
1021         struct io io;
1023         return io_run(&io, IO_RD, NULL, argv) && io_read_buf(&io, buf, bufsize);
1026 static int
1027 io_load(struct io *io, const char *separators,
1028         int (*read_property)(char *, size_t, char *, size_t))
1030         char *name;
1031         int state = OK;
1033         while (state == OK && (name = io_get(io, '\n', TRUE))) {
1034                 char *value;
1035                 size_t namelen;
1036                 size_t valuelen;
1038                 name = chomp_string(name);
1039                 namelen = strcspn(name, separators);
1041                 if (name[namelen]) {
1042                         name[namelen] = 0;
1043                         value = chomp_string(name + namelen + 1);
1044                         valuelen = strlen(value);
1046                 } else {
1047                         value = "";
1048                         valuelen = 0;
1049                 }
1051                 state = read_property(name, namelen, value, valuelen);
1052         }
1054         if (state != ERR && io_error(io))
1055                 state = ERR;
1056         io_done(io);
1058         return state;
1061 static int
1062 io_run_load(const char **argv, const char *separators,
1063             int (*read_property)(char *, size_t, char *, size_t))
1065         struct io io;
1067         if (!io_run(&io, IO_RD, NULL, argv))
1068                 return ERR;
1069         return io_load(&io, separators, read_property);
1073 /*
1074  * User requests
1075  */
1077 #define REQ_INFO \
1078         /* XXX: Keep the view request first and in sync with views[]. */ \
1079         REQ_GROUP("View switching") \
1080         REQ_(VIEW_MAIN,         "Show main view"), \
1081         REQ_(VIEW_DIFF,         "Show diff view"), \
1082         REQ_(VIEW_LOG,          "Show log view"), \
1083         REQ_(VIEW_TREE,         "Show tree view"), \
1084         REQ_(VIEW_BLOB,         "Show blob view"), \
1085         REQ_(VIEW_BLAME,        "Show blame view"), \
1086         REQ_(VIEW_BRANCH,       "Show branch view"), \
1087         REQ_(VIEW_HELP,         "Show help page"), \
1088         REQ_(VIEW_PAGER,        "Show pager view"), \
1089         REQ_(VIEW_STATUS,       "Show status view"), \
1090         REQ_(VIEW_STAGE,        "Show stage view"), \
1091         \
1092         REQ_GROUP("View manipulation") \
1093         REQ_(ENTER,             "Enter current line and scroll"), \
1094         REQ_(NEXT,              "Move to next"), \
1095         REQ_(PREVIOUS,          "Move to previous"), \
1096         REQ_(PARENT,            "Move to parent"), \
1097         REQ_(VIEW_NEXT,         "Move focus to next view"), \
1098         REQ_(REFRESH,           "Reload and refresh"), \
1099         REQ_(MAXIMIZE,          "Maximize the current view"), \
1100         REQ_(VIEW_CLOSE,        "Close the current view"), \
1101         REQ_(QUIT,              "Close all views and quit"), \
1102         \
1103         REQ_GROUP("View specific requests") \
1104         REQ_(STATUS_UPDATE,     "Update file status"), \
1105         REQ_(STATUS_REVERT,     "Revert file changes"), \
1106         REQ_(STATUS_MERGE,      "Merge file using external tool"), \
1107         REQ_(STAGE_NEXT,        "Find next chunk to stage"), \
1108         \
1109         REQ_GROUP("Cursor navigation") \
1110         REQ_(MOVE_UP,           "Move cursor one line up"), \
1111         REQ_(MOVE_DOWN,         "Move cursor one line down"), \
1112         REQ_(MOVE_PAGE_DOWN,    "Move cursor one page down"), \
1113         REQ_(MOVE_PAGE_UP,      "Move cursor one page up"), \
1114         REQ_(MOVE_FIRST_LINE,   "Move cursor to first line"), \
1115         REQ_(MOVE_LAST_LINE,    "Move cursor to last line"), \
1116         \
1117         REQ_GROUP("Scrolling") \
1118         REQ_(SCROLL_LEFT,       "Scroll two columns left"), \
1119         REQ_(SCROLL_RIGHT,      "Scroll two columns right"), \
1120         REQ_(SCROLL_LINE_UP,    "Scroll one line up"), \
1121         REQ_(SCROLL_LINE_DOWN,  "Scroll one line down"), \
1122         REQ_(SCROLL_PAGE_UP,    "Scroll one page up"), \
1123         REQ_(SCROLL_PAGE_DOWN,  "Scroll one page down"), \
1124         \
1125         REQ_GROUP("Searching") \
1126         REQ_(SEARCH,            "Search the view"), \
1127         REQ_(SEARCH_BACK,       "Search backwards in the view"), \
1128         REQ_(FIND_NEXT,         "Find next search match"), \
1129         REQ_(FIND_PREV,         "Find previous search match"), \
1130         \
1131         REQ_GROUP("Option manipulation") \
1132         REQ_(OPTIONS,           "Open option menu"), \
1133         REQ_(TOGGLE_LINENO,     "Toggle line numbers"), \
1134         REQ_(TOGGLE_DATE,       "Toggle date display"), \
1135         REQ_(TOGGLE_DATE_SHORT, "Toggle short (date-only) dates"), \
1136         REQ_(TOGGLE_AUTHOR,     "Toggle author display"), \
1137         REQ_(TOGGLE_REV_GRAPH,  "Toggle revision graph visualization"), \
1138         REQ_(TOGGLE_REFS,       "Toggle reference display (tags/branches)"), \
1139         REQ_(TOGGLE_SORT_ORDER, "Toggle ascending/descending sort order"), \
1140         REQ_(TOGGLE_SORT_FIELD, "Toggle field to sort by"), \
1141         \
1142         REQ_GROUP("Misc") \
1143         REQ_(PROMPT,            "Bring up the prompt"), \
1144         REQ_(SCREEN_REDRAW,     "Redraw the screen"), \
1145         REQ_(SHOW_VERSION,      "Show version information"), \
1146         REQ_(STOP_LOADING,      "Stop all loading views"), \
1147         REQ_(EDIT,              "Open in editor"), \
1148         REQ_(NONE,              "Do nothing")
1151 /* User action requests. */
1152 enum request {
1153 #define REQ_GROUP(help)
1154 #define REQ_(req, help) REQ_##req
1156         /* Offset all requests to avoid conflicts with ncurses getch values. */
1157         REQ_UNKNOWN = KEY_MAX + 1,
1158         REQ_OFFSET,
1159         REQ_INFO
1161 #undef  REQ_GROUP
1162 #undef  REQ_
1163 };
1165 struct request_info {
1166         enum request request;
1167         const char *name;
1168         int namelen;
1169         const char *help;
1170 };
1172 static const struct request_info req_info[] = {
1173 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
1174 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
1175         REQ_INFO
1176 #undef  REQ_GROUP
1177 #undef  REQ_
1178 };
1180 static enum request
1181 get_request(const char *name)
1183         int namelen = strlen(name);
1184         int i;
1186         for (i = 0; i < ARRAY_SIZE(req_info); i++)
1187                 if (enum_equals(req_info[i], name, namelen))
1188                         return req_info[i].request;
1190         return REQ_UNKNOWN;
1194 /*
1195  * Options
1196  */
1198 /* Option and state variables. */
1199 static enum date opt_date               = DATE_DEFAULT;
1200 static enum author opt_author           = AUTHOR_DEFAULT;
1201 static bool opt_line_number             = FALSE;
1202 static bool opt_line_graphics           = TRUE;
1203 static bool opt_rev_graph               = FALSE;
1204 static bool opt_show_refs               = TRUE;
1205 static int opt_num_interval             = 5;
1206 static double opt_hscroll               = 0.50;
1207 static double opt_scale_split_view      = 2.0 / 3.0;
1208 static int opt_tab_size                 = 8;
1209 static int opt_author_cols              = AUTHOR_COLS;
1210 static char opt_path[SIZEOF_STR]        = "";
1211 static char opt_file[SIZEOF_STR]        = "";
1212 static char opt_ref[SIZEOF_REF]         = "";
1213 static char opt_head[SIZEOF_REF]        = "";
1214 static char opt_remote[SIZEOF_REF]      = "";
1215 static char opt_encoding[20]            = "UTF-8";
1216 static iconv_t opt_iconv_in             = ICONV_NONE;
1217 static iconv_t opt_iconv_out            = ICONV_NONE;
1218 static char opt_search[SIZEOF_STR]      = "";
1219 static char opt_cdup[SIZEOF_STR]        = "";
1220 static char opt_prefix[SIZEOF_STR]      = "";
1221 static char opt_git_dir[SIZEOF_STR]     = "";
1222 static signed char opt_is_inside_work_tree      = -1; /* set to TRUE or FALSE */
1223 static char opt_editor[SIZEOF_STR]      = "";
1224 static FILE *opt_tty                    = NULL;
1226 #define is_initial_commit()     (!get_ref_head())
1227 #define is_head_commit(rev)     (!strcmp((rev), "HEAD") || (get_ref_head() && !strcmp(rev, get_ref_head()->id)))
1230 /*
1231  * Line-oriented content detection.
1232  */
1234 #define LINE_INFO \
1235 LINE(DIFF_HEADER,  "diff --git ",       COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1236 LINE(DIFF_CHUNK,   "@@",                COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1237 LINE(DIFF_ADD,     "+",                 COLOR_GREEN,    COLOR_DEFAULT,  0), \
1238 LINE(DIFF_DEL,     "-",                 COLOR_RED,      COLOR_DEFAULT,  0), \
1239 LINE(DIFF_INDEX,        "index ",         COLOR_BLUE,   COLOR_DEFAULT,  0), \
1240 LINE(DIFF_OLDMODE,      "old file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
1241 LINE(DIFF_NEWMODE,      "new file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
1242 LINE(DIFF_COPY_FROM,    "copy from",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
1243 LINE(DIFF_COPY_TO,      "copy to",        COLOR_YELLOW, COLOR_DEFAULT,  0), \
1244 LINE(DIFF_RENAME_FROM,  "rename from",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
1245 LINE(DIFF_RENAME_TO,    "rename to",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
1246 LINE(DIFF_SIMILARITY,   "similarity ",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
1247 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
1248 LINE(DIFF_TREE,         "diff-tree ",     COLOR_BLUE,   COLOR_DEFAULT,  0), \
1249 LINE(PP_AUTHOR,    "Author: ",          COLOR_CYAN,     COLOR_DEFAULT,  0), \
1250 LINE(PP_COMMIT,    "Commit: ",          COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1251 LINE(PP_MERGE,     "Merge: ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
1252 LINE(PP_DATE,      "Date:   ",          COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1253 LINE(PP_ADATE,     "AuthorDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1254 LINE(PP_CDATE,     "CommitDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1255 LINE(PP_REFS,      "Refs: ",            COLOR_RED,      COLOR_DEFAULT,  0), \
1256 LINE(COMMIT,       "commit ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
1257 LINE(PARENT,       "parent ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
1258 LINE(TREE,         "tree ",             COLOR_BLUE,     COLOR_DEFAULT,  0), \
1259 LINE(AUTHOR,       "author ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
1260 LINE(COMMITTER,    "committer ",        COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1261 LINE(SIGNOFF,      "    Signed-off-by", COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1262 LINE(ACKED,        "    Acked-by",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1263 LINE(TESTED,       "    Tested-by",     COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1264 LINE(REVIEWED,     "    Reviewed-by",   COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1265 LINE(DEFAULT,      "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
1266 LINE(CURSOR,       "",                  COLOR_WHITE,    COLOR_GREEN,    A_BOLD), \
1267 LINE(STATUS,       "",                  COLOR_GREEN,    COLOR_DEFAULT,  0), \
1268 LINE(DELIMITER,    "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1269 LINE(DATE,         "",                  COLOR_BLUE,     COLOR_DEFAULT,  0), \
1270 LINE(MODE,         "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1271 LINE(LINE_NUMBER,  "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1272 LINE(TITLE_BLUR,   "",                  COLOR_WHITE,    COLOR_BLUE,     0), \
1273 LINE(TITLE_FOCUS,  "",                  COLOR_WHITE,    COLOR_BLUE,     A_BOLD), \
1274 LINE(MAIN_COMMIT,  "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
1275 LINE(MAIN_TAG,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  A_BOLD), \
1276 LINE(MAIN_LOCAL_TAG,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1277 LINE(MAIN_REMOTE,  "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1278 LINE(MAIN_TRACKED, "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_BOLD), \
1279 LINE(MAIN_REF,     "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1280 LINE(MAIN_HEAD,    "",                  COLOR_CYAN,     COLOR_DEFAULT,  A_BOLD), \
1281 LINE(MAIN_REVGRAPH,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1282 LINE(TREE_HEAD,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_BOLD), \
1283 LINE(TREE_DIR,     "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_NORMAL), \
1284 LINE(TREE_FILE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
1285 LINE(STAT_HEAD,    "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1286 LINE(STAT_SECTION, "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1287 LINE(STAT_NONE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
1288 LINE(STAT_STAGED,  "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1289 LINE(STAT_UNSTAGED,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1290 LINE(STAT_UNTRACKED,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1291 LINE(HELP_KEYMAP,  "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1292 LINE(HELP_GROUP,   "",                  COLOR_BLUE,     COLOR_DEFAULT,  0), \
1293 LINE(BLAME_ID,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0)
1295 enum line_type {
1296 #define LINE(type, line, fg, bg, attr) \
1297         LINE_##type
1298         LINE_INFO,
1299         LINE_NONE
1300 #undef  LINE
1301 };
1303 struct line_info {
1304         const char *name;       /* Option name. */
1305         int namelen;            /* Size of option name. */
1306         const char *line;       /* The start of line to match. */
1307         int linelen;            /* Size of string to match. */
1308         int fg, bg, attr;       /* Color and text attributes for the lines. */
1309 };
1311 static struct line_info line_info[] = {
1312 #define LINE(type, line, fg, bg, attr) \
1313         { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
1314         LINE_INFO
1315 #undef  LINE
1316 };
1318 static enum line_type
1319 get_line_type(const char *line)
1321         int linelen = strlen(line);
1322         enum line_type type;
1324         for (type = 0; type < ARRAY_SIZE(line_info); type++)
1325                 /* Case insensitive search matches Signed-off-by lines better. */
1326                 if (linelen >= line_info[type].linelen &&
1327                     !strncasecmp(line_info[type].line, line, line_info[type].linelen))
1328                         return type;
1330         return LINE_DEFAULT;
1333 static inline int
1334 get_line_attr(enum line_type type)
1336         assert(type < ARRAY_SIZE(line_info));
1337         return COLOR_PAIR(type) | line_info[type].attr;
1340 static struct line_info *
1341 get_line_info(const char *name)
1343         size_t namelen = strlen(name);
1344         enum line_type type;
1346         for (type = 0; type < ARRAY_SIZE(line_info); type++)
1347                 if (enum_equals(line_info[type], name, namelen))
1348                         return &line_info[type];
1350         return NULL;
1353 static void
1354 init_colors(void)
1356         int default_bg = line_info[LINE_DEFAULT].bg;
1357         int default_fg = line_info[LINE_DEFAULT].fg;
1358         enum line_type type;
1360         start_color();
1362         if (assume_default_colors(default_fg, default_bg) == ERR) {
1363                 default_bg = COLOR_BLACK;
1364                 default_fg = COLOR_WHITE;
1365         }
1367         for (type = 0; type < ARRAY_SIZE(line_info); type++) {
1368                 struct line_info *info = &line_info[type];
1369                 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
1370                 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
1372                 init_pair(type, fg, bg);
1373         }
1376 struct line {
1377         enum line_type type;
1379         /* State flags */
1380         unsigned int selected:1;
1381         unsigned int dirty:1;
1382         unsigned int cleareol:1;
1383         unsigned int other:16;
1385         void *data;             /* User data */
1386 };
1389 /*
1390  * Keys
1391  */
1393 struct keybinding {
1394         int alias;
1395         enum request request;
1396 };
1398 static struct keybinding default_keybindings[] = {
1399         /* View switching */
1400         { 'm',          REQ_VIEW_MAIN },
1401         { 'd',          REQ_VIEW_DIFF },
1402         { 'l',          REQ_VIEW_LOG },
1403         { 't',          REQ_VIEW_TREE },
1404         { 'f',          REQ_VIEW_BLOB },
1405         { 'B',          REQ_VIEW_BLAME },
1406         { 'H',          REQ_VIEW_BRANCH },
1407         { 'p',          REQ_VIEW_PAGER },
1408         { 'h',          REQ_VIEW_HELP },
1409         { 'S',          REQ_VIEW_STATUS },
1410         { 'c',          REQ_VIEW_STAGE },
1412         /* View manipulation */
1413         { 'q',          REQ_VIEW_CLOSE },
1414         { KEY_TAB,      REQ_VIEW_NEXT },
1415         { KEY_RETURN,   REQ_ENTER },
1416         { KEY_UP,       REQ_PREVIOUS },
1417         { KEY_DOWN,     REQ_NEXT },
1418         { 'R',          REQ_REFRESH },
1419         { KEY_F(5),     REQ_REFRESH },
1420         { 'O',          REQ_MAXIMIZE },
1422         /* Cursor navigation */
1423         { 'k',          REQ_MOVE_UP },
1424         { 'j',          REQ_MOVE_DOWN },
1425         { KEY_HOME,     REQ_MOVE_FIRST_LINE },
1426         { KEY_END,      REQ_MOVE_LAST_LINE },
1427         { KEY_NPAGE,    REQ_MOVE_PAGE_DOWN },
1428         { ' ',          REQ_MOVE_PAGE_DOWN },
1429         { KEY_PPAGE,    REQ_MOVE_PAGE_UP },
1430         { 'b',          REQ_MOVE_PAGE_UP },
1431         { '-',          REQ_MOVE_PAGE_UP },
1433         /* Scrolling */
1434         { KEY_LEFT,     REQ_SCROLL_LEFT },
1435         { KEY_RIGHT,    REQ_SCROLL_RIGHT },
1436         { KEY_IC,       REQ_SCROLL_LINE_UP },
1437         { KEY_DC,       REQ_SCROLL_LINE_DOWN },
1438         { 'w',          REQ_SCROLL_PAGE_UP },
1439         { 's',          REQ_SCROLL_PAGE_DOWN },
1441         /* Searching */
1442         { '/',          REQ_SEARCH },
1443         { '?',          REQ_SEARCH_BACK },
1444         { 'n',          REQ_FIND_NEXT },
1445         { 'N',          REQ_FIND_PREV },
1447         /* Misc */
1448         { 'Q',          REQ_QUIT },
1449         { 'z',          REQ_STOP_LOADING },
1450         { 'v',          REQ_SHOW_VERSION },
1451         { 'r',          REQ_SCREEN_REDRAW },
1452         { 'o',          REQ_OPTIONS },
1453         { '.',          REQ_TOGGLE_LINENO },
1454         { 'D',          REQ_TOGGLE_DATE },
1455         { 'A',          REQ_TOGGLE_AUTHOR },
1456         { 'g',          REQ_TOGGLE_REV_GRAPH },
1457         { 'F',          REQ_TOGGLE_REFS },
1458         { 'I',          REQ_TOGGLE_SORT_ORDER },
1459         { 'i',          REQ_TOGGLE_SORT_FIELD },
1460         { ':',          REQ_PROMPT },
1461         { 'u',          REQ_STATUS_UPDATE },
1462         { '!',          REQ_STATUS_REVERT },
1463         { 'M',          REQ_STATUS_MERGE },
1464         { '@',          REQ_STAGE_NEXT },
1465         { ',',          REQ_PARENT },
1466         { 'e',          REQ_EDIT },
1467 };
1469 #define KEYMAP_INFO \
1470         KEYMAP_(GENERIC), \
1471         KEYMAP_(MAIN), \
1472         KEYMAP_(DIFF), \
1473         KEYMAP_(LOG), \
1474         KEYMAP_(TREE), \
1475         KEYMAP_(BLOB), \
1476         KEYMAP_(BLAME), \
1477         KEYMAP_(BRANCH), \
1478         KEYMAP_(PAGER), \
1479         KEYMAP_(HELP), \
1480         KEYMAP_(STATUS), \
1481         KEYMAP_(STAGE)
1483 enum keymap {
1484 #define KEYMAP_(name) KEYMAP_##name
1485         KEYMAP_INFO
1486 #undef  KEYMAP_
1487 };
1489 static const struct enum_map keymap_table[] = {
1490 #define KEYMAP_(name) ENUM_MAP(#name, KEYMAP_##name)
1491         KEYMAP_INFO
1492 #undef  KEYMAP_
1493 };
1495 #define set_keymap(map, name) map_enum(map, keymap_table, name)
1497 struct keybinding_table {
1498         struct keybinding *data;
1499         size_t size;
1500 };
1502 static struct keybinding_table keybindings[ARRAY_SIZE(keymap_table)];
1504 static void
1505 add_keybinding(enum keymap keymap, enum request request, int key)
1507         struct keybinding_table *table = &keybindings[keymap];
1508         size_t i;
1510         for (i = 0; i < keybindings[keymap].size; i++) {
1511                 if (keybindings[keymap].data[i].alias == key) {
1512                         keybindings[keymap].data[i].request = request;
1513                         return;
1514                 }
1515         }
1517         table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data));
1518         if (!table->data)
1519                 die("Failed to allocate keybinding");
1520         table->data[table->size].alias = key;
1521         table->data[table->size++].request = request;
1523         if (request == REQ_NONE && keymap == KEYMAP_GENERIC) {
1524                 int i;
1526                 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1527                         if (default_keybindings[i].alias == key)
1528                                 default_keybindings[i].request = REQ_NONE;
1529         }
1532 /* Looks for a key binding first in the given map, then in the generic map, and
1533  * lastly in the default keybindings. */
1534 static enum request
1535 get_keybinding(enum keymap keymap, int key)
1537         size_t i;
1539         for (i = 0; i < keybindings[keymap].size; i++)
1540                 if (keybindings[keymap].data[i].alias == key)
1541                         return keybindings[keymap].data[i].request;
1543         for (i = 0; i < keybindings[KEYMAP_GENERIC].size; i++)
1544                 if (keybindings[KEYMAP_GENERIC].data[i].alias == key)
1545                         return keybindings[KEYMAP_GENERIC].data[i].request;
1547         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1548                 if (default_keybindings[i].alias == key)
1549                         return default_keybindings[i].request;
1551         return (enum request) key;
1555 struct key {
1556         const char *name;
1557         int value;
1558 };
1560 static const struct key key_table[] = {
1561         { "Enter",      KEY_RETURN },
1562         { "Space",      ' ' },
1563         { "Backspace",  KEY_BACKSPACE },
1564         { "Tab",        KEY_TAB },
1565         { "Escape",     KEY_ESC },
1566         { "Left",       KEY_LEFT },
1567         { "Right",      KEY_RIGHT },
1568         { "Up",         KEY_UP },
1569         { "Down",       KEY_DOWN },
1570         { "Insert",     KEY_IC },
1571         { "Delete",     KEY_DC },
1572         { "Hash",       '#' },
1573         { "Home",       KEY_HOME },
1574         { "End",        KEY_END },
1575         { "PageUp",     KEY_PPAGE },
1576         { "PageDown",   KEY_NPAGE },
1577         { "F1",         KEY_F(1) },
1578         { "F2",         KEY_F(2) },
1579         { "F3",         KEY_F(3) },
1580         { "F4",         KEY_F(4) },
1581         { "F5",         KEY_F(5) },
1582         { "F6",         KEY_F(6) },
1583         { "F7",         KEY_F(7) },
1584         { "F8",         KEY_F(8) },
1585         { "F9",         KEY_F(9) },
1586         { "F10",        KEY_F(10) },
1587         { "F11",        KEY_F(11) },
1588         { "F12",        KEY_F(12) },
1589 };
1591 static int
1592 get_key_value(const char *name)
1594         int i;
1596         for (i = 0; i < ARRAY_SIZE(key_table); i++)
1597                 if (!strcasecmp(key_table[i].name, name))
1598                         return key_table[i].value;
1600         if (strlen(name) == 1 && isprint(*name))
1601                 return (int) *name;
1603         return ERR;
1606 static const char *
1607 get_key_name(int key_value)
1609         static char key_char[] = "'X'";
1610         const char *seq = NULL;
1611         int key;
1613         for (key = 0; key < ARRAY_SIZE(key_table); key++)
1614                 if (key_table[key].value == key_value)
1615                         seq = key_table[key].name;
1617         if (seq == NULL &&
1618             key_value < 127 &&
1619             isprint(key_value)) {
1620                 key_char[1] = (char) key_value;
1621                 seq = key_char;
1622         }
1624         return seq ? seq : "(no key)";
1627 static bool
1628 append_key(char *buf, size_t *pos, const struct keybinding *keybinding)
1630         const char *sep = *pos > 0 ? ", " : "";
1631         const char *keyname = get_key_name(keybinding->alias);
1633         return string_nformat(buf, BUFSIZ, pos, "%s%s", sep, keyname);
1636 static bool
1637 append_keymap_request_keys(char *buf, size_t *pos, enum request request,
1638                            enum keymap keymap, bool all)
1640         int i;
1642         for (i = 0; i < keybindings[keymap].size; i++) {
1643                 if (keybindings[keymap].data[i].request == request) {
1644                         if (!append_key(buf, pos, &keybindings[keymap].data[i]))
1645                                 return FALSE;
1646                         if (!all)
1647                                 break;
1648                 }
1649         }
1651         return TRUE;
1654 #define get_key(keymap, request) get_keys(keymap, request, FALSE)
1656 static const char *
1657 get_keys(enum keymap keymap, enum request request, bool all)
1659         static char buf[BUFSIZ];
1660         size_t pos = 0;
1661         int i;
1663         buf[pos] = 0;
1665         if (!append_keymap_request_keys(buf, &pos, request, keymap, all))
1666                 return "Too many keybindings!";
1667         if (pos > 0 && !all)
1668                 return buf;
1670         if (keymap != KEYMAP_GENERIC) {
1671                 /* Only the generic keymap includes the default keybindings when
1672                  * listing all keys. */
1673                 if (all)
1674                         return buf;
1676                 if (!append_keymap_request_keys(buf, &pos, request, KEYMAP_GENERIC, all))
1677                         return "Too many keybindings!";
1678                 if (pos)
1679                         return buf;
1680         }
1682         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1683                 if (default_keybindings[i].request == request) {
1684                         if (!append_key(buf, &pos, &default_keybindings[i]))
1685                                 return "Too many keybindings!";
1686                         if (!all)
1687                                 return buf;
1688                 }
1689         }
1691         return buf;
1694 struct run_request {
1695         enum keymap keymap;
1696         int key;
1697         const char **argv;
1698 };
1700 static struct run_request *run_request;
1701 static size_t run_requests;
1703 DEFINE_ALLOCATOR(realloc_run_requests, struct run_request, 8)
1705 static enum request
1706 add_run_request(enum keymap keymap, int key, const char **argv)
1708         struct run_request *req;
1710         if (!realloc_run_requests(&run_request, run_requests, 1))
1711                 return REQ_NONE;
1713         req = &run_request[run_requests];
1714         req->keymap = keymap;
1715         req->key = key;
1716         req->argv = NULL;
1718         if (!argv_copy(&req->argv, argv))
1719                 return REQ_NONE;
1721         return REQ_NONE + ++run_requests;
1724 static struct run_request *
1725 get_run_request(enum request request)
1727         if (request <= REQ_NONE)
1728                 return NULL;
1729         return &run_request[request - REQ_NONE - 1];
1732 static void
1733 add_builtin_run_requests(void)
1735         const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1736         const char *checkout[] = { "git", "checkout", "%(branch)", NULL };
1737         const char *commit[] = { "git", "commit", NULL };
1738         const char *gc[] = { "git", "gc", NULL };
1739         struct run_request reqs[] = {
1740                 { KEYMAP_MAIN,    'C', cherry_pick },
1741                 { KEYMAP_STATUS,  'C', commit },
1742                 { KEYMAP_BRANCH,  'C', checkout },
1743                 { KEYMAP_GENERIC, 'G', gc },
1744         };
1745         int i;
1747         for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1748                 enum request req = get_keybinding(reqs[i].keymap, reqs[i].key);
1750                 if (req != reqs[i].key)
1751                         continue;
1752                 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argv);
1753                 if (req != REQ_NONE)
1754                         add_keybinding(reqs[i].keymap, req, reqs[i].key);
1755         }
1758 /*
1759  * User config file handling.
1760  */
1762 static int   config_lineno;
1763 static bool  config_errors;
1764 static const char *config_msg;
1766 static const struct enum_map color_map[] = {
1767 #define COLOR_MAP(name) ENUM_MAP(#name, COLOR_##name)
1768         COLOR_MAP(DEFAULT),
1769         COLOR_MAP(BLACK),
1770         COLOR_MAP(BLUE),
1771         COLOR_MAP(CYAN),
1772         COLOR_MAP(GREEN),
1773         COLOR_MAP(MAGENTA),
1774         COLOR_MAP(RED),
1775         COLOR_MAP(WHITE),
1776         COLOR_MAP(YELLOW),
1777 };
1779 static const struct enum_map attr_map[] = {
1780 #define ATTR_MAP(name) ENUM_MAP(#name, A_##name)
1781         ATTR_MAP(NORMAL),
1782         ATTR_MAP(BLINK),
1783         ATTR_MAP(BOLD),
1784         ATTR_MAP(DIM),
1785         ATTR_MAP(REVERSE),
1786         ATTR_MAP(STANDOUT),
1787         ATTR_MAP(UNDERLINE),
1788 };
1790 #define set_attribute(attr, name)       map_enum(attr, attr_map, name)
1792 static int parse_step(double *opt, const char *arg)
1794         *opt = atoi(arg);
1795         if (!strchr(arg, '%'))
1796                 return OK;
1798         /* "Shift down" so 100% and 1 does not conflict. */
1799         *opt = (*opt - 1) / 100;
1800         if (*opt >= 1.0) {
1801                 *opt = 0.99;
1802                 config_msg = "Step value larger than 100%";
1803                 return ERR;
1804         }
1805         if (*opt < 0.0) {
1806                 *opt = 1;
1807                 config_msg = "Invalid step value";
1808                 return ERR;
1809         }
1810         return OK;
1813 static int
1814 parse_int(int *opt, const char *arg, int min, int max)
1816         int value = atoi(arg);
1818         if (min <= value && value <= max) {
1819                 *opt = value;
1820                 return OK;
1821         }
1823         config_msg = "Integer value out of bound";
1824         return ERR;
1827 static bool
1828 set_color(int *color, const char *name)
1830         if (map_enum(color, color_map, name))
1831                 return TRUE;
1832         if (!prefixcmp(name, "color"))
1833                 return parse_int(color, name + 5, 0, 255) == OK;
1834         return FALSE;
1837 /* Wants: object fgcolor bgcolor [attribute] */
1838 static int
1839 option_color_command(int argc, const char *argv[])
1841         struct line_info *info;
1843         if (argc < 3) {
1844                 config_msg = "Wrong number of arguments given to color command";
1845                 return ERR;
1846         }
1848         info = get_line_info(argv[0]);
1849         if (!info) {
1850                 static const struct enum_map obsolete[] = {
1851                         ENUM_MAP("main-delim",  LINE_DELIMITER),
1852                         ENUM_MAP("main-date",   LINE_DATE),
1853                         ENUM_MAP("main-author", LINE_AUTHOR),
1854                 };
1855                 int index;
1857                 if (!map_enum(&index, obsolete, argv[0])) {
1858                         config_msg = "Unknown color name";
1859                         return ERR;
1860                 }
1861                 info = &line_info[index];
1862         }
1864         if (!set_color(&info->fg, argv[1]) ||
1865             !set_color(&info->bg, argv[2])) {
1866                 config_msg = "Unknown color";
1867                 return ERR;
1868         }
1870         info->attr = 0;
1871         while (argc-- > 3) {
1872                 int attr;
1874                 if (!set_attribute(&attr, argv[argc])) {
1875                         config_msg = "Unknown attribute";
1876                         return ERR;
1877                 }
1878                 info->attr |= attr;
1879         }
1881         return OK;
1884 static int parse_bool(bool *opt, const char *arg)
1886         *opt = (!strcmp(arg, "1") || !strcmp(arg, "true") || !strcmp(arg, "yes"))
1887                 ? TRUE : FALSE;
1888         return OK;
1891 static int parse_enum_do(unsigned int *opt, const char *arg,
1892                          const struct enum_map *map, size_t map_size)
1894         bool is_true;
1896         assert(map_size > 1);
1898         if (map_enum_do(map, map_size, (int *) opt, arg))
1899                 return OK;
1901         if (parse_bool(&is_true, arg) != OK)
1902                 return ERR;
1904         *opt = is_true ? map[1].value : map[0].value;
1905         return OK;
1908 #define parse_enum(opt, arg, map) \
1909         parse_enum_do(opt, arg, map, ARRAY_SIZE(map))
1911 static int
1912 parse_string(char *opt, const char *arg, size_t optsize)
1914         int arglen = strlen(arg);
1916         switch (arg[0]) {
1917         case '\"':
1918         case '\'':
1919                 if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1920                         config_msg = "Unmatched quotation";
1921                         return ERR;
1922                 }
1923                 arg += 1; arglen -= 2;
1924         default:
1925                 string_ncopy_do(opt, optsize, arg, arglen);
1926                 return OK;
1927         }
1930 /* Wants: name = value */
1931 static int
1932 option_set_command(int argc, const char *argv[])
1934         if (argc != 3) {
1935                 config_msg = "Wrong number of arguments given to set command";
1936                 return ERR;
1937         }
1939         if (strcmp(argv[1], "=")) {
1940                 config_msg = "No value assigned";
1941                 return ERR;
1942         }
1944         if (!strcmp(argv[0], "show-author"))
1945                 return parse_enum(&opt_author, argv[2], author_map);
1947         if (!strcmp(argv[0], "show-date"))
1948                 return parse_enum(&opt_date, argv[2], date_map);
1950         if (!strcmp(argv[0], "show-rev-graph"))
1951                 return parse_bool(&opt_rev_graph, argv[2]);
1953         if (!strcmp(argv[0], "show-refs"))
1954                 return parse_bool(&opt_show_refs, argv[2]);
1956         if (!strcmp(argv[0], "show-line-numbers"))
1957                 return parse_bool(&opt_line_number, argv[2]);
1959         if (!strcmp(argv[0], "line-graphics"))
1960                 return parse_bool(&opt_line_graphics, argv[2]);
1962         if (!strcmp(argv[0], "line-number-interval"))
1963                 return parse_int(&opt_num_interval, argv[2], 1, 1024);
1965         if (!strcmp(argv[0], "author-width"))
1966                 return parse_int(&opt_author_cols, argv[2], 0, 1024);
1968         if (!strcmp(argv[0], "horizontal-scroll"))
1969                 return parse_step(&opt_hscroll, argv[2]);
1971         if (!strcmp(argv[0], "split-view-height"))
1972                 return parse_step(&opt_scale_split_view, argv[2]);
1974         if (!strcmp(argv[0], "tab-size"))
1975                 return parse_int(&opt_tab_size, argv[2], 1, 1024);
1977         if (!strcmp(argv[0], "commit-encoding"))
1978                 return parse_string(opt_encoding, argv[2], sizeof(opt_encoding));
1980         config_msg = "Unknown variable name";
1981         return ERR;
1984 /* Wants: mode request key */
1985 static int
1986 option_bind_command(int argc, const char *argv[])
1988         enum request request;
1989         int keymap = -1;
1990         int key;
1992         if (argc < 3) {
1993                 config_msg = "Wrong number of arguments given to bind command";
1994                 return ERR;
1995         }
1997         if (!set_keymap(&keymap, argv[0])) {
1998                 config_msg = "Unknown key map";
1999                 return ERR;
2000         }
2002         key = get_key_value(argv[1]);
2003         if (key == ERR) {
2004                 config_msg = "Unknown key";
2005                 return ERR;
2006         }
2008         request = get_request(argv[2]);
2009         if (request == REQ_UNKNOWN) {
2010                 static const struct enum_map obsolete[] = {
2011                         ENUM_MAP("cherry-pick",         REQ_NONE),
2012                         ENUM_MAP("screen-resize",       REQ_NONE),
2013                         ENUM_MAP("tree-parent",         REQ_PARENT),
2014                 };
2015                 int alias;
2017                 if (map_enum(&alias, obsolete, argv[2])) {
2018                         if (alias != REQ_NONE)
2019                                 add_keybinding(keymap, alias, key);
2020                         config_msg = "Obsolete request name";
2021                         return ERR;
2022                 }
2023         }
2024         if (request == REQ_UNKNOWN && *argv[2]++ == '!')
2025                 request = add_run_request(keymap, key, argv + 2);
2026         if (request == REQ_UNKNOWN) {
2027                 config_msg = "Unknown request name";
2028                 return ERR;
2029         }
2031         add_keybinding(keymap, request, key);
2033         return OK;
2036 static int
2037 set_option(const char *opt, char *value)
2039         const char *argv[SIZEOF_ARG];
2040         int argc = 0;
2042         if (!argv_from_string(argv, &argc, value)) {
2043                 config_msg = "Too many option arguments";
2044                 return ERR;
2045         }
2047         if (!strcmp(opt, "color"))
2048                 return option_color_command(argc, argv);
2050         if (!strcmp(opt, "set"))
2051                 return option_set_command(argc, argv);
2053         if (!strcmp(opt, "bind"))
2054                 return option_bind_command(argc, argv);
2056         config_msg = "Unknown option command";
2057         return ERR;
2060 static int
2061 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
2063         int status = OK;
2065         config_lineno++;
2066         config_msg = "Internal error";
2068         /* Check for comment markers, since read_properties() will
2069          * only ensure opt and value are split at first " \t". */
2070         optlen = strcspn(opt, "#");
2071         if (optlen == 0)
2072                 return OK;
2074         if (opt[optlen] != 0) {
2075                 config_msg = "No option value";
2076                 status = ERR;
2078         }  else {
2079                 /* Look for comment endings in the value. */
2080                 size_t len = strcspn(value, "#");
2082                 if (len < valuelen) {
2083                         valuelen = len;
2084                         value[valuelen] = 0;
2085                 }
2087                 status = set_option(opt, value);
2088         }
2090         if (status == ERR) {
2091                 warn("Error on line %d, near '%.*s': %s",
2092                      config_lineno, (int) optlen, opt, config_msg);
2093                 config_errors = TRUE;
2094         }
2096         /* Always keep going if errors are encountered. */
2097         return OK;
2100 static void
2101 load_option_file(const char *path)
2103         struct io io;
2105         /* It's OK that the file doesn't exist. */
2106         if (!io_open(&io, "%s", path))
2107                 return;
2109         config_lineno = 0;
2110         config_errors = FALSE;
2112         if (io_load(&io, " \t", read_option) == ERR ||
2113             config_errors == TRUE)
2114                 warn("Errors while loading %s.", path);
2117 static int
2118 load_options(void)
2120         const char *home = getenv("HOME");
2121         const char *tigrc_user = getenv("TIGRC_USER");
2122         const char *tigrc_system = getenv("TIGRC_SYSTEM");
2123         char buf[SIZEOF_STR];
2125         if (!tigrc_system)
2126                 tigrc_system = SYSCONFDIR "/tigrc";
2127         load_option_file(tigrc_system);
2129         if (!tigrc_user) {
2130                 if (!home || !string_format(buf, "%s/.tigrc", home))
2131                         return ERR;
2132                 tigrc_user = buf;
2133         }
2134         load_option_file(tigrc_user);
2136         /* Add _after_ loading config files to avoid adding run requests
2137          * that conflict with keybindings. */
2138         add_builtin_run_requests();
2140         return OK;
2144 /*
2145  * The viewer
2146  */
2148 struct view;
2149 struct view_ops;
2151 /* The display array of active views and the index of the current view. */
2152 static struct view *display[2];
2153 static unsigned int current_view;
2155 #define foreach_displayed_view(view, i) \
2156         for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
2158 #define displayed_views()       (display[1] != NULL ? 2 : 1)
2160 /* Current head and commit ID */
2161 static char ref_blob[SIZEOF_REF]        = "";
2162 static char ref_commit[SIZEOF_REF]      = "HEAD";
2163 static char ref_head[SIZEOF_REF]        = "HEAD";
2164 static char ref_branch[SIZEOF_REF]      = "";
2166 enum view_type {
2167         VIEW_MAIN,
2168         VIEW_DIFF,
2169         VIEW_LOG,
2170         VIEW_TREE,
2171         VIEW_BLOB,
2172         VIEW_BLAME,
2173         VIEW_BRANCH,
2174         VIEW_HELP,
2175         VIEW_PAGER,
2176         VIEW_STATUS,
2177         VIEW_STAGE,
2178 };
2180 struct view {
2181         enum view_type type;    /* View type */
2182         const char *name;       /* View name */
2183         const char *cmd_env;    /* Command line set via environment */
2184         const char *id;         /* Points to either of ref_{head,commit,blob} */
2186         struct view_ops *ops;   /* View operations */
2188         enum keymap keymap;     /* What keymap does this view have */
2189         bool git_dir;           /* Whether the view requires a git directory. */
2191         char ref[SIZEOF_REF];   /* Hovered commit reference */
2192         char vid[SIZEOF_REF];   /* View ID. Set to id member when updating. */
2194         int height, width;      /* The width and height of the main window */
2195         WINDOW *win;            /* The main window */
2196         WINDOW *title;          /* The title window living below the main window */
2198         /* Navigation */
2199         unsigned long offset;   /* Offset of the window top */
2200         unsigned long yoffset;  /* Offset from the window side. */
2201         unsigned long lineno;   /* Current line number */
2202         unsigned long p_offset; /* Previous offset of the window top */
2203         unsigned long p_yoffset;/* Previous offset from the window side */
2204         unsigned long p_lineno; /* Previous current line number */
2205         bool p_restore;         /* Should the previous position be restored. */
2207         /* Searching */
2208         char grep[SIZEOF_STR];  /* Search string */
2209         regex_t *regex;         /* Pre-compiled regexp */
2211         /* If non-NULL, points to the view that opened this view. If this view
2212          * is closed tig will switch back to the parent view. */
2213         struct view *parent;
2214         struct view *prev;
2216         /* Buffering */
2217         size_t lines;           /* Total number of lines */
2218         struct line *line;      /* Line index */
2219         unsigned int digits;    /* Number of digits in the lines member. */
2221         /* Drawing */
2222         struct line *curline;   /* Line currently being drawn. */
2223         enum line_type curtype; /* Attribute currently used for drawing. */
2224         unsigned long col;      /* Column when drawing. */
2225         bool has_scrolled;      /* View was scrolled. */
2227         /* Loading */
2228         const char **argv;      /* Shell command arguments. */
2229         const char *dir;        /* Directory from which to execute. */
2230         struct io io;
2231         struct io *pipe;
2232         time_t start_time;
2233         time_t update_secs;
2234 };
2236 struct view_ops {
2237         /* What type of content being displayed. Used in the title bar. */
2238         const char *type;
2239         /* Default command arguments. */
2240         const char **argv;
2241         /* Open and reads in all view content. */
2242         bool (*open)(struct view *view);
2243         /* Read one line; updates view->line. */
2244         bool (*read)(struct view *view, char *data);
2245         /* Draw one line; @lineno must be < view->height. */
2246         bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
2247         /* Depending on view handle a special requests. */
2248         enum request (*request)(struct view *view, enum request request, struct line *line);
2249         /* Search for regexp in a line. */
2250         bool (*grep)(struct view *view, struct line *line);
2251         /* Select line */
2252         void (*select)(struct view *view, struct line *line);
2253         /* Prepare view for loading */
2254         bool (*prepare)(struct view *view);
2255 };
2257 static struct view_ops blame_ops;
2258 static struct view_ops blob_ops;
2259 static struct view_ops diff_ops;
2260 static struct view_ops help_ops;
2261 static struct view_ops log_ops;
2262 static struct view_ops main_ops;
2263 static struct view_ops pager_ops;
2264 static struct view_ops stage_ops;
2265 static struct view_ops status_ops;
2266 static struct view_ops tree_ops;
2267 static struct view_ops branch_ops;
2269 #define VIEW_STR(type, name, env, ref, ops, map, git) \
2270         { type, name, #env, ref, ops, map, git }
2272 #define VIEW_(id, name, ops, git, ref) \
2273         VIEW_STR(VIEW_##id, name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
2275 static struct view views[] = {
2276         VIEW_(MAIN,   "main",   &main_ops,   TRUE,  ref_head),
2277         VIEW_(DIFF,   "diff",   &diff_ops,   TRUE,  ref_commit),
2278         VIEW_(LOG,    "log",    &log_ops,    TRUE,  ref_head),
2279         VIEW_(TREE,   "tree",   &tree_ops,   TRUE,  ref_commit),
2280         VIEW_(BLOB,   "blob",   &blob_ops,   TRUE,  ref_blob),
2281         VIEW_(BLAME,  "blame",  &blame_ops,  TRUE,  ref_commit),
2282         VIEW_(BRANCH, "branch", &branch_ops, TRUE,  ref_head),
2283         VIEW_(HELP,   "help",   &help_ops,   FALSE, ""),
2284         VIEW_(PAGER,  "pager",  &pager_ops,  FALSE, "stdin"),
2285         VIEW_(STATUS, "status", &status_ops, TRUE,  ""),
2286         VIEW_(STAGE,  "stage",  &stage_ops,  TRUE,  ""),
2287 };
2289 #define VIEW(req)       (&views[(req) - REQ_OFFSET - 1])
2291 #define foreach_view(view, i) \
2292         for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
2294 #define view_is_displayed(view) \
2295         (view == display[0] || view == display[1])
2297 static enum request
2298 view_request(struct view *view, enum request request)
2300         if (!view || !view->lines)
2301                 return request;
2302         return view->ops->request(view, request, &view->line[view->lineno]);
2306 /*
2307  * View drawing.
2308  */
2310 static inline void
2311 set_view_attr(struct view *view, enum line_type type)
2313         if (!view->curline->selected && view->curtype != type) {
2314                 (void) wattrset(view->win, get_line_attr(type));
2315                 wchgat(view->win, -1, 0, type, NULL);
2316                 view->curtype = type;
2317         }
2320 static int
2321 draw_chars(struct view *view, enum line_type type, const char *string,
2322            int max_len, bool use_tilde)
2324         static char out_buffer[BUFSIZ * 2];
2325         int len = 0;
2326         int col = 0;
2327         int trimmed = FALSE;
2328         size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2330         if (max_len <= 0)
2331                 return 0;
2333         len = utf8_length(&string, skip, &col, max_len, &trimmed, use_tilde, opt_tab_size);
2335         set_view_attr(view, type);
2336         if (len > 0) {
2337                 if (opt_iconv_out != ICONV_NONE) {
2338                         ICONV_CONST char *inbuf = (ICONV_CONST char *) string;
2339                         size_t inlen = len + 1;
2341                         char *outbuf = out_buffer;
2342                         size_t outlen = sizeof(out_buffer);
2344                         size_t ret;
2346                         ret = iconv(opt_iconv_out, &inbuf, &inlen, &outbuf, &outlen);
2347                         if (ret != (size_t) -1) {
2348                                 string = out_buffer;
2349                                 len = sizeof(out_buffer) - outlen;
2350                         }
2351                 }
2353                 waddnstr(view->win, string, len);
2354         }
2355         if (trimmed && use_tilde) {
2356                 set_view_attr(view, LINE_DELIMITER);
2357                 waddch(view->win, '~');
2358                 col++;
2359         }
2361         return col;
2364 static int
2365 draw_space(struct view *view, enum line_type type, int max, int spaces)
2367         static char space[] = "                    ";
2368         int col = 0;
2370         spaces = MIN(max, spaces);
2372         while (spaces > 0) {
2373                 int len = MIN(spaces, sizeof(space) - 1);
2375                 col += draw_chars(view, type, space, len, FALSE);
2376                 spaces -= len;
2377         }
2379         return col;
2382 static bool
2383 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
2385         view->col += draw_chars(view, type, string, view->width + view->yoffset - view->col, trim);
2386         return view->width + view->yoffset <= view->col;
2389 static bool
2390 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
2392         size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2393         int max = view->width + view->yoffset - view->col;
2394         int i;
2396         if (max < size)
2397                 size = max;
2399         set_view_attr(view, type);
2400         /* Using waddch() instead of waddnstr() ensures that
2401          * they'll be rendered correctly for the cursor line. */
2402         for (i = skip; i < size; i++)
2403                 waddch(view->win, graphic[i]);
2405         view->col += size;
2406         if (size < max && skip <= size)
2407                 waddch(view->win, ' ');
2408         view->col++;
2410         return view->width + view->yoffset <= view->col;
2413 static bool
2414 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
2416         int max = MIN(view->width + view->yoffset - view->col, len);
2417         int col;
2419         if (text)
2420                 col = draw_chars(view, type, text, max - 1, trim);
2421         else
2422                 col = draw_space(view, type, max - 1, max - 1);
2424         view->col += col;
2425         view->col += draw_space(view, LINE_DEFAULT, max - col, max - col);
2426         return view->width + view->yoffset <= view->col;
2429 static bool
2430 draw_date(struct view *view, struct time *time)
2432         const char *date = mkdate(time, opt_date);
2433         int cols = opt_date == DATE_SHORT ? DATE_SHORT_COLS : DATE_COLS;
2435         return draw_field(view, LINE_DATE, date, cols, FALSE);
2438 static bool
2439 draw_author(struct view *view, const char *author)
2441         bool trim = opt_author_cols == 0 || opt_author_cols > 5;
2442         bool abbreviate = opt_author == AUTHOR_ABBREVIATED || !trim;
2444         if (abbreviate && author)
2445                 author = get_author_initials(author);
2447         return draw_field(view, LINE_AUTHOR, author, opt_author_cols, trim);
2450 static bool
2451 draw_mode(struct view *view, mode_t mode)
2453         const char *str;
2455         if (S_ISDIR(mode))
2456                 str = "drwxr-xr-x";
2457         else if (S_ISLNK(mode))
2458                 str = "lrwxrwxrwx";
2459         else if (S_ISGITLINK(mode))
2460                 str = "m---------";
2461         else if (S_ISREG(mode) && mode & S_IXUSR)
2462                 str = "-rwxr-xr-x";
2463         else if (S_ISREG(mode))
2464                 str = "-rw-r--r--";
2465         else
2466                 str = "----------";
2468         return draw_field(view, LINE_MODE, str, STRING_SIZE("-rw-r--r-- "), FALSE);
2471 static bool
2472 draw_lineno(struct view *view, unsigned int lineno)
2474         char number[10];
2475         int digits3 = view->digits < 3 ? 3 : view->digits;
2476         int max = MIN(view->width + view->yoffset - view->col, digits3);
2477         char *text = NULL;
2478         chtype separator = opt_line_graphics ? ACS_VLINE : '|';
2480         lineno += view->offset + 1;
2481         if (lineno == 1 || (lineno % opt_num_interval) == 0) {
2482                 static char fmt[] = "%1ld";
2484                 fmt[1] = '0' + (view->digits <= 9 ? digits3 : 1);
2485                 if (string_format(number, fmt, lineno))
2486                         text = number;
2487         }
2488         if (text)
2489                 view->col += draw_chars(view, LINE_LINE_NUMBER, text, max, TRUE);
2490         else
2491                 view->col += draw_space(view, LINE_LINE_NUMBER, max, digits3);
2492         return draw_graphic(view, LINE_DEFAULT, &separator, 1);
2495 static bool
2496 draw_view_line(struct view *view, unsigned int lineno)
2498         struct line *line;
2499         bool selected = (view->offset + lineno == view->lineno);
2501         assert(view_is_displayed(view));
2503         if (view->offset + lineno >= view->lines)
2504                 return FALSE;
2506         line = &view->line[view->offset + lineno];
2508         wmove(view->win, lineno, 0);
2509         if (line->cleareol)
2510                 wclrtoeol(view->win);
2511         view->col = 0;
2512         view->curline = line;
2513         view->curtype = LINE_NONE;
2514         line->selected = FALSE;
2515         line->dirty = line->cleareol = 0;
2517         if (selected) {
2518                 set_view_attr(view, LINE_CURSOR);
2519                 line->selected = TRUE;
2520                 view->ops->select(view, line);
2521         }
2523         return view->ops->draw(view, line, lineno);
2526 static void
2527 redraw_view_dirty(struct view *view)
2529         bool dirty = FALSE;
2530         int lineno;
2532         for (lineno = 0; lineno < view->height; lineno++) {
2533                 if (view->offset + lineno >= view->lines)
2534                         break;
2535                 if (!view->line[view->offset + lineno].dirty)
2536                         continue;
2537                 dirty = TRUE;
2538                 if (!draw_view_line(view, lineno))
2539                         break;
2540         }
2542         if (!dirty)
2543                 return;
2544         wnoutrefresh(view->win);
2547 static void
2548 redraw_view_from(struct view *view, int lineno)
2550         assert(0 <= lineno && lineno < view->height);
2552         for (; lineno < view->height; lineno++) {
2553                 if (!draw_view_line(view, lineno))
2554                         break;
2555         }
2557         wnoutrefresh(view->win);
2560 static void
2561 redraw_view(struct view *view)
2563         werase(view->win);
2564         redraw_view_from(view, 0);
2568 static void
2569 update_view_title(struct view *view)
2571         char buf[SIZEOF_STR];
2572         char state[SIZEOF_STR];
2573         size_t bufpos = 0, statelen = 0;
2575         assert(view_is_displayed(view));
2577         if (view->type != VIEW_STATUS && view->lines) {
2578                 unsigned int view_lines = view->offset + view->height;
2579                 unsigned int lines = view->lines
2580                                    ? MIN(view_lines, view->lines) * 100 / view->lines
2581                                    : 0;
2583                 string_format_from(state, &statelen, " - %s %d of %d (%d%%)",
2584                                    view->ops->type,
2585                                    view->lineno + 1,
2586                                    view->lines,
2587                                    lines);
2589         }
2591         if (view->pipe) {
2592                 time_t secs = time(NULL) - view->start_time;
2594                 /* Three git seconds are a long time ... */
2595                 if (secs > 2)
2596                         string_format_from(state, &statelen, " loading %lds", secs);
2597         }
2599         string_format_from(buf, &bufpos, "[%s]", view->name);
2600         if (*view->ref && bufpos < view->width) {
2601                 size_t refsize = strlen(view->ref);
2602                 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2604                 if (minsize < view->width)
2605                         refsize = view->width - minsize + 7;
2606                 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2607         }
2609         if (statelen && bufpos < view->width) {
2610                 string_format_from(buf, &bufpos, "%s", state);
2611         }
2613         if (view == display[current_view])
2614                 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2615         else
2616                 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2618         mvwaddnstr(view->title, 0, 0, buf, bufpos);
2619         wclrtoeol(view->title);
2620         wnoutrefresh(view->title);
2623 static int
2624 apply_step(double step, int value)
2626         if (step >= 1)
2627                 return (int) step;
2628         value *= step + 0.01;
2629         return value ? value : 1;
2632 static void
2633 resize_display(void)
2635         int offset, i;
2636         struct view *base = display[0];
2637         struct view *view = display[1] ? display[1] : display[0];
2639         /* Setup window dimensions */
2641         getmaxyx(stdscr, base->height, base->width);
2643         /* Make room for the status window. */
2644         base->height -= 1;
2646         if (view != base) {
2647                 /* Horizontal split. */
2648                 view->width   = base->width;
2649                 view->height  = apply_step(opt_scale_split_view, base->height);
2650                 view->height  = MAX(view->height, MIN_VIEW_HEIGHT);
2651                 view->height  = MIN(view->height, base->height - MIN_VIEW_HEIGHT);
2652                 base->height -= view->height;
2654                 /* Make room for the title bar. */
2655                 view->height -= 1;
2656         }
2658         /* Make room for the title bar. */
2659         base->height -= 1;
2661         offset = 0;
2663         foreach_displayed_view (view, i) {
2664                 if (!view->win) {
2665                         view->win = newwin(view->height, 0, offset, 0);
2666                         if (!view->win)
2667                                 die("Failed to create %s view", view->name);
2669                         scrollok(view->win, FALSE);
2671                         view->title = newwin(1, 0, offset + view->height, 0);
2672                         if (!view->title)
2673                                 die("Failed to create title window");
2675                 } else {
2676                         wresize(view->win, view->height, view->width);
2677                         mvwin(view->win,   offset, 0);
2678                         mvwin(view->title, offset + view->height, 0);
2679                 }
2681                 offset += view->height + 1;
2682         }
2685 static void
2686 redraw_display(bool clear)
2688         struct view *view;
2689         int i;
2691         foreach_displayed_view (view, i) {
2692                 if (clear)
2693                         wclear(view->win);
2694                 redraw_view(view);
2695                 update_view_title(view);
2696         }
2700 /*
2701  * Option management
2702  */
2704 static void
2705 toggle_enum_option_do(unsigned int *opt, const char *help,
2706                       const struct enum_map *map, size_t size)
2708         *opt = (*opt + 1) % size;
2709         redraw_display(FALSE);
2710         report("Displaying %s %s", enum_name(map[*opt]), help);
2713 #define toggle_enum_option(opt, help, map) \
2714         toggle_enum_option_do(opt, help, map, ARRAY_SIZE(map))
2716 #define toggle_date() toggle_enum_option(&opt_date, "dates", date_map)
2717 #define toggle_author() toggle_enum_option(&opt_author, "author names", author_map)
2719 static void
2720 toggle_view_option(bool *option, const char *help)
2722         *option = !*option;
2723         redraw_display(FALSE);
2724         report("%sabling %s", *option ? "En" : "Dis", help);
2727 static void
2728 open_option_menu(void)
2730         const struct menu_item menu[] = {
2731                 { '.', "line numbers", &opt_line_number },
2732                 { 'D', "date display", &opt_date },
2733                 { 'A', "author display", &opt_author },
2734                 { 'g', "revision graph display", &opt_rev_graph },
2735                 { 'F', "reference display", &opt_show_refs },
2736                 { 0 }
2737         };
2738         int selected = 0;
2740         if (prompt_menu("Toggle option", menu, &selected)) {
2741                 if (menu[selected].data == &opt_date)
2742                         toggle_date();
2743                 else if (menu[selected].data == &opt_author)
2744                         toggle_author();
2745                 else
2746                         toggle_view_option(menu[selected].data, menu[selected].text);
2747         }
2750 static void
2751 maximize_view(struct view *view)
2753         memset(display, 0, sizeof(display));
2754         current_view = 0;
2755         display[current_view] = view;
2756         resize_display();
2757         redraw_display(FALSE);
2758         report("");
2762 /*
2763  * Navigation
2764  */
2766 static bool
2767 goto_view_line(struct view *view, unsigned long offset, unsigned long lineno)
2769         if (lineno >= view->lines)
2770                 lineno = view->lines > 0 ? view->lines - 1 : 0;
2772         if (offset > lineno || offset + view->height <= lineno) {
2773                 unsigned long half = view->height / 2;
2775                 if (lineno > half)
2776                         offset = lineno - half;
2777                 else
2778                         offset = 0;
2779         }
2781         if (offset != view->offset || lineno != view->lineno) {
2782                 view->offset = offset;
2783                 view->lineno = lineno;
2784                 return TRUE;
2785         }
2787         return FALSE;
2790 /* Scrolling backend */
2791 static void
2792 do_scroll_view(struct view *view, int lines)
2794         bool redraw_current_line = FALSE;
2796         /* The rendering expects the new offset. */
2797         view->offset += lines;
2799         assert(0 <= view->offset && view->offset < view->lines);
2800         assert(lines);
2802         /* Move current line into the view. */
2803         if (view->lineno < view->offset) {
2804                 view->lineno = view->offset;
2805                 redraw_current_line = TRUE;
2806         } else if (view->lineno >= view->offset + view->height) {
2807                 view->lineno = view->offset + view->height - 1;
2808                 redraw_current_line = TRUE;
2809         }
2811         assert(view->offset <= view->lineno && view->lineno < view->lines);
2813         /* Redraw the whole screen if scrolling is pointless. */
2814         if (view->height < ABS(lines)) {
2815                 redraw_view(view);
2817         } else {
2818                 int line = lines > 0 ? view->height - lines : 0;
2819                 int end = line + ABS(lines);
2821                 scrollok(view->win, TRUE);
2822                 wscrl(view->win, lines);
2823                 scrollok(view->win, FALSE);
2825                 while (line < end && draw_view_line(view, line))
2826                         line++;
2828                 if (redraw_current_line)
2829                         draw_view_line(view, view->lineno - view->offset);
2830                 wnoutrefresh(view->win);
2831         }
2833         view->has_scrolled = TRUE;
2834         report("");
2837 /* Scroll frontend */
2838 static void
2839 scroll_view(struct view *view, enum request request)
2841         int lines = 1;
2843         assert(view_is_displayed(view));
2845         switch (request) {
2846         case REQ_SCROLL_LEFT:
2847                 if (view->yoffset == 0) {
2848                         report("Cannot scroll beyond the first column");
2849                         return;
2850                 }
2851                 if (view->yoffset <= apply_step(opt_hscroll, view->width))
2852                         view->yoffset = 0;
2853                 else
2854                         view->yoffset -= apply_step(opt_hscroll, view->width);
2855                 redraw_view_from(view, 0);
2856                 report("");
2857                 return;
2858         case REQ_SCROLL_RIGHT:
2859                 view->yoffset += apply_step(opt_hscroll, view->width);
2860                 redraw_view(view);
2861                 report("");
2862                 return;
2863         case REQ_SCROLL_PAGE_DOWN:
2864                 lines = view->height;
2865         case REQ_SCROLL_LINE_DOWN:
2866                 if (view->offset + lines > view->lines)
2867                         lines = view->lines - view->offset;
2869                 if (lines == 0 || view->offset + view->height >= view->lines) {
2870                         report("Cannot scroll beyond the last line");
2871                         return;
2872                 }
2873                 break;
2875         case REQ_SCROLL_PAGE_UP:
2876                 lines = view->height;
2877         case REQ_SCROLL_LINE_UP:
2878                 if (lines > view->offset)
2879                         lines = view->offset;
2881                 if (lines == 0) {
2882                         report("Cannot scroll beyond the first line");
2883                         return;
2884                 }
2886                 lines = -lines;
2887                 break;
2889         default:
2890                 die("request %d not handled in switch", request);
2891         }
2893         do_scroll_view(view, lines);
2896 /* Cursor moving */
2897 static void
2898 move_view(struct view *view, enum request request)
2900         int scroll_steps = 0;
2901         int steps;
2903         switch (request) {
2904         case REQ_MOVE_FIRST_LINE:
2905                 steps = -view->lineno;
2906                 break;
2908         case REQ_MOVE_LAST_LINE:
2909                 steps = view->lines - view->lineno - 1;
2910                 break;
2912         case REQ_MOVE_PAGE_UP:
2913                 steps = view->height > view->lineno
2914                       ? -view->lineno : -view->height;
2915                 break;
2917         case REQ_MOVE_PAGE_DOWN:
2918                 steps = view->lineno + view->height >= view->lines
2919                       ? view->lines - view->lineno - 1 : view->height;
2920                 break;
2922         case REQ_MOVE_UP:
2923                 steps = -1;
2924                 break;
2926         case REQ_MOVE_DOWN:
2927                 steps = 1;
2928                 break;
2930         default:
2931                 die("request %d not handled in switch", request);
2932         }
2934         if (steps <= 0 && view->lineno == 0) {
2935                 report("Cannot move beyond the first line");
2936                 return;
2938         } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2939                 report("Cannot move beyond the last line");
2940                 return;
2941         }
2943         /* Move the current line */
2944         view->lineno += steps;
2945         assert(0 <= view->lineno && view->lineno < view->lines);
2947         /* Check whether the view needs to be scrolled */
2948         if (view->lineno < view->offset ||
2949             view->lineno >= view->offset + view->height) {
2950                 scroll_steps = steps;
2951                 if (steps < 0 && -steps > view->offset) {
2952                         scroll_steps = -view->offset;
2954                 } else if (steps > 0) {
2955                         if (view->lineno == view->lines - 1 &&
2956                             view->lines > view->height) {
2957                                 scroll_steps = view->lines - view->offset - 1;
2958                                 if (scroll_steps >= view->height)
2959                                         scroll_steps -= view->height - 1;
2960                         }
2961                 }
2962         }
2964         if (!view_is_displayed(view)) {
2965                 view->offset += scroll_steps;
2966                 assert(0 <= view->offset && view->offset < view->lines);
2967                 view->ops->select(view, &view->line[view->lineno]);
2968                 return;
2969         }
2971         /* Repaint the old "current" line if we be scrolling */
2972         if (ABS(steps) < view->height)
2973                 draw_view_line(view, view->lineno - steps - view->offset);
2975         if (scroll_steps) {
2976                 do_scroll_view(view, scroll_steps);
2977                 return;
2978         }
2980         /* Draw the current line */
2981         draw_view_line(view, view->lineno - view->offset);
2983         wnoutrefresh(view->win);
2984         report("");
2988 /*
2989  * Searching
2990  */
2992 static void search_view(struct view *view, enum request request);
2994 static bool
2995 grep_text(struct view *view, const char *text[])
2997         regmatch_t pmatch;
2998         size_t i;
3000         for (i = 0; text[i]; i++)
3001                 if (*text[i] &&
3002                     regexec(view->regex, text[i], 1, &pmatch, 0) != REG_NOMATCH)
3003                         return TRUE;
3004         return FALSE;
3007 static void
3008 select_view_line(struct view *view, unsigned long lineno)
3010         unsigned long old_lineno = view->lineno;
3011         unsigned long old_offset = view->offset;
3013         if (goto_view_line(view, view->offset, lineno)) {
3014                 if (view_is_displayed(view)) {
3015                         if (old_offset != view->offset) {
3016                                 redraw_view(view);
3017                         } else {
3018                                 draw_view_line(view, old_lineno - view->offset);
3019                                 draw_view_line(view, view->lineno - view->offset);
3020                                 wnoutrefresh(view->win);
3021                         }
3022                 } else {
3023                         view->ops->select(view, &view->line[view->lineno]);
3024                 }
3025         }
3028 static void
3029 find_next(struct view *view, enum request request)
3031         unsigned long lineno = view->lineno;
3032         int direction;
3034         if (!*view->grep) {
3035                 if (!*opt_search)
3036                         report("No previous search");
3037                 else
3038                         search_view(view, request);
3039                 return;
3040         }
3042         switch (request) {
3043         case REQ_SEARCH:
3044         case REQ_FIND_NEXT:
3045                 direction = 1;
3046                 break;
3048         case REQ_SEARCH_BACK:
3049         case REQ_FIND_PREV:
3050                 direction = -1;
3051                 break;
3053         default:
3054                 return;
3055         }
3057         if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
3058                 lineno += direction;
3060         /* Note, lineno is unsigned long so will wrap around in which case it
3061          * will become bigger than view->lines. */
3062         for (; lineno < view->lines; lineno += direction) {
3063                 if (view->ops->grep(view, &view->line[lineno])) {
3064                         select_view_line(view, lineno);
3065                         report("Line %ld matches '%s'", lineno + 1, view->grep);
3066                         return;
3067                 }
3068         }
3070         report("No match found for '%s'", view->grep);
3073 static void
3074 search_view(struct view *view, enum request request)
3076         int regex_err;
3078         if (view->regex) {
3079                 regfree(view->regex);
3080                 *view->grep = 0;
3081         } else {
3082                 view->regex = calloc(1, sizeof(*view->regex));
3083                 if (!view->regex)
3084                         return;
3085         }
3087         regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
3088         if (regex_err != 0) {
3089                 char buf[SIZEOF_STR] = "unknown error";
3091                 regerror(regex_err, view->regex, buf, sizeof(buf));
3092                 report("Search failed: %s", buf);
3093                 return;
3094         }
3096         string_copy(view->grep, opt_search);
3098         find_next(view, request);
3101 /*
3102  * Incremental updating
3103  */
3105 static void
3106 reset_view(struct view *view)
3108         int i;
3110         for (i = 0; i < view->lines; i++)
3111                 free(view->line[i].data);
3112         free(view->line);
3114         view->p_offset = view->offset;
3115         view->p_yoffset = view->yoffset;
3116         view->p_lineno = view->lineno;
3118         view->line = NULL;
3119         view->offset = 0;
3120         view->yoffset = 0;
3121         view->lines  = 0;
3122         view->lineno = 0;
3123         view->vid[0] = 0;
3124         view->update_secs = 0;
3127 static const char *
3128 format_arg(const char *name)
3130         static struct {
3131                 const char *name;
3132                 size_t namelen;
3133                 const char *value;
3134                 const char *value_if_empty;
3135         } vars[] = {
3136 #define FORMAT_VAR(name, value, value_if_empty) \
3137         { name, STRING_SIZE(name), value, value_if_empty }
3138                 FORMAT_VAR("%(directory)",      opt_path,       ""),
3139                 FORMAT_VAR("%(file)",           opt_file,       ""),
3140                 FORMAT_VAR("%(ref)",            opt_ref,        "HEAD"),
3141                 FORMAT_VAR("%(head)",           ref_head,       ""),
3142                 FORMAT_VAR("%(commit)",         ref_commit,     ""),
3143                 FORMAT_VAR("%(blob)",           ref_blob,       ""),
3144                 FORMAT_VAR("%(branch)",         ref_branch,     ""),
3145         };
3146         int i;
3148         for (i = 0; i < ARRAY_SIZE(vars); i++)
3149                 if (!strncmp(name, vars[i].name, vars[i].namelen))
3150                         return *vars[i].value ? vars[i].value : vars[i].value_if_empty;
3152         report("Unknown replacement: `%s`", name);
3153         return NULL;
3156 static bool
3157 format_argv(const char ***dst_argv, const char *src_argv[], bool replace)
3159         char buf[SIZEOF_STR];
3160         int argc;
3162         argv_free(*dst_argv);
3164         for (argc = 0; src_argv[argc]; argc++) {
3165                 const char *arg = src_argv[argc];
3166                 size_t bufpos = 0;
3168                 while (arg) {
3169                         char *next = strstr(arg, "%(");
3170                         int len = next - arg;
3171                         const char *value;
3173                         if (!next || !replace) {
3174                                 len = strlen(arg);
3175                                 value = "";
3177                         } else {
3178                                 value = format_arg(next);
3180                                 if (!value) {
3181                                         return FALSE;
3182                                 }
3183                         }
3185                         if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
3186                                 return FALSE;
3188                         arg = next && replace ? strchr(next, ')') + 1 : NULL;
3189                 }
3191                 if (!argv_append(dst_argv, buf))
3192                         break;
3193         }
3195         return src_argv[argc] == NULL;
3198 static bool
3199 restore_view_position(struct view *view)
3201         if (!view->p_restore || (view->pipe && view->lines <= view->p_lineno))
3202                 return FALSE;
3204         /* Changing the view position cancels the restoring. */
3205         /* FIXME: Changing back to the first line is not detected. */
3206         if (view->offset != 0 || view->lineno != 0) {
3207                 view->p_restore = FALSE;
3208                 return FALSE;
3209         }
3211         if (goto_view_line(view, view->p_offset, view->p_lineno) &&
3212             view_is_displayed(view))
3213                 werase(view->win);
3215         view->yoffset = view->p_yoffset;
3216         view->p_restore = FALSE;
3218         return TRUE;
3221 static void
3222 end_update(struct view *view, bool force)
3224         if (!view->pipe)
3225                 return;
3226         while (!view->ops->read(view, NULL))
3227                 if (!force)
3228                         return;
3229         if (force)
3230                 io_kill(view->pipe);
3231         io_done(view->pipe);
3232         view->pipe = NULL;
3235 static void
3236 setup_update(struct view *view, const char *vid)
3238         reset_view(view);
3239         string_copy_rev(view->vid, vid);
3240         view->pipe = &view->io;
3241         view->start_time = time(NULL);
3244 static bool
3245 prepare_io(struct view *view, const char *dir, const char *argv[], bool replace)
3247         view->dir = dir;
3248         return format_argv(&view->argv, argv, replace);
3251 static bool
3252 prepare_update(struct view *view, const char *argv[], const char *dir)
3254         if (view->pipe)
3255                 end_update(view, TRUE);
3256         return prepare_io(view, dir, argv, FALSE);
3259 static bool
3260 start_update(struct view *view, const char **argv, const char *dir)
3262         if (view->pipe)
3263                 io_done(view->pipe);
3264         return prepare_io(view, dir, argv, FALSE) &&
3265                io_run(&view->io, IO_RD, dir, view->argv);
3268 static bool
3269 prepare_update_file(struct view *view, const char *name)
3271         if (view->pipe)
3272                 end_update(view, TRUE);
3273         argv_free(view->argv);
3274         return io_open(&view->io, "%s/%s", opt_cdup[0] ? opt_cdup : ".", name);
3277 static bool
3278 begin_update(struct view *view, bool refresh)
3280         if (view->pipe)
3281                 end_update(view, TRUE);
3283         if (!refresh) {
3284                 if (view->ops->prepare) {
3285                         if (!view->ops->prepare(view))
3286                                 return FALSE;
3287                 } else if (!prepare_io(view, NULL, view->ops->argv, TRUE)) {
3288                         return FALSE;
3289                 }
3291                 /* Put the current ref_* value to the view title ref
3292                  * member. This is needed by the blob view. Most other
3293                  * views sets it automatically after loading because the
3294                  * first line is a commit line. */
3295                 string_copy_rev(view->ref, view->id);
3296         }
3298         if (view->argv && view->argv[0] &&
3299             !io_run(&view->io, IO_RD, view->dir, view->argv))
3300                 return FALSE;
3302         setup_update(view, view->id);
3304         return TRUE;
3307 static bool
3308 update_view(struct view *view)
3310         char out_buffer[BUFSIZ * 2];
3311         char *line;
3312         /* Clear the view and redraw everything since the tree sorting
3313          * might have rearranged things. */
3314         bool redraw = view->lines == 0;
3315         bool can_read = TRUE;
3317         if (!view->pipe)
3318                 return TRUE;
3320         if (!io_can_read(view->pipe)) {
3321                 if (view->lines == 0 && view_is_displayed(view)) {
3322                         time_t secs = time(NULL) - view->start_time;
3324                         if (secs > 1 && secs > view->update_secs) {
3325                                 if (view->update_secs == 0)
3326                                         redraw_view(view);
3327                                 update_view_title(view);
3328                                 view->update_secs = secs;
3329                         }
3330                 }
3331                 return TRUE;
3332         }
3334         for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
3335                 if (opt_iconv_in != ICONV_NONE) {
3336                         ICONV_CONST char *inbuf = line;
3337                         size_t inlen = strlen(line) + 1;
3339                         char *outbuf = out_buffer;
3340                         size_t outlen = sizeof(out_buffer);
3342                         size_t ret;
3344                         ret = iconv(opt_iconv_in, &inbuf, &inlen, &outbuf, &outlen);
3345                         if (ret != (size_t) -1)
3346                                 line = out_buffer;
3347                 }
3349                 if (!view->ops->read(view, line)) {
3350                         report("Allocation failure");
3351                         end_update(view, TRUE);
3352                         return FALSE;
3353                 }
3354         }
3356         {
3357                 unsigned long lines = view->lines;
3358                 int digits;
3360                 for (digits = 0; lines; digits++)
3361                         lines /= 10;
3363                 /* Keep the displayed view in sync with line number scaling. */
3364                 if (digits != view->digits) {
3365                         view->digits = digits;
3366                         if (opt_line_number || view->type == VIEW_BLAME)
3367                                 redraw = TRUE;
3368                 }
3369         }
3371         if (io_error(view->pipe)) {
3372                 report("Failed to read: %s", io_strerror(view->pipe));
3373                 end_update(view, TRUE);
3375         } else if (io_eof(view->pipe)) {
3376                 if (view_is_displayed(view))
3377                         report("");
3378                 end_update(view, FALSE);
3379         }
3381         if (restore_view_position(view))
3382                 redraw = TRUE;
3384         if (!view_is_displayed(view))
3385                 return TRUE;
3387         if (redraw)
3388                 redraw_view_from(view, 0);
3389         else
3390                 redraw_view_dirty(view);
3392         /* Update the title _after_ the redraw so that if the redraw picks up a
3393          * commit reference in view->ref it'll be available here. */
3394         update_view_title(view);
3395         return TRUE;
3398 DEFINE_ALLOCATOR(realloc_lines, struct line, 256)
3400 static struct line *
3401 add_line_data(struct view *view, void *data, enum line_type type)
3403         struct line *line;
3405         if (!realloc_lines(&view->line, view->lines, 1))
3406                 return NULL;
3408         line = &view->line[view->lines++];
3409         memset(line, 0, sizeof(*line));
3410         line->type = type;
3411         line->data = data;
3412         line->dirty = 1;
3414         return line;
3417 static struct line *
3418 add_line_text(struct view *view, const char *text, enum line_type type)
3420         char *data = text ? strdup(text) : NULL;
3422         return data ? add_line_data(view, data, type) : NULL;
3425 static struct line *
3426 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
3428         char buf[SIZEOF_STR];
3429         va_list args;
3431         va_start(args, fmt);
3432         if (vsnprintf(buf, sizeof(buf), fmt, args) >= sizeof(buf))
3433                 buf[0] = 0;
3434         va_end(args);
3436         return buf[0] ? add_line_text(view, buf, type) : NULL;
3439 /*
3440  * View opening
3441  */
3443 enum open_flags {
3444         OPEN_DEFAULT = 0,       /* Use default view switching. */
3445         OPEN_SPLIT = 1,         /* Split current view. */
3446         OPEN_RELOAD = 4,        /* Reload view even if it is the current. */
3447         OPEN_REFRESH = 16,      /* Refresh view using previous command. */
3448         OPEN_PREPARED = 32,     /* Open already prepared command. */
3449 };
3451 static void
3452 open_view(struct view *prev, enum request request, enum open_flags flags)
3454         bool split = !!(flags & OPEN_SPLIT);
3455         bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
3456         bool nomaximize = !!(flags & OPEN_REFRESH);
3457         struct view *view = VIEW(request);
3458         int nviews = displayed_views();
3459         struct view *base_view = display[0];
3461         if (view == prev && nviews == 1 && !reload) {
3462                 report("Already in %s view", view->name);
3463                 return;
3464         }
3466         if (view->git_dir && !opt_git_dir[0]) {
3467                 report("The %s view is disabled in pager view", view->name);
3468                 return;
3469         }
3471         if (split) {
3472                 display[1] = view;
3473                 current_view = 1;
3474                 view->parent = prev;
3475         } else if (!nomaximize) {
3476                 /* Maximize the current view. */
3477                 memset(display, 0, sizeof(display));
3478                 current_view = 0;
3479                 display[current_view] = view;
3480         }
3482         /* No prev signals that this is the first loaded view. */
3483         if (prev && view != prev) {
3484                 view->prev = prev;
3485         }
3487         /* Resize the view when switching between split- and full-screen,
3488          * or when switching between two different full-screen views. */
3489         if (nviews != displayed_views() ||
3490             (nviews == 1 && base_view != display[0]))
3491                 resize_display();
3493         if (view->ops->open) {
3494                 if (view->pipe)
3495                         end_update(view, TRUE);
3496                 if (!view->ops->open(view)) {
3497                         report("Failed to load %s view", view->name);
3498                         return;
3499                 }
3500                 restore_view_position(view);
3502         } else if ((reload || strcmp(view->vid, view->id)) &&
3503                    !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
3504                 report("Failed to load %s view", view->name);
3505                 return;
3506         }
3508         if (split && prev->lineno - prev->offset >= prev->height) {
3509                 /* Take the title line into account. */
3510                 int lines = prev->lineno - prev->offset - prev->height + 1;
3512                 /* Scroll the view that was split if the current line is
3513                  * outside the new limited view. */
3514                 do_scroll_view(prev, lines);
3515         }
3517         if (prev && view != prev && split && view_is_displayed(prev)) {
3518                 /* "Blur" the previous view. */
3519                 update_view_title(prev);
3520         }
3522         if (view->pipe && view->lines == 0) {
3523                 /* Clear the old view and let the incremental updating refill
3524                  * the screen. */
3525                 werase(view->win);
3526                 view->p_restore = flags & (OPEN_RELOAD | OPEN_REFRESH);
3527                 report("");
3528         } else if (view_is_displayed(view)) {
3529                 redraw_view(view);
3530                 report("");
3531         }
3534 static void
3535 open_external_viewer(const char *argv[], const char *dir)
3537         def_prog_mode();           /* save current tty modes */
3538         endwin();                  /* restore original tty modes */
3539         io_run_fg(argv, dir);
3540         fprintf(stderr, "Press Enter to continue");
3541         getc(opt_tty);
3542         reset_prog_mode();
3543         redraw_display(TRUE);
3546 static void
3547 open_mergetool(const char *file)
3549         const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
3551         open_external_viewer(mergetool_argv, opt_cdup);
3554 static void
3555 open_editor(const char *file)
3557         const char *editor_argv[] = { "vi", file, NULL };
3558         const char *editor;
3560         editor = getenv("GIT_EDITOR");
3561         if (!editor && *opt_editor)
3562                 editor = opt_editor;
3563         if (!editor)
3564                 editor = getenv("VISUAL");
3565         if (!editor)
3566                 editor = getenv("EDITOR");
3567         if (!editor)
3568                 editor = "vi";
3570         editor_argv[0] = editor;
3571         open_external_viewer(editor_argv, opt_cdup);
3574 static void
3575 open_run_request(enum request request)
3577         struct run_request *req = get_run_request(request);
3578         const char **argv = NULL;
3580         if (!req) {
3581                 report("Unknown run request");
3582                 return;
3583         }
3585         if (format_argv(&argv, req->argv, TRUE))
3586                 open_external_viewer(argv, NULL);
3587         if (argv)
3588                 argv_free(argv);
3589         free(argv);
3592 /*
3593  * User request switch noodle
3594  */
3596 static int
3597 view_driver(struct view *view, enum request request)
3599         int i;
3601         if (request == REQ_NONE)
3602                 return TRUE;
3604         if (request > REQ_NONE) {
3605                 open_run_request(request);
3606                 view_request(view, REQ_REFRESH);
3607                 return TRUE;
3608         }
3610         request = view_request(view, request);
3611         if (request == REQ_NONE)
3612                 return TRUE;
3614         switch (request) {
3615         case REQ_MOVE_UP:
3616         case REQ_MOVE_DOWN:
3617         case REQ_MOVE_PAGE_UP:
3618         case REQ_MOVE_PAGE_DOWN:
3619         case REQ_MOVE_FIRST_LINE:
3620         case REQ_MOVE_LAST_LINE:
3621                 move_view(view, request);
3622                 break;
3624         case REQ_SCROLL_LEFT:
3625         case REQ_SCROLL_RIGHT:
3626         case REQ_SCROLL_LINE_DOWN:
3627         case REQ_SCROLL_LINE_UP:
3628         case REQ_SCROLL_PAGE_DOWN:
3629         case REQ_SCROLL_PAGE_UP:
3630                 scroll_view(view, request);
3631                 break;
3633         case REQ_VIEW_BLAME:
3634                 if (!opt_file[0]) {
3635                         report("No file chosen, press %s to open tree view",
3636                                get_key(view->keymap, REQ_VIEW_TREE));
3637                         break;
3638                 }
3639                 open_view(view, request, OPEN_DEFAULT);
3640                 break;
3642         case REQ_VIEW_BLOB:
3643                 if (!ref_blob[0]) {
3644                         report("No file chosen, press %s to open tree view",
3645                                get_key(view->keymap, REQ_VIEW_TREE));
3646                         break;
3647                 }
3648                 open_view(view, request, OPEN_DEFAULT);
3649                 break;
3651         case REQ_VIEW_PAGER:
3652                 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3653                         report("No pager content, press %s to run command from prompt",
3654                                get_key(view->keymap, REQ_PROMPT));
3655                         break;
3656                 }
3657                 open_view(view, request, OPEN_DEFAULT);
3658                 break;
3660         case REQ_VIEW_STAGE:
3661                 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3662                         report("No stage content, press %s to open the status view and choose file",
3663                                get_key(view->keymap, REQ_VIEW_STATUS));
3664                         break;
3665                 }
3666                 open_view(view, request, OPEN_DEFAULT);
3667                 break;
3669         case REQ_VIEW_STATUS:
3670                 if (opt_is_inside_work_tree == FALSE) {
3671                         report("The status view requires a working tree");
3672                         break;
3673                 }
3674                 open_view(view, request, OPEN_DEFAULT);
3675                 break;
3677         case REQ_VIEW_MAIN:
3678         case REQ_VIEW_DIFF:
3679         case REQ_VIEW_LOG:
3680         case REQ_VIEW_TREE:
3681         case REQ_VIEW_HELP:
3682         case REQ_VIEW_BRANCH:
3683                 open_view(view, request, OPEN_DEFAULT);
3684                 break;
3686         case REQ_NEXT:
3687         case REQ_PREVIOUS:
3688                 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3690                 if (view->parent) {
3691                         int line;
3693                         view = view->parent;
3694                         line = view->lineno;
3695                         move_view(view, request);
3696                         if (view_is_displayed(view))
3697                                 update_view_title(view);
3698                         if (line != view->lineno)
3699                                 view_request(view, REQ_ENTER);
3700                 } else {
3701                         move_view(view, request);
3702                 }
3703                 break;
3705         case REQ_VIEW_NEXT:
3706         {
3707                 int nviews = displayed_views();
3708                 int next_view = (current_view + 1) % nviews;
3710                 if (next_view == current_view) {
3711                         report("Only one view is displayed");
3712                         break;
3713                 }
3715                 current_view = next_view;
3716                 /* Blur out the title of the previous view. */
3717                 update_view_title(view);
3718                 report("");
3719                 break;
3720         }
3721         case REQ_REFRESH:
3722                 report("Refreshing is not yet supported for the %s view", view->name);
3723                 break;
3725         case REQ_MAXIMIZE:
3726                 if (displayed_views() == 2)
3727                         maximize_view(view);
3728                 break;
3730         case REQ_OPTIONS:
3731                 open_option_menu();
3732                 break;
3734         case REQ_TOGGLE_LINENO:
3735                 toggle_view_option(&opt_line_number, "line numbers");
3736                 break;
3738         case REQ_TOGGLE_DATE:
3739                 toggle_date();
3740                 break;
3742         case REQ_TOGGLE_AUTHOR:
3743                 toggle_author();
3744                 break;
3746         case REQ_TOGGLE_REV_GRAPH:
3747                 toggle_view_option(&opt_rev_graph, "revision graph display");
3748                 break;
3750         case REQ_TOGGLE_REFS:
3751                 toggle_view_option(&opt_show_refs, "reference display");
3752                 break;
3754         case REQ_TOGGLE_SORT_FIELD:
3755         case REQ_TOGGLE_SORT_ORDER:
3756                 report("Sorting is not yet supported for the %s view", view->name);
3757                 break;
3759         case REQ_SEARCH:
3760         case REQ_SEARCH_BACK:
3761                 search_view(view, request);
3762                 break;
3764         case REQ_FIND_NEXT:
3765         case REQ_FIND_PREV:
3766                 find_next(view, request);
3767                 break;
3769         case REQ_STOP_LOADING:
3770                 foreach_view(view, i) {
3771                         if (view->pipe)
3772                                 report("Stopped loading the %s view", view->name),
3773                         end_update(view, TRUE);
3774                 }
3775                 break;
3777         case REQ_SHOW_VERSION:
3778                 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3779                 return TRUE;
3781         case REQ_SCREEN_REDRAW:
3782                 redraw_display(TRUE);
3783                 break;
3785         case REQ_EDIT:
3786                 report("Nothing to edit");
3787                 break;
3789         case REQ_ENTER:
3790                 report("Nothing to enter");
3791                 break;
3793         case REQ_VIEW_CLOSE:
3794                 /* XXX: Mark closed views by letting view->prev point to the
3795                  * view itself. Parents to closed view should never be
3796                  * followed. */
3797                 if (view->prev && view->prev != view) {
3798                         maximize_view(view->prev);
3799                         view->prev = view;
3800                         break;
3801                 }
3802                 /* Fall-through */
3803         case REQ_QUIT:
3804                 return FALSE;
3806         default:
3807                 report("Unknown key, press %s for help",
3808                        get_key(view->keymap, REQ_VIEW_HELP));
3809                 return TRUE;
3810         }
3812         return TRUE;
3816 /*
3817  * View backend utilities
3818  */
3820 enum sort_field {
3821         ORDERBY_NAME,
3822         ORDERBY_DATE,
3823         ORDERBY_AUTHOR,
3824 };
3826 struct sort_state {
3827         const enum sort_field *fields;
3828         size_t size, current;
3829         bool reverse;
3830 };
3832 #define SORT_STATE(fields) { fields, ARRAY_SIZE(fields), 0 }
3833 #define get_sort_field(state) ((state).fields[(state).current])
3834 #define sort_order(state, result) ((state).reverse ? -(result) : (result))
3836 static void
3837 sort_view(struct view *view, enum request request, struct sort_state *state,
3838           int (*compare)(const void *, const void *))
3840         switch (request) {
3841         case REQ_TOGGLE_SORT_FIELD:
3842                 state->current = (state->current + 1) % state->size;
3843                 break;
3845         case REQ_TOGGLE_SORT_ORDER:
3846                 state->reverse = !state->reverse;
3847                 break;
3848         default:
3849                 die("Not a sort request");
3850         }
3852         qsort(view->line, view->lines, sizeof(*view->line), compare);
3853         redraw_view(view);
3856 DEFINE_ALLOCATOR(realloc_authors, const char *, 256)
3858 /* Small author cache to reduce memory consumption. It uses binary
3859  * search to lookup or find place to position new entries. No entries
3860  * are ever freed. */
3861 static const char *
3862 get_author(const char *name)
3864         static const char **authors;
3865         static size_t authors_size;
3866         int from = 0, to = authors_size - 1;
3868         while (from <= to) {
3869                 size_t pos = (to + from) / 2;
3870                 int cmp = strcmp(name, authors[pos]);
3872                 if (!cmp)
3873                         return authors[pos];
3875                 if (cmp < 0)
3876                         to = pos - 1;
3877                 else
3878                         from = pos + 1;
3879         }
3881         if (!realloc_authors(&authors, authors_size, 1))
3882                 return NULL;
3883         name = strdup(name);
3884         if (!name)
3885                 return NULL;
3887         memmove(authors + from + 1, authors + from, (authors_size - from) * sizeof(*authors));
3888         authors[from] = name;
3889         authors_size++;
3891         return name;
3894 static void
3895 parse_timesec(struct time *time, const char *sec)
3897         time->sec = (time_t) atol(sec);
3900 static void
3901 parse_timezone(struct time *time, const char *zone)
3903         long tz;
3905         tz  = ('0' - zone[1]) * 60 * 60 * 10;
3906         tz += ('0' - zone[2]) * 60 * 60;
3907         tz += ('0' - zone[3]) * 60 * 10;
3908         tz += ('0' - zone[4]) * 60;
3910         if (zone[0] == '-')
3911                 tz = -tz;
3913         time->tz = tz;
3914         time->sec -= tz;
3917 /* Parse author lines where the name may be empty:
3918  *      author  <email@address.tld> 1138474660 +0100
3919  */
3920 static void
3921 parse_author_line(char *ident, const char **author, struct time *time)
3923         char *nameend = strchr(ident, '<');
3924         char *emailend = strchr(ident, '>');
3926         if (nameend && emailend)
3927                 *nameend = *emailend = 0;
3928         ident = chomp_string(ident);
3929         if (!*ident) {
3930                 if (nameend)
3931                         ident = chomp_string(nameend + 1);
3932                 if (!*ident)
3933                         ident = "Unknown";
3934         }
3936         *author = get_author(ident);
3938         /* Parse epoch and timezone */
3939         if (emailend && emailend[1] == ' ') {
3940                 char *secs = emailend + 2;
3941                 char *zone = strchr(secs, ' ');
3943                 parse_timesec(time, secs);
3945                 if (zone && strlen(zone) == STRING_SIZE(" +0700"))
3946                         parse_timezone(time, zone + 1);
3947         }
3950 static bool
3951 open_commit_parent_menu(char buf[SIZEOF_STR], int *parents)
3953         char rev[SIZEOF_REV];
3954         const char *revlist_argv[] = {
3955                 "git", "log", "--no-color", "-1", "--pretty=format:%s", rev, NULL
3956         };
3957         struct menu_item *items;
3958         char text[SIZEOF_STR];
3959         bool ok = TRUE;
3960         int i;
3962         items = calloc(*parents + 1, sizeof(*items));
3963         if (!items)
3964                 return FALSE;
3966         for (i = 0; i < *parents; i++) {
3967                 string_copy_rev(rev, &buf[SIZEOF_REV * i]);
3968                 if (!io_run_buf(revlist_argv, text, sizeof(text)) ||
3969                     !(items[i].text = strdup(text))) {
3970                         ok = FALSE;
3971                         break;
3972                 }
3973         }
3975         if (ok) {
3976                 *parents = 0;
3977                 ok = prompt_menu("Select parent", items, parents);
3978         }
3979         for (i = 0; items[i].text; i++)
3980                 free((char *) items[i].text);
3981         free(items);
3982         return ok;
3985 static bool
3986 select_commit_parent(const char *id, char rev[SIZEOF_REV], const char *path)
3988         char buf[SIZEOF_STR * 4];
3989         const char *revlist_argv[] = {
3990                 "git", "log", "--no-color", "-1",
3991                         "--pretty=format:%P", id, "--", path, NULL
3992         };
3993         int parents;
3995         if (!io_run_buf(revlist_argv, buf, sizeof(buf)) ||
3996             (parents = strlen(buf) / 40) < 0) {
3997                 report("Failed to get parent information");
3998                 return FALSE;
4000         } else if (parents == 0) {
4001                 if (path)
4002                         report("Path '%s' does not exist in the parent", path);
4003                 else
4004                         report("The selected commit has no parents");
4005                 return FALSE;
4006         }
4008         if (parents == 1)
4009                 parents = 0;
4010         else if (!open_commit_parent_menu(buf, &parents))
4011                 return FALSE;
4013         string_copy_rev(rev, &buf[41 * parents]);
4014         return TRUE;
4017 /*
4018  * Pager backend
4019  */
4021 static bool
4022 pager_draw(struct view *view, struct line *line, unsigned int lineno)
4024         char text[SIZEOF_STR];
4026         if (opt_line_number && draw_lineno(view, lineno))
4027                 return TRUE;
4029         string_expand(text, sizeof(text), line->data, opt_tab_size);
4030         draw_text(view, line->type, text, TRUE);
4031         return TRUE;
4034 static bool
4035 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
4037         const char *describe_argv[] = { "git", "describe", commit_id, NULL };
4038         char ref[SIZEOF_STR];
4040         if (!io_run_buf(describe_argv, ref, sizeof(ref)) || !*ref)
4041                 return TRUE;
4043         /* This is the only fatal call, since it can "corrupt" the buffer. */
4044         if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
4045                 return FALSE;
4047         return TRUE;
4050 static void
4051 add_pager_refs(struct view *view, struct line *line)
4053         char buf[SIZEOF_STR];
4054         char *commit_id = (char *)line->data + STRING_SIZE("commit ");
4055         struct ref_list *list;
4056         size_t bufpos = 0, i;
4057         const char *sep = "Refs: ";
4058         bool is_tag = FALSE;
4060         assert(line->type == LINE_COMMIT);
4062         list = get_ref_list(commit_id);
4063         if (!list) {
4064                 if (view->type == VIEW_DIFF)
4065                         goto try_add_describe_ref;
4066                 return;
4067         }
4069         for (i = 0; i < list->size; i++) {
4070                 struct ref *ref = list->refs[i];
4071                 const char *fmt = ref->tag    ? "%s[%s]" :
4072                                   ref->remote ? "%s<%s>" : "%s%s";
4074                 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
4075                         return;
4076                 sep = ", ";
4077                 if (ref->tag)
4078                         is_tag = TRUE;
4079         }
4081         if (!is_tag && view->type == VIEW_DIFF) {
4082 try_add_describe_ref:
4083                 /* Add <tag>-g<commit_id> "fake" reference. */
4084                 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
4085                         return;
4086         }
4088         if (bufpos == 0)
4089                 return;
4091         add_line_text(view, buf, LINE_PP_REFS);
4094 static bool
4095 pager_read(struct view *view, char *data)
4097         struct line *line;
4099         if (!data)
4100                 return TRUE;
4102         line = add_line_text(view, data, get_line_type(data));
4103         if (!line)
4104                 return FALSE;
4106         if (line->type == LINE_COMMIT &&
4107             (view->type == VIEW_DIFF ||
4108              view->type == VIEW_LOG))
4109                 add_pager_refs(view, line);
4111         return TRUE;
4114 static enum request
4115 pager_request(struct view *view, enum request request, struct line *line)
4117         int split = 0;
4119         if (request != REQ_ENTER)
4120                 return request;
4122         if (line->type == LINE_COMMIT &&
4123            (view->type == VIEW_LOG ||
4124             view->type == VIEW_PAGER)) {
4125                 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
4126                 split = 1;
4127         }
4129         /* Always scroll the view even if it was split. That way
4130          * you can use Enter to scroll through the log view and
4131          * split open each commit diff. */
4132         scroll_view(view, REQ_SCROLL_LINE_DOWN);
4134         /* FIXME: A minor workaround. Scrolling the view will call report("")
4135          * but if we are scrolling a non-current view this won't properly
4136          * update the view title. */
4137         if (split)
4138                 update_view_title(view);
4140         return REQ_NONE;
4143 static bool
4144 pager_grep(struct view *view, struct line *line)
4146         const char *text[] = { line->data, NULL };
4148         return grep_text(view, text);
4151 static void
4152 pager_select(struct view *view, struct line *line)
4154         if (line->type == LINE_COMMIT) {
4155                 char *text = (char *)line->data + STRING_SIZE("commit ");
4157                 if (view->type != VIEW_PAGER)
4158                         string_copy_rev(view->ref, text);
4159                 string_copy_rev(ref_commit, text);
4160         }
4163 static struct view_ops pager_ops = {
4164         "line",
4165         NULL,
4166         NULL,
4167         pager_read,
4168         pager_draw,
4169         pager_request,
4170         pager_grep,
4171         pager_select,
4172 };
4174 static const char *log_argv[SIZEOF_ARG] = {
4175         "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
4176 };
4178 static enum request
4179 log_request(struct view *view, enum request request, struct line *line)
4181         switch (request) {
4182         case REQ_REFRESH:
4183                 load_refs();
4184                 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
4185                 return REQ_NONE;
4186         default:
4187                 return pager_request(view, request, line);
4188         }
4191 static struct view_ops log_ops = {
4192         "line",
4193         log_argv,
4194         NULL,
4195         pager_read,
4196         pager_draw,
4197         log_request,
4198         pager_grep,
4199         pager_select,
4200 };
4202 static const char *diff_argv[SIZEOF_ARG] = {
4203         "git", "show", "--pretty=fuller", "--no-color", "--root",
4204                 "--patch-with-stat", "--find-copies-harder", "-C", "%(commit)", NULL
4205 };
4207 static struct view_ops diff_ops = {
4208         "line",
4209         diff_argv,
4210         NULL,
4211         pager_read,
4212         pager_draw,
4213         pager_request,
4214         pager_grep,
4215         pager_select,
4216 };
4218 /*
4219  * Help backend
4220  */
4222 static bool help_keymap_hidden[ARRAY_SIZE(keymap_table)];
4224 static bool
4225 help_open_keymap_title(struct view *view, enum keymap keymap)
4227         struct line *line;
4229         line = add_line_format(view, LINE_HELP_KEYMAP, "[%c] %s bindings",
4230                                help_keymap_hidden[keymap] ? '+' : '-',
4231                                enum_name(keymap_table[keymap]));
4232         if (line)
4233                 line->other = keymap;
4235         return help_keymap_hidden[keymap];
4238 static void
4239 help_open_keymap(struct view *view, enum keymap keymap)
4241         const char *group = NULL;
4242         char buf[SIZEOF_STR];
4243         size_t bufpos;
4244         bool add_title = TRUE;
4245         int i;
4247         for (i = 0; i < ARRAY_SIZE(req_info); i++) {
4248                 const char *key = NULL;
4250                 if (req_info[i].request == REQ_NONE)
4251                         continue;
4253                 if (!req_info[i].request) {
4254                         group = req_info[i].help;
4255                         continue;
4256                 }
4258                 key = get_keys(keymap, req_info[i].request, TRUE);
4259                 if (!key || !*key)
4260                         continue;
4262                 if (add_title && help_open_keymap_title(view, keymap))
4263                         return;
4264                 add_title = FALSE;
4266                 if (group) {
4267                         add_line_text(view, group, LINE_HELP_GROUP);
4268                         group = NULL;
4269                 }
4271                 add_line_format(view, LINE_DEFAULT, "    %-25s %-20s %s", key,
4272                                 enum_name(req_info[i]), req_info[i].help);
4273         }
4275         group = "External commands:";
4277         for (i = 0; i < run_requests; i++) {
4278                 struct run_request *req = get_run_request(REQ_NONE + i + 1);
4279                 const char *key;
4280                 int argc;
4282                 if (!req || req->keymap != keymap)
4283                         continue;
4285                 key = get_key_name(req->key);
4286                 if (!*key)
4287                         key = "(no key defined)";
4289                 if (add_title && help_open_keymap_title(view, keymap))
4290                         return;
4291                 if (group) {
4292                         add_line_text(view, group, LINE_HELP_GROUP);
4293                         group = NULL;
4294                 }
4296                 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
4297                         if (!string_format_from(buf, &bufpos, "%s%s",
4298                                                 argc ? " " : "", req->argv[argc]))
4299                                 return;
4301                 add_line_format(view, LINE_DEFAULT, "    %-25s `%s`", key, buf);
4302         }
4305 static bool
4306 help_open(struct view *view)
4308         enum keymap keymap;
4310         reset_view(view);
4311         add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
4312         add_line_text(view, "", LINE_DEFAULT);
4314         for (keymap = 0; keymap < ARRAY_SIZE(keymap_table); keymap++)
4315                 help_open_keymap(view, keymap);
4317         return TRUE;
4320 static enum request
4321 help_request(struct view *view, enum request request, struct line *line)
4323         switch (request) {
4324         case REQ_ENTER:
4325                 if (line->type == LINE_HELP_KEYMAP) {
4326                         help_keymap_hidden[line->other] =
4327                                 !help_keymap_hidden[line->other];
4328                         view->p_restore = TRUE;
4329                         open_view(view, REQ_VIEW_HELP, OPEN_REFRESH);
4330                 }
4332                 return REQ_NONE;
4333         default:
4334                 return pager_request(view, request, line);
4335         }
4338 static struct view_ops help_ops = {
4339         "line",
4340         NULL,
4341         help_open,
4342         NULL,
4343         pager_draw,
4344         help_request,
4345         pager_grep,
4346         pager_select,
4347 };
4350 /*
4351  * Tree backend
4352  */
4354 struct tree_stack_entry {
4355         struct tree_stack_entry *prev;  /* Entry below this in the stack */
4356         unsigned long lineno;           /* Line number to restore */
4357         char *name;                     /* Position of name in opt_path */
4358 };
4360 /* The top of the path stack. */
4361 static struct tree_stack_entry *tree_stack = NULL;
4362 unsigned long tree_lineno = 0;
4364 static void
4365 pop_tree_stack_entry(void)
4367         struct tree_stack_entry *entry = tree_stack;
4369         tree_lineno = entry->lineno;
4370         entry->name[0] = 0;
4371         tree_stack = entry->prev;
4372         free(entry);
4375 static void
4376 push_tree_stack_entry(const char *name, unsigned long lineno)
4378         struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
4379         size_t pathlen = strlen(opt_path);
4381         if (!entry)
4382                 return;
4384         entry->prev = tree_stack;
4385         entry->name = opt_path + pathlen;
4386         tree_stack = entry;
4388         if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
4389                 pop_tree_stack_entry();
4390                 return;
4391         }
4393         /* Move the current line to the first tree entry. */
4394         tree_lineno = 1;
4395         entry->lineno = lineno;
4398 /* Parse output from git-ls-tree(1):
4399  *
4400  * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
4401  */
4403 #define SIZEOF_TREE_ATTR \
4404         STRING_SIZE("100644 blob f931e1d229c3e185caad4449bf5b66ed72462657\t")
4406 #define SIZEOF_TREE_MODE \
4407         STRING_SIZE("100644 ")
4409 #define TREE_ID_OFFSET \
4410         STRING_SIZE("100644 blob ")
4412 struct tree_entry {
4413         char id[SIZEOF_REV];
4414         mode_t mode;
4415         struct time time;               /* Date from the author ident. */
4416         const char *author;             /* Author of the commit. */
4417         char name[1];
4418 };
4420 static const char *
4421 tree_path(const struct line *line)
4423         return ((struct tree_entry *) line->data)->name;
4426 static int
4427 tree_compare_entry(const struct line *line1, const struct line *line2)
4429         if (line1->type != line2->type)
4430                 return line1->type == LINE_TREE_DIR ? -1 : 1;
4431         return strcmp(tree_path(line1), tree_path(line2));
4434 static const enum sort_field tree_sort_fields[] = {
4435         ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
4436 };
4437 static struct sort_state tree_sort_state = SORT_STATE(tree_sort_fields);
4439 static int
4440 tree_compare(const void *l1, const void *l2)
4442         const struct line *line1 = (const struct line *) l1;
4443         const struct line *line2 = (const struct line *) l2;
4444         const struct tree_entry *entry1 = ((const struct line *) l1)->data;
4445         const struct tree_entry *entry2 = ((const struct line *) l2)->data;
4447         if (line1->type == LINE_TREE_HEAD)
4448                 return -1;
4449         if (line2->type == LINE_TREE_HEAD)
4450                 return 1;
4452         switch (get_sort_field(tree_sort_state)) {
4453         case ORDERBY_DATE:
4454                 return sort_order(tree_sort_state, timecmp(&entry1->time, &entry2->time));
4456         case ORDERBY_AUTHOR:
4457                 return sort_order(tree_sort_state, strcmp(entry1->author, entry2->author));
4459         case ORDERBY_NAME:
4460         default:
4461                 return sort_order(tree_sort_state, tree_compare_entry(line1, line2));
4462         }
4466 static struct line *
4467 tree_entry(struct view *view, enum line_type type, const char *path,
4468            const char *mode, const char *id)
4470         struct tree_entry *entry = calloc(1, sizeof(*entry) + strlen(path));
4471         struct line *line = entry ? add_line_data(view, entry, type) : NULL;
4473         if (!entry || !line) {
4474                 free(entry);
4475                 return NULL;
4476         }
4478         strncpy(entry->name, path, strlen(path));
4479         if (mode)
4480                 entry->mode = strtoul(mode, NULL, 8);
4481         if (id)
4482                 string_copy_rev(entry->id, id);
4484         return line;
4487 static bool
4488 tree_read_date(struct view *view, char *text, bool *read_date)
4490         static const char *author_name;
4491         static struct time author_time;
4493         if (!text && *read_date) {
4494                 *read_date = FALSE;
4495                 return TRUE;
4497         } else if (!text) {
4498                 char *path = *opt_path ? opt_path : ".";
4499                 /* Find next entry to process */
4500                 const char *log_file[] = {
4501                         "git", "log", "--no-color", "--pretty=raw",
4502                                 "--cc", "--raw", view->id, "--", path, NULL
4503                 };
4505                 if (!view->lines) {
4506                         tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL);
4507                         report("Tree is empty");
4508                         return TRUE;
4509                 }
4511                 if (!start_update(view, log_file, opt_cdup)) {
4512                         report("Failed to load tree data");
4513                         return TRUE;
4514                 }
4516                 *read_date = TRUE;
4517                 return FALSE;
4519         } else if (*text == 'a' && get_line_type(text) == LINE_AUTHOR) {
4520                 parse_author_line(text + STRING_SIZE("author "),
4521                                   &author_name, &author_time);
4523         } else if (*text == ':') {
4524                 char *pos;
4525                 size_t annotated = 1;
4526                 size_t i;
4528                 pos = strchr(text, '\t');
4529                 if (!pos)
4530                         return TRUE;
4531                 text = pos + 1;
4532                 if (*opt_path && !strncmp(text, opt_path, strlen(opt_path)))
4533                         text += strlen(opt_path);
4534                 pos = strchr(text, '/');
4535                 if (pos)
4536                         *pos = 0;
4538                 for (i = 1; i < view->lines; i++) {
4539                         struct line *line = &view->line[i];
4540                         struct tree_entry *entry = line->data;
4542                         annotated += !!entry->author;
4543                         if (entry->author || strcmp(entry->name, text))
4544                                 continue;
4546                         entry->author = author_name;
4547                         entry->time = author_time;
4548                         line->dirty = 1;
4549                         break;
4550                 }
4552                 if (annotated == view->lines)
4553                         io_kill(view->pipe);
4554         }
4555         return TRUE;
4558 static bool
4559 tree_read(struct view *view, char *text)
4561         static bool read_date = FALSE;
4562         struct tree_entry *data;
4563         struct line *entry, *line;
4564         enum line_type type;
4565         size_t textlen = text ? strlen(text) : 0;
4566         char *path = text + SIZEOF_TREE_ATTR;
4568         if (read_date || !text)
4569                 return tree_read_date(view, text, &read_date);
4571         if (textlen <= SIZEOF_TREE_ATTR)
4572                 return FALSE;
4573         if (view->lines == 0 &&
4574             !tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL))
4575                 return FALSE;
4577         /* Strip the path part ... */
4578         if (*opt_path) {
4579                 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
4580                 size_t striplen = strlen(opt_path);
4582                 if (pathlen > striplen)
4583                         memmove(path, path + striplen,
4584                                 pathlen - striplen + 1);
4586                 /* Insert "link" to parent directory. */
4587                 if (view->lines == 1 &&
4588                     !tree_entry(view, LINE_TREE_DIR, "..", "040000", view->ref))
4589                         return FALSE;
4590         }
4592         type = text[SIZEOF_TREE_MODE] == 't' ? LINE_TREE_DIR : LINE_TREE_FILE;
4593         entry = tree_entry(view, type, path, text, text + TREE_ID_OFFSET);
4594         if (!entry)
4595                 return FALSE;
4596         data = entry->data;
4598         /* Skip "Directory ..." and ".." line. */
4599         for (line = &view->line[1 + !!*opt_path]; line < entry; line++) {
4600                 if (tree_compare_entry(line, entry) <= 0)
4601                         continue;
4603                 memmove(line + 1, line, (entry - line) * sizeof(*entry));
4605                 line->data = data;
4606                 line->type = type;
4607                 for (; line <= entry; line++)
4608                         line->dirty = line->cleareol = 1;
4609                 return TRUE;
4610         }
4612         if (tree_lineno > view->lineno) {
4613                 view->lineno = tree_lineno;
4614                 tree_lineno = 0;
4615         }
4617         return TRUE;
4620 static bool
4621 tree_draw(struct view *view, struct line *line, unsigned int lineno)
4623         struct tree_entry *entry = line->data;
4625         if (line->type == LINE_TREE_HEAD) {
4626                 if (draw_text(view, line->type, "Directory path /", TRUE))
4627                         return TRUE;
4628         } else {
4629                 if (draw_mode(view, entry->mode))
4630                         return TRUE;
4632                 if (opt_author && draw_author(view, entry->author))
4633                         return TRUE;
4635                 if (opt_date && draw_date(view, &entry->time))
4636                         return TRUE;
4637         }
4638         if (draw_text(view, line->type, entry->name, TRUE))
4639                 return TRUE;
4640         return TRUE;
4643 static void
4644 open_blob_editor(const char *id)
4646         const char *blob_argv[] = { "git", "cat-file", "blob", id, NULL };
4647         char file[SIZEOF_STR] = "/tmp/tigblob.XXXXXX";
4648         int fd = mkstemp(file);
4650         if (fd == -1)
4651                 report("Failed to create temporary file");
4652         else if (!io_run_append(blob_argv, fd))
4653                 report("Failed to save blob data to file");
4654         else
4655                 open_editor(file);
4656         if (fd != -1)
4657                 unlink(file);
4660 static enum request
4661 tree_request(struct view *view, enum request request, struct line *line)
4663         enum open_flags flags;
4664         struct tree_entry *entry = line->data;
4666         switch (request) {
4667         case REQ_VIEW_BLAME:
4668                 if (line->type != LINE_TREE_FILE) {
4669                         report("Blame only supported for files");
4670                         return REQ_NONE;
4671                 }
4673                 string_copy(opt_ref, view->vid);
4674                 return request;
4676         case REQ_EDIT:
4677                 if (line->type != LINE_TREE_FILE) {
4678                         report("Edit only supported for files");
4679                 } else if (!is_head_commit(view->vid)) {
4680                         open_blob_editor(entry->id);
4681                 } else {
4682                         open_editor(opt_file);
4683                 }
4684                 return REQ_NONE;
4686         case REQ_TOGGLE_SORT_FIELD:
4687         case REQ_TOGGLE_SORT_ORDER:
4688                 sort_view(view, request, &tree_sort_state, tree_compare);
4689                 return REQ_NONE;
4691         case REQ_PARENT:
4692                 if (!*opt_path) {
4693                         /* quit view if at top of tree */
4694                         return REQ_VIEW_CLOSE;
4695                 }
4696                 /* fake 'cd  ..' */
4697                 line = &view->line[1];
4698                 break;
4700         case REQ_ENTER:
4701                 break;
4703         default:
4704                 return request;
4705         }
4707         /* Cleanup the stack if the tree view is at a different tree. */
4708         while (!*opt_path && tree_stack)
4709                 pop_tree_stack_entry();
4711         switch (line->type) {
4712         case LINE_TREE_DIR:
4713                 /* Depending on whether it is a subdirectory or parent link
4714                  * mangle the path buffer. */
4715                 if (line == &view->line[1] && *opt_path) {
4716                         pop_tree_stack_entry();
4718                 } else {
4719                         const char *basename = tree_path(line);
4721                         push_tree_stack_entry(basename, view->lineno);
4722                 }
4724                 /* Trees and subtrees share the same ID, so they are not not
4725                  * unique like blobs. */
4726                 flags = OPEN_RELOAD;
4727                 request = REQ_VIEW_TREE;
4728                 break;
4730         case LINE_TREE_FILE:
4731                 flags = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
4732                 request = REQ_VIEW_BLOB;
4733                 break;
4735         default:
4736                 return REQ_NONE;
4737         }
4739         open_view(view, request, flags);
4740         if (request == REQ_VIEW_TREE)
4741                 view->lineno = tree_lineno;
4743         return REQ_NONE;
4746 static bool
4747 tree_grep(struct view *view, struct line *line)
4749         struct tree_entry *entry = line->data;
4750         const char *text[] = {
4751                 entry->name,
4752                 opt_author ? entry->author : "",
4753                 mkdate(&entry->time, opt_date),
4754                 NULL
4755         };
4757         return grep_text(view, text);
4760 static void
4761 tree_select(struct view *view, struct line *line)
4763         struct tree_entry *entry = line->data;
4765         if (line->type == LINE_TREE_FILE) {
4766                 string_copy_rev(ref_blob, entry->id);
4767                 string_format(opt_file, "%s%s", opt_path, tree_path(line));
4769         } else if (line->type != LINE_TREE_DIR) {
4770                 return;
4771         }
4773         string_copy_rev(view->ref, entry->id);
4776 static bool
4777 tree_prepare(struct view *view)
4779         if (view->lines == 0 && opt_prefix[0]) {
4780                 char *pos = opt_prefix;
4782                 while (pos && *pos) {
4783                         char *end = strchr(pos, '/');
4785                         if (end)
4786                                 *end = 0;
4787                         push_tree_stack_entry(pos, 0);
4788                         pos = end;
4789                         if (end) {
4790                                 *end = '/';
4791                                 pos++;
4792                         }
4793                 }
4795         } else if (strcmp(view->vid, view->id)) {
4796                 opt_path[0] = 0;
4797         }
4799         return prepare_io(view, opt_cdup, view->ops->argv, TRUE);
4802 static const char *tree_argv[SIZEOF_ARG] = {
4803         "git", "ls-tree", "%(commit)", "%(directory)", NULL
4804 };
4806 static struct view_ops tree_ops = {
4807         "file",
4808         tree_argv,
4809         NULL,
4810         tree_read,
4811         tree_draw,
4812         tree_request,
4813         tree_grep,
4814         tree_select,
4815         tree_prepare,
4816 };
4818 static bool
4819 blob_read(struct view *view, char *line)
4821         if (!line)
4822                 return TRUE;
4823         return add_line_text(view, line, LINE_DEFAULT) != NULL;
4826 static enum request
4827 blob_request(struct view *view, enum request request, struct line *line)
4829         switch (request) {
4830         case REQ_EDIT:
4831                 open_blob_editor(view->vid);
4832                 return REQ_NONE;
4833         default:
4834                 return pager_request(view, request, line);
4835         }
4838 static const char *blob_argv[SIZEOF_ARG] = {
4839         "git", "cat-file", "blob", "%(blob)", NULL
4840 };
4842 static struct view_ops blob_ops = {
4843         "line",
4844         blob_argv,
4845         NULL,
4846         blob_read,
4847         pager_draw,
4848         blob_request,
4849         pager_grep,
4850         pager_select,
4851 };
4853 /*
4854  * Blame backend
4855  *
4856  * Loading the blame view is a two phase job:
4857  *
4858  *  1. File content is read either using opt_file from the
4859  *     filesystem or using git-cat-file.
4860  *  2. Then blame information is incrementally added by
4861  *     reading output from git-blame.
4862  */
4864 struct blame_commit {
4865         char id[SIZEOF_REV];            /* SHA1 ID. */
4866         char title[128];                /* First line of the commit message. */
4867         const char *author;             /* Author of the commit. */
4868         struct time time;               /* Date from the author ident. */
4869         char filename[128];             /* Name of file. */
4870         bool has_previous;              /* Was a "previous" line detected. */
4871 };
4873 struct blame {
4874         struct blame_commit *commit;
4875         unsigned long lineno;
4876         char text[1];
4877 };
4879 static bool
4880 blame_open(struct view *view)
4882         char path[SIZEOF_STR];
4884         if (!view->prev && *opt_prefix) {
4885                 string_copy(path, opt_file);
4886                 if (!string_format(opt_file, "%s%s", opt_prefix, path))
4887                         return FALSE;
4888         }
4890         if (*opt_ref || !io_open(&view->io, "%s%s", opt_cdup, opt_file)) {
4891                 const char *blame_cat_file_argv[] = {
4892                         "git", "cat-file", "blob", path, NULL
4893                 };
4895                 if (!string_format(path, "%s:%s", opt_ref, opt_file) ||
4896                     !start_update(view, blame_cat_file_argv, opt_cdup))
4897                         return FALSE;
4898         }
4900         setup_update(view, opt_file);
4901         string_format(view->ref, "%s ...", opt_file);
4903         return TRUE;
4906 static struct blame_commit *
4907 get_blame_commit(struct view *view, const char *id)
4909         size_t i;
4911         for (i = 0; i < view->lines; i++) {
4912                 struct blame *blame = view->line[i].data;
4914                 if (!blame->commit)
4915                         continue;
4917                 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
4918                         return blame->commit;
4919         }
4921         {
4922                 struct blame_commit *commit = calloc(1, sizeof(*commit));
4924                 if (commit)
4925                         string_ncopy(commit->id, id, SIZEOF_REV);
4926                 return commit;
4927         }
4930 static bool
4931 parse_number(const char **posref, size_t *number, size_t min, size_t max)
4933         const char *pos = *posref;
4935         *posref = NULL;
4936         pos = strchr(pos + 1, ' ');
4937         if (!pos || !isdigit(pos[1]))
4938                 return FALSE;
4939         *number = atoi(pos + 1);
4940         if (*number < min || *number > max)
4941                 return FALSE;
4943         *posref = pos;
4944         return TRUE;
4947 static struct blame_commit *
4948 parse_blame_commit(struct view *view, const char *text, int *blamed)
4950         struct blame_commit *commit;
4951         struct blame *blame;
4952         const char *pos = text + SIZEOF_REV - 2;
4953         size_t orig_lineno = 0;
4954         size_t lineno;
4955         size_t group;
4957         if (strlen(text) <= SIZEOF_REV || pos[1] != ' ')
4958                 return NULL;
4960         if (!parse_number(&pos, &orig_lineno, 1, 9999999) ||
4961             !parse_number(&pos, &lineno, 1, view->lines) ||
4962             !parse_number(&pos, &group, 1, view->lines - lineno + 1))
4963                 return NULL;
4965         commit = get_blame_commit(view, text);
4966         if (!commit)
4967                 return NULL;
4969         *blamed += group;
4970         while (group--) {
4971                 struct line *line = &view->line[lineno + group - 1];
4973                 blame = line->data;
4974                 blame->commit = commit;
4975                 blame->lineno = orig_lineno + group - 1;
4976                 line->dirty = 1;
4977         }
4979         return commit;
4982 static bool
4983 blame_read_file(struct view *view, const char *line, bool *read_file)
4985         if (!line) {
4986                 const char *blame_argv[] = {
4987                         "git", "blame", "--incremental",
4988                                 *opt_ref ? opt_ref : "--incremental", "--", opt_file, NULL
4989                 };
4991                 if (view->lines == 0 && !view->prev)
4992                         die("No blame exist for %s", view->vid);
4994                 if (view->lines == 0 || !start_update(view, blame_argv, opt_cdup)) {
4995                         report("Failed to load blame data");
4996                         return TRUE;
4997                 }
4999                 *read_file = FALSE;
5000                 return FALSE;
5002         } else {
5003                 size_t linelen = strlen(line);
5004                 struct blame *blame = malloc(sizeof(*blame) + linelen);
5006                 if (!blame)
5007                         return FALSE;
5009                 blame->commit = NULL;
5010                 strncpy(blame->text, line, linelen);
5011                 blame->text[linelen] = 0;
5012                 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
5013         }
5016 static bool
5017 match_blame_header(const char *name, char **line)
5019         size_t namelen = strlen(name);
5020         bool matched = !strncmp(name, *line, namelen);
5022         if (matched)
5023                 *line += namelen;
5025         return matched;
5028 static bool
5029 blame_read(struct view *view, char *line)
5031         static struct blame_commit *commit = NULL;
5032         static int blamed = 0;
5033         static bool read_file = TRUE;
5035         if (read_file)
5036                 return blame_read_file(view, line, &read_file);
5038         if (!line) {
5039                 /* Reset all! */
5040                 commit = NULL;
5041                 blamed = 0;
5042                 read_file = TRUE;
5043                 string_format(view->ref, "%s", view->vid);
5044                 if (view_is_displayed(view)) {
5045                         update_view_title(view);
5046                         redraw_view_from(view, 0);
5047                 }
5048                 return TRUE;
5049         }
5051         if (!commit) {
5052                 commit = parse_blame_commit(view, line, &blamed);
5053                 string_format(view->ref, "%s %2d%%", view->vid,
5054                               view->lines ? blamed * 100 / view->lines : 0);
5056         } else if (match_blame_header("author ", &line)) {
5057                 commit->author = get_author(line);
5059         } else if (match_blame_header("author-time ", &line)) {
5060                 parse_timesec(&commit->time, line);
5062         } else if (match_blame_header("author-tz ", &line)) {
5063                 parse_timezone(&commit->time, line);
5065         } else if (match_blame_header("summary ", &line)) {
5066                 string_ncopy(commit->title, line, strlen(line));
5068         } else if (match_blame_header("previous ", &line)) {
5069                 commit->has_previous = TRUE;
5071         } else if (match_blame_header("filename ", &line)) {
5072                 string_ncopy(commit->filename, line, strlen(line));
5073                 commit = NULL;
5074         }
5076         return TRUE;
5079 static bool
5080 blame_draw(struct view *view, struct line *line, unsigned int lineno)
5082         struct blame *blame = line->data;
5083         struct time *time = NULL;
5084         const char *id = NULL, *author = NULL;
5085         char text[SIZEOF_STR];
5087         if (blame->commit && *blame->commit->filename) {
5088                 id = blame->commit->id;
5089                 author = blame->commit->author;
5090                 time = &blame->commit->time;
5091         }
5093         if (opt_date && draw_date(view, time))
5094                 return TRUE;
5096         if (opt_author && draw_author(view, author))
5097                 return TRUE;
5099         if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
5100                 return TRUE;
5102         if (draw_lineno(view, lineno))
5103                 return TRUE;
5105         string_expand(text, sizeof(text), blame->text, opt_tab_size);
5106         draw_text(view, LINE_DEFAULT, text, TRUE);
5107         return TRUE;
5110 static bool
5111 check_blame_commit(struct blame *blame, bool check_null_id)
5113         if (!blame->commit)
5114                 report("Commit data not loaded yet");
5115         else if (check_null_id && !strcmp(blame->commit->id, NULL_ID))
5116                 report("No commit exist for the selected line");
5117         else
5118                 return TRUE;
5119         return FALSE;
5122 static void
5123 setup_blame_parent_line(struct view *view, struct blame *blame)
5125         const char *diff_tree_argv[] = {
5126                 "git", "diff-tree", "-U0", blame->commit->id,
5127                         "--", blame->commit->filename, NULL
5128         };
5129         struct io io;
5130         int parent_lineno = -1;
5131         int blamed_lineno = -1;
5132         char *line;
5134         if (!io_run(&io, IO_RD, NULL, diff_tree_argv))
5135                 return;
5137         while ((line = io_get(&io, '\n', TRUE))) {
5138                 if (*line == '@') {
5139                         char *pos = strchr(line, '+');
5141                         parent_lineno = atoi(line + 4);
5142                         if (pos)
5143                                 blamed_lineno = atoi(pos + 1);
5145                 } else if (*line == '+' && parent_lineno != -1) {
5146                         if (blame->lineno == blamed_lineno - 1 &&
5147                             !strcmp(blame->text, line + 1)) {
5148                                 view->lineno = parent_lineno ? parent_lineno - 1 : 0;
5149                                 break;
5150                         }
5151                         blamed_lineno++;
5152                 }
5153         }
5155         io_done(&io);
5158 static enum request
5159 blame_request(struct view *view, enum request request, struct line *line)
5161         enum open_flags flags = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
5162         struct blame *blame = line->data;
5164         switch (request) {
5165         case REQ_VIEW_BLAME:
5166                 if (check_blame_commit(blame, TRUE)) {
5167                         string_copy(opt_ref, blame->commit->id);
5168                         string_copy(opt_file, blame->commit->filename);
5169                         if (blame->lineno)
5170                                 view->lineno = blame->lineno;
5171                         open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
5172                 }
5173                 break;
5175         case REQ_PARENT:
5176                 if (check_blame_commit(blame, TRUE) &&
5177                     select_commit_parent(blame->commit->id, opt_ref,
5178                                          blame->commit->filename)) {
5179                         string_copy(opt_file, blame->commit->filename);
5180                         setup_blame_parent_line(view, blame);
5181                         open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
5182                 }
5183                 break;
5185         case REQ_ENTER:
5186                 if (!check_blame_commit(blame, FALSE))
5187                         break;
5189                 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
5190                     !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
5191                         break;
5193                 if (!strcmp(blame->commit->id, NULL_ID)) {
5194                         struct view *diff = VIEW(REQ_VIEW_DIFF);
5195                         const char *diff_index_argv[] = {
5196                                 "git", "diff-index", "--root", "--patch-with-stat",
5197                                         "-C", "-M", "HEAD", "--", view->vid, NULL
5198                         };
5200                         if (!blame->commit->has_previous) {
5201                                 diff_index_argv[1] = "diff";
5202                                 diff_index_argv[2] = "--no-color";
5203                                 diff_index_argv[6] = "--";
5204                                 diff_index_argv[7] = "/dev/null";
5205                         }
5207                         if (!prepare_update(diff, diff_index_argv, NULL)) {
5208                                 report("Failed to allocate diff command");
5209                                 break;
5210                         }
5211                         flags |= OPEN_PREPARED;
5212                 }
5214                 open_view(view, REQ_VIEW_DIFF, flags);
5215                 if (VIEW(REQ_VIEW_DIFF)->pipe && !strcmp(blame->commit->id, NULL_ID))
5216                         string_copy_rev(VIEW(REQ_VIEW_DIFF)->ref, NULL_ID);
5217                 break;
5219         default:
5220                 return request;
5221         }
5223         return REQ_NONE;
5226 static bool
5227 blame_grep(struct view *view, struct line *line)
5229         struct blame *blame = line->data;
5230         struct blame_commit *commit = blame->commit;
5231         const char *text[] = {
5232                 blame->text,
5233                 commit ? commit->title : "",
5234                 commit ? commit->id : "",
5235                 commit && opt_author ? commit->author : "",
5236                 commit ? mkdate(&commit->time, opt_date) : "",
5237                 NULL
5238         };
5240         return grep_text(view, text);
5243 static void
5244 blame_select(struct view *view, struct line *line)
5246         struct blame *blame = line->data;
5247         struct blame_commit *commit = blame->commit;
5249         if (!commit)
5250                 return;
5252         if (!strcmp(commit->id, NULL_ID))
5253                 string_ncopy(ref_commit, "HEAD", 4);
5254         else
5255                 string_copy_rev(ref_commit, commit->id);
5258 static struct view_ops blame_ops = {
5259         "line",
5260         NULL,
5261         blame_open,
5262         blame_read,
5263         blame_draw,
5264         blame_request,
5265         blame_grep,
5266         blame_select,
5267 };
5269 /*
5270  * Branch backend
5271  */
5273 struct branch {
5274         const char *author;             /* Author of the last commit. */
5275         struct time time;               /* Date of the last activity. */
5276         const struct ref *ref;          /* Name and commit ID information. */
5277 };
5279 static const struct ref branch_all;
5281 static const enum sort_field branch_sort_fields[] = {
5282         ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
5283 };
5284 static struct sort_state branch_sort_state = SORT_STATE(branch_sort_fields);
5286 static int
5287 branch_compare(const void *l1, const void *l2)
5289         const struct branch *branch1 = ((const struct line *) l1)->data;
5290         const struct branch *branch2 = ((const struct line *) l2)->data;
5292         switch (get_sort_field(branch_sort_state)) {
5293         case ORDERBY_DATE:
5294                 return sort_order(branch_sort_state, timecmp(&branch1->time, &branch2->time));
5296         case ORDERBY_AUTHOR:
5297                 return sort_order(branch_sort_state, strcmp(branch1->author, branch2->author));
5299         case ORDERBY_NAME:
5300         default:
5301                 return sort_order(branch_sort_state, strcmp(branch1->ref->name, branch2->ref->name));
5302         }
5305 static bool
5306 branch_draw(struct view *view, struct line *line, unsigned int lineno)
5308         struct branch *branch = line->data;
5309         enum line_type type = branch->ref->head ? LINE_MAIN_HEAD : LINE_DEFAULT;
5311         if (opt_date && draw_date(view, &branch->time))
5312                 return TRUE;
5314         if (opt_author && draw_author(view, branch->author))
5315                 return TRUE;
5317         draw_text(view, type, branch->ref == &branch_all ? "All branches" : branch->ref->name, TRUE);
5318         return TRUE;
5321 static enum request
5322 branch_request(struct view *view, enum request request, struct line *line)
5324         struct branch *branch = line->data;
5326         switch (request) {
5327         case REQ_REFRESH:
5328                 load_refs();
5329                 open_view(view, REQ_VIEW_BRANCH, OPEN_REFRESH);
5330                 return REQ_NONE;
5332         case REQ_TOGGLE_SORT_FIELD:
5333         case REQ_TOGGLE_SORT_ORDER:
5334                 sort_view(view, request, &branch_sort_state, branch_compare);
5335                 return REQ_NONE;
5337         case REQ_ENTER:
5338                 if (branch->ref == &branch_all) {
5339                         const char *all_branches_argv[] = {
5340                                 "git", "log", "--no-color", "--pretty=raw", "--parents",
5341                                       "--topo-order", "--all", NULL
5342                         };
5343                         struct view *main_view = VIEW(REQ_VIEW_MAIN);
5345                         if (!prepare_update(main_view, all_branches_argv, NULL)) {
5346                                 report("Failed to load view of all branches");
5347                                 return REQ_NONE;
5348                         }
5349                         open_view(view, REQ_VIEW_MAIN, OPEN_PREPARED | OPEN_SPLIT);
5350                 } else {
5351                         open_view(view, REQ_VIEW_MAIN, OPEN_SPLIT);
5352                 }
5353                 return REQ_NONE;
5355         default:
5356                 return request;
5357         }
5360 static bool
5361 branch_read(struct view *view, char *line)
5363         static char id[SIZEOF_REV];
5364         struct branch *reference;
5365         size_t i;
5367         if (!line)
5368                 return TRUE;
5370         switch (get_line_type(line)) {
5371         case LINE_COMMIT:
5372                 string_copy_rev(id, line + STRING_SIZE("commit "));
5373                 return TRUE;
5375         case LINE_AUTHOR:
5376                 for (i = 0, reference = NULL; i < view->lines; i++) {
5377                         struct branch *branch = view->line[i].data;
5379                         if (strcmp(branch->ref->id, id))
5380                                 continue;
5382                         view->line[i].dirty = TRUE;
5383                         if (reference) {
5384                                 branch->author = reference->author;
5385                                 branch->time = reference->time;
5386                                 continue;
5387                         }
5389                         parse_author_line(line + STRING_SIZE("author "),
5390                                           &branch->author, &branch->time);
5391                         reference = branch;
5392                 }
5393                 return TRUE;
5395         default:
5396                 return TRUE;
5397         }
5401 static bool
5402 branch_open_visitor(void *data, const struct ref *ref)
5404         struct view *view = data;
5405         struct branch *branch;
5407         if (ref->tag || ref->ltag || ref->remote)
5408                 return TRUE;
5410         branch = calloc(1, sizeof(*branch));
5411         if (!branch)
5412                 return FALSE;
5414         branch->ref = ref;
5415         return !!add_line_data(view, branch, LINE_DEFAULT);
5418 static bool
5419 branch_open(struct view *view)
5421         const char *branch_log[] = {
5422                 "git", "log", "--no-color", "--pretty=raw",
5423                         "--simplify-by-decoration", "--all", NULL
5424         };
5426         if (!start_update(view, branch_log, NULL)) {
5427                 report("Failed to load branch data");
5428                 return TRUE;
5429         }
5431         setup_update(view, view->id);
5432         branch_open_visitor(view, &branch_all);
5433         foreach_ref(branch_open_visitor, view);
5434         view->p_restore = TRUE;
5436         return TRUE;
5439 static bool
5440 branch_grep(struct view *view, struct line *line)
5442         struct branch *branch = line->data;
5443         const char *text[] = {
5444                 branch->ref->name,
5445                 branch->author,
5446                 NULL
5447         };
5449         return grep_text(view, text);
5452 static void
5453 branch_select(struct view *view, struct line *line)
5455         struct branch *branch = line->data;
5457         string_copy_rev(view->ref, branch->ref->id);
5458         string_copy_rev(ref_commit, branch->ref->id);
5459         string_copy_rev(ref_head, branch->ref->id);
5460         string_copy_rev(ref_branch, branch->ref->name);
5463 static struct view_ops branch_ops = {
5464         "branch",
5465         NULL,
5466         branch_open,
5467         branch_read,
5468         branch_draw,
5469         branch_request,
5470         branch_grep,
5471         branch_select,
5472 };
5474 /*
5475  * Status backend
5476  */
5478 struct status {
5479         char status;
5480         struct {
5481                 mode_t mode;
5482                 char rev[SIZEOF_REV];
5483                 char name[SIZEOF_STR];
5484         } old;
5485         struct {
5486                 mode_t mode;
5487                 char rev[SIZEOF_REV];
5488                 char name[SIZEOF_STR];
5489         } new;
5490 };
5492 static char status_onbranch[SIZEOF_STR];
5493 static struct status stage_status;
5494 static enum line_type stage_line_type;
5495 static size_t stage_chunks;
5496 static int *stage_chunk;
5498 DEFINE_ALLOCATOR(realloc_ints, int, 32)
5500 /* This should work even for the "On branch" line. */
5501 static inline bool
5502 status_has_none(struct view *view, struct line *line)
5504         return line < view->line + view->lines && !line[1].data;
5507 /* Get fields from the diff line:
5508  * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
5509  */
5510 static inline bool
5511 status_get_diff(struct status *file, const char *buf, size_t bufsize)
5513         const char *old_mode = buf +  1;
5514         const char *new_mode = buf +  8;
5515         const char *old_rev  = buf + 15;
5516         const char *new_rev  = buf + 56;
5517         const char *status   = buf + 97;
5519         if (bufsize < 98 ||
5520             old_mode[-1] != ':' ||
5521             new_mode[-1] != ' ' ||
5522             old_rev[-1]  != ' ' ||
5523             new_rev[-1]  != ' ' ||
5524             status[-1]   != ' ')
5525                 return FALSE;
5527         file->status = *status;
5529         string_copy_rev(file->old.rev, old_rev);
5530         string_copy_rev(file->new.rev, new_rev);
5532         file->old.mode = strtoul(old_mode, NULL, 8);
5533         file->new.mode = strtoul(new_mode, NULL, 8);
5535         file->old.name[0] = file->new.name[0] = 0;
5537         return TRUE;
5540 static bool
5541 status_run(struct view *view, const char *argv[], char status, enum line_type type)
5543         struct status *unmerged = NULL;
5544         char *buf;
5545         struct io io;
5547         if (!io_run(&io, IO_RD, opt_cdup, argv))
5548                 return FALSE;
5550         add_line_data(view, NULL, type);
5552         while ((buf = io_get(&io, 0, TRUE))) {
5553                 struct status *file = unmerged;
5555                 if (!file) {
5556                         file = calloc(1, sizeof(*file));
5557                         if (!file || !add_line_data(view, file, type))
5558                                 goto error_out;
5559                 }
5561                 /* Parse diff info part. */
5562                 if (status) {
5563                         file->status = status;
5564                         if (status == 'A')
5565                                 string_copy(file->old.rev, NULL_ID);
5567                 } else if (!file->status || file == unmerged) {
5568                         if (!status_get_diff(file, buf, strlen(buf)))
5569                                 goto error_out;
5571                         buf = io_get(&io, 0, TRUE);
5572                         if (!buf)
5573                                 break;
5575                         /* Collapse all modified entries that follow an
5576                          * associated unmerged entry. */
5577                         if (unmerged == file) {
5578                                 unmerged->status = 'U';
5579                                 unmerged = NULL;
5580                         } else if (file->status == 'U') {
5581                                 unmerged = file;
5582                         }
5583                 }
5585                 /* Grab the old name for rename/copy. */
5586                 if (!*file->old.name &&
5587                     (file->status == 'R' || file->status == 'C')) {
5588                         string_ncopy(file->old.name, buf, strlen(buf));
5590                         buf = io_get(&io, 0, TRUE);
5591                         if (!buf)
5592                                 break;
5593                 }
5595                 /* git-ls-files just delivers a NUL separated list of
5596                  * file names similar to the second half of the
5597                  * git-diff-* output. */
5598                 string_ncopy(file->new.name, buf, strlen(buf));
5599                 if (!*file->old.name)
5600                         string_copy(file->old.name, file->new.name);
5601                 file = NULL;
5602         }
5604         if (io_error(&io)) {
5605 error_out:
5606                 io_done(&io);
5607                 return FALSE;
5608         }
5610         if (!view->line[view->lines - 1].data)
5611                 add_line_data(view, NULL, LINE_STAT_NONE);
5613         io_done(&io);
5614         return TRUE;
5617 /* Don't show unmerged entries in the staged section. */
5618 static const char *status_diff_index_argv[] = {
5619         "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
5620                              "--cached", "-M", "HEAD", NULL
5621 };
5623 static const char *status_diff_files_argv[] = {
5624         "git", "diff-files", "-z", NULL
5625 };
5627 static const char *status_list_other_argv[] = {
5628         "git", "ls-files", "-z", "--others", "--exclude-standard", opt_prefix, NULL
5629 };
5631 static const char *status_list_no_head_argv[] = {
5632         "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
5633 };
5635 static const char *update_index_argv[] = {
5636         "git", "update-index", "-q", "--unmerged", "--refresh", NULL
5637 };
5639 /* Restore the previous line number to stay in the context or select a
5640  * line with something that can be updated. */
5641 static void
5642 status_restore(struct view *view)
5644         if (view->p_lineno >= view->lines)
5645                 view->p_lineno = view->lines - 1;
5646         while (view->p_lineno < view->lines && !view->line[view->p_lineno].data)
5647                 view->p_lineno++;
5648         while (view->p_lineno > 0 && !view->line[view->p_lineno].data)
5649                 view->p_lineno--;
5651         /* If the above fails, always skip the "On branch" line. */
5652         if (view->p_lineno < view->lines)
5653                 view->lineno = view->p_lineno;
5654         else
5655                 view->lineno = 1;
5657         if (view->lineno < view->offset)
5658                 view->offset = view->lineno;
5659         else if (view->offset + view->height <= view->lineno)
5660                 view->offset = view->lineno - view->height + 1;
5662         view->p_restore = FALSE;
5665 static void
5666 status_update_onbranch(void)
5668         static const char *paths[][2] = {
5669                 { "rebase-apply/rebasing",      "Rebasing" },
5670                 { "rebase-apply/applying",      "Applying mailbox" },
5671                 { "rebase-apply/",              "Rebasing mailbox" },
5672                 { "rebase-merge/interactive",   "Interactive rebase" },
5673                 { "rebase-merge/",              "Rebase merge" },
5674                 { "MERGE_HEAD",                 "Merging" },
5675                 { "BISECT_LOG",                 "Bisecting" },
5676                 { "HEAD",                       "On branch" },
5677         };
5678         char buf[SIZEOF_STR];
5679         struct stat stat;
5680         int i;
5682         if (is_initial_commit()) {
5683                 string_copy(status_onbranch, "Initial commit");
5684                 return;
5685         }
5687         for (i = 0; i < ARRAY_SIZE(paths); i++) {
5688                 char *head = opt_head;
5690                 if (!string_format(buf, "%s/%s", opt_git_dir, paths[i][0]) ||
5691                     lstat(buf, &stat) < 0)
5692                         continue;
5694                 if (!*opt_head) {
5695                         struct io io;
5697                         if (io_open(&io, "%s/rebase-merge/head-name", opt_git_dir) &&
5698                             io_read_buf(&io, buf, sizeof(buf))) {
5699                                 head = buf;
5700                                 if (!prefixcmp(head, "refs/heads/"))
5701                                         head += STRING_SIZE("refs/heads/");
5702                         }
5703                 }
5705                 if (!string_format(status_onbranch, "%s %s", paths[i][1], head))
5706                         string_copy(status_onbranch, opt_head);
5707                 return;
5708         }
5710         string_copy(status_onbranch, "Not currently on any branch");
5713 /* First parse staged info using git-diff-index(1), then parse unstaged
5714  * info using git-diff-files(1), and finally untracked files using
5715  * git-ls-files(1). */
5716 static bool
5717 status_open(struct view *view)
5719         reset_view(view);
5721         add_line_data(view, NULL, LINE_STAT_HEAD);
5722         status_update_onbranch();
5724         io_run_bg(update_index_argv);
5726         if (is_initial_commit()) {
5727                 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
5728                         return FALSE;
5729         } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
5730                 return FALSE;
5731         }
5733         if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
5734             !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
5735                 return FALSE;
5737         /* Restore the exact position or use the specialized restore
5738          * mode? */
5739         if (!view->p_restore)
5740                 status_restore(view);
5741         return TRUE;
5744 static bool
5745 status_draw(struct view *view, struct line *line, unsigned int lineno)
5747         struct status *status = line->data;
5748         enum line_type type;
5749         const char *text;
5751         if (!status) {
5752                 switch (line->type) {
5753                 case LINE_STAT_STAGED:
5754                         type = LINE_STAT_SECTION;
5755                         text = "Changes to be committed:";
5756                         break;
5758                 case LINE_STAT_UNSTAGED:
5759                         type = LINE_STAT_SECTION;
5760                         text = "Changed but not updated:";
5761                         break;
5763                 case LINE_STAT_UNTRACKED:
5764                         type = LINE_STAT_SECTION;
5765                         text = "Untracked files:";
5766                         break;
5768                 case LINE_STAT_NONE:
5769                         type = LINE_DEFAULT;
5770                         text = "  (no files)";
5771                         break;
5773                 case LINE_STAT_HEAD:
5774                         type = LINE_STAT_HEAD;
5775                         text = status_onbranch;
5776                         break;
5778                 default:
5779                         return FALSE;
5780                 }
5781         } else {
5782                 static char buf[] = { '?', ' ', ' ', ' ', 0 };
5784                 buf[0] = status->status;
5785                 if (draw_text(view, line->type, buf, TRUE))
5786                         return TRUE;
5787                 type = LINE_DEFAULT;
5788                 text = status->new.name;
5789         }
5791         draw_text(view, type, text, TRUE);
5792         return TRUE;
5795 static enum request
5796 status_load_error(struct view *view, struct view *stage, const char *path)
5798         if (displayed_views() == 2 || display[current_view] != view)
5799                 maximize_view(view);
5800         report("Failed to load '%s': %s", path, io_strerror(&stage->io));
5801         return REQ_NONE;
5804 static enum request
5805 status_enter(struct view *view, struct line *line)
5807         struct status *status = line->data;
5808         const char *oldpath = status ? status->old.name : NULL;
5809         /* Diffs for unmerged entries are empty when passing the new
5810          * path, so leave it empty. */
5811         const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
5812         const char *info;
5813         enum open_flags split;
5814         struct view *stage = VIEW(REQ_VIEW_STAGE);
5816         if (line->type == LINE_STAT_NONE ||
5817             (!status && line[1].type == LINE_STAT_NONE)) {
5818                 report("No file to diff");
5819                 return REQ_NONE;
5820         }
5822         switch (line->type) {
5823         case LINE_STAT_STAGED:
5824                 if (is_initial_commit()) {
5825                         const char *no_head_diff_argv[] = {
5826                                 "git", "diff", "--no-color", "--patch-with-stat",
5827                                         "--", "/dev/null", newpath, NULL
5828                         };
5830                         if (!prepare_update(stage, no_head_diff_argv, opt_cdup))
5831                                 return status_load_error(view, stage, newpath);
5832                 } else {
5833                         const char *index_show_argv[] = {
5834                                 "git", "diff-index", "--root", "--patch-with-stat",
5835                                         "-C", "-M", "--cached", "HEAD", "--",
5836                                         oldpath, newpath, NULL
5837                         };
5839                         if (!prepare_update(stage, index_show_argv, opt_cdup))
5840                                 return status_load_error(view, stage, newpath);
5841                 }
5843                 if (status)
5844                         info = "Staged changes to %s";
5845                 else
5846                         info = "Staged changes";
5847                 break;
5849         case LINE_STAT_UNSTAGED:
5850         {
5851                 const char *files_show_argv[] = {
5852                         "git", "diff-files", "--root", "--patch-with-stat",
5853                                 "-C", "-M", "--", oldpath, newpath, NULL
5854                 };
5856                 if (!prepare_update(stage, files_show_argv, opt_cdup))
5857                         return status_load_error(view, stage, newpath);
5858                 if (status)
5859                         info = "Unstaged changes to %s";
5860                 else
5861                         info = "Unstaged changes";
5862                 break;
5863         }
5864         case LINE_STAT_UNTRACKED:
5865                 if (!newpath) {
5866                         report("No file to show");
5867                         return REQ_NONE;
5868                 }
5870                 if (!suffixcmp(status->new.name, -1, "/")) {
5871                         report("Cannot display a directory");
5872                         return REQ_NONE;
5873                 }
5875                 if (!prepare_update_file(stage, newpath))
5876                         return status_load_error(view, stage, newpath);
5877                 info = "Untracked file %s";
5878                 break;
5880         case LINE_STAT_HEAD:
5881                 return REQ_NONE;
5883         default:
5884                 die("line type %d not handled in switch", line->type);
5885         }
5887         split = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
5888         open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split);
5889         if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
5890                 if (status) {
5891                         stage_status = *status;
5892                 } else {
5893                         memset(&stage_status, 0, sizeof(stage_status));
5894                 }
5896                 stage_line_type = line->type;
5897                 stage_chunks = 0;
5898                 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
5899         }
5901         return REQ_NONE;
5904 static bool
5905 status_exists(struct status *status, enum line_type type)
5907         struct view *view = VIEW(REQ_VIEW_STATUS);
5908         unsigned long lineno;
5910         for (lineno = 0; lineno < view->lines; lineno++) {
5911                 struct line *line = &view->line[lineno];
5912                 struct status *pos = line->data;
5914                 if (line->type != type)
5915                         continue;
5916                 if (!pos && (!status || !status->status) && line[1].data) {
5917                         select_view_line(view, lineno);
5918                         return TRUE;
5919                 }
5920                 if (pos && !strcmp(status->new.name, pos->new.name)) {
5921                         select_view_line(view, lineno);
5922                         return TRUE;
5923                 }
5924         }
5926         return FALSE;
5930 static bool
5931 status_update_prepare(struct io *io, enum line_type type)
5933         const char *staged_argv[] = {
5934                 "git", "update-index", "-z", "--index-info", NULL
5935         };
5936         const char *others_argv[] = {
5937                 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
5938         };
5940         switch (type) {
5941         case LINE_STAT_STAGED:
5942                 return io_run(io, IO_WR, opt_cdup, staged_argv);
5944         case LINE_STAT_UNSTAGED:
5945         case LINE_STAT_UNTRACKED:
5946                 return io_run(io, IO_WR, opt_cdup, others_argv);
5948         default:
5949                 die("line type %d not handled in switch", type);
5950                 return FALSE;
5951         }
5954 static bool
5955 status_update_write(struct io *io, struct status *status, enum line_type type)
5957         char buf[SIZEOF_STR];
5958         size_t bufsize = 0;
5960         switch (type) {
5961         case LINE_STAT_STAGED:
5962                 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
5963                                         status->old.mode,
5964                                         status->old.rev,
5965                                         status->old.name, 0))
5966                         return FALSE;
5967                 break;
5969         case LINE_STAT_UNSTAGED:
5970         case LINE_STAT_UNTRACKED:
5971                 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
5972                         return FALSE;
5973                 break;
5975         default:
5976                 die("line type %d not handled in switch", type);
5977         }
5979         return io_write(io, buf, bufsize);
5982 static bool
5983 status_update_file(struct status *status, enum line_type type)
5985         struct io io;
5986         bool result;
5988         if (!status_update_prepare(&io, type))
5989                 return FALSE;
5991         result = status_update_write(&io, status, type);
5992         return io_done(&io) && result;
5995 static bool
5996 status_update_files(struct view *view, struct line *line)
5998         char buf[sizeof(view->ref)];
5999         struct io io;
6000         bool result = TRUE;
6001         struct line *pos = view->line + view->lines;
6002         int files = 0;
6003         int file, done;
6004         int cursor_y = -1, cursor_x = -1;
6006         if (!status_update_prepare(&io, line->type))
6007                 return FALSE;
6009         for (pos = line; pos < view->line + view->lines && pos->data; pos++)
6010                 files++;
6012         string_copy(buf, view->ref);
6013         getsyx(cursor_y, cursor_x);
6014         for (file = 0, done = 5; result && file < files; line++, file++) {
6015                 int almost_done = file * 100 / files;
6017                 if (almost_done > done) {
6018                         done = almost_done;
6019                         string_format(view->ref, "updating file %u of %u (%d%% done)",
6020                                       file, files, done);
6021                         update_view_title(view);
6022                         setsyx(cursor_y, cursor_x);
6023                         doupdate();
6024                 }
6025                 result = status_update_write(&io, line->data, line->type);
6026         }
6027         string_copy(view->ref, buf);
6029         return io_done(&io) && result;
6032 static bool
6033 status_update(struct view *view)
6035         struct line *line = &view->line[view->lineno];
6037         assert(view->lines);
6039         if (!line->data) {
6040                 /* This should work even for the "On branch" line. */
6041                 if (line < view->line + view->lines && !line[1].data) {
6042                         report("Nothing to update");
6043                         return FALSE;
6044                 }
6046                 if (!status_update_files(view, line + 1)) {
6047                         report("Failed to update file status");
6048                         return FALSE;
6049                 }
6051         } else if (!status_update_file(line->data, line->type)) {
6052                 report("Failed to update file status");
6053                 return FALSE;
6054         }
6056         return TRUE;
6059 static bool
6060 status_revert(struct status *status, enum line_type type, bool has_none)
6062         if (!status || type != LINE_STAT_UNSTAGED) {
6063                 if (type == LINE_STAT_STAGED) {
6064                         report("Cannot revert changes to staged files");
6065                 } else if (type == LINE_STAT_UNTRACKED) {
6066                         report("Cannot revert changes to untracked files");
6067                 } else if (has_none) {
6068                         report("Nothing to revert");
6069                 } else {
6070                         report("Cannot revert changes to multiple files");
6071                 }
6073         } else if (prompt_yesno("Are you sure you want to revert changes?")) {
6074                 char mode[10] = "100644";
6075                 const char *reset_argv[] = {
6076                         "git", "update-index", "--cacheinfo", mode,
6077                                 status->old.rev, status->old.name, NULL
6078                 };
6079                 const char *checkout_argv[] = {
6080                         "git", "checkout", "--", status->old.name, NULL
6081                 };
6083                 if (status->status == 'U') {
6084                         string_format(mode, "%5o", status->old.mode);
6086                         if (status->old.mode == 0 && status->new.mode == 0) {
6087                                 reset_argv[2] = "--force-remove";
6088                                 reset_argv[3] = status->old.name;
6089                                 reset_argv[4] = NULL;
6090                         }
6092                         if (!io_run_fg(reset_argv, opt_cdup))
6093                                 return FALSE;
6094                         if (status->old.mode == 0 && status->new.mode == 0)
6095                                 return TRUE;
6096                 }
6098                 return io_run_fg(checkout_argv, opt_cdup);
6099         }
6101         return FALSE;
6104 static enum request
6105 status_request(struct view *view, enum request request, struct line *line)
6107         struct status *status = line->data;
6109         switch (request) {
6110         case REQ_STATUS_UPDATE:
6111                 if (!status_update(view))
6112                         return REQ_NONE;
6113                 break;
6115         case REQ_STATUS_REVERT:
6116                 if (!status_revert(status, line->type, status_has_none(view, line)))
6117                         return REQ_NONE;
6118                 break;
6120         case REQ_STATUS_MERGE:
6121                 if (!status || status->status != 'U') {
6122                         report("Merging only possible for files with unmerged status ('U').");
6123                         return REQ_NONE;
6124                 }
6125                 open_mergetool(status->new.name);
6126                 break;
6128         case REQ_EDIT:
6129                 if (!status)
6130                         return request;
6131                 if (status->status == 'D') {
6132                         report("File has been deleted.");
6133                         return REQ_NONE;
6134                 }
6136                 open_editor(status->new.name);
6137                 break;
6139         case REQ_VIEW_BLAME:
6140                 if (status)
6141                         opt_ref[0] = 0;
6142                 return request;
6144         case REQ_ENTER:
6145                 /* After returning the status view has been split to
6146                  * show the stage view. No further reloading is
6147                  * necessary. */
6148                 return status_enter(view, line);
6150         case REQ_REFRESH:
6151                 /* Simply reload the view. */
6152                 break;
6154         default:
6155                 return request;
6156         }
6158         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
6160         return REQ_NONE;
6163 static void
6164 status_select(struct view *view, struct line *line)
6166         struct status *status = line->data;
6167         char file[SIZEOF_STR] = "all files";
6168         const char *text;
6169         const char *key;
6171         if (status && !string_format(file, "'%s'", status->new.name))
6172                 return;
6174         if (!status && line[1].type == LINE_STAT_NONE)
6175                 line++;
6177         switch (line->type) {
6178         case LINE_STAT_STAGED:
6179                 text = "Press %s to unstage %s for commit";
6180                 break;
6182         case LINE_STAT_UNSTAGED:
6183                 text = "Press %s to stage %s for commit";
6184                 break;
6186         case LINE_STAT_UNTRACKED:
6187                 text = "Press %s to stage %s for addition";
6188                 break;
6190         case LINE_STAT_HEAD:
6191         case LINE_STAT_NONE:
6192                 text = "Nothing to update";
6193                 break;
6195         default:
6196                 die("line type %d not handled in switch", line->type);
6197         }
6199         if (status && status->status == 'U') {
6200                 text = "Press %s to resolve conflict in %s";
6201                 key = get_key(KEYMAP_STATUS, REQ_STATUS_MERGE);
6203         } else {
6204                 key = get_key(KEYMAP_STATUS, REQ_STATUS_UPDATE);
6205         }
6207         string_format(view->ref, text, key, file);
6208         if (status)
6209                 string_copy(opt_file, status->new.name);
6212 static bool
6213 status_grep(struct view *view, struct line *line)
6215         struct status *status = line->data;
6217         if (status) {
6218                 const char buf[2] = { status->status, 0 };
6219                 const char *text[] = { status->new.name, buf, NULL };
6221                 return grep_text(view, text);
6222         }
6224         return FALSE;
6227 static struct view_ops status_ops = {
6228         "file",
6229         NULL,
6230         status_open,
6231         NULL,
6232         status_draw,
6233         status_request,
6234         status_grep,
6235         status_select,
6236 };
6239 static bool
6240 stage_diff_write(struct io *io, struct line *line, struct line *end)
6242         while (line < end) {
6243                 if (!io_write(io, line->data, strlen(line->data)) ||
6244                     !io_write(io, "\n", 1))
6245                         return FALSE;
6246                 line++;
6247                 if (line->type == LINE_DIFF_CHUNK ||
6248                     line->type == LINE_DIFF_HEADER)
6249                         break;
6250         }
6252         return TRUE;
6255 static struct line *
6256 stage_diff_find(struct view *view, struct line *line, enum line_type type)
6258         for (; view->line < line; line--)
6259                 if (line->type == type)
6260                         return line;
6262         return NULL;
6265 static bool
6266 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
6268         const char *apply_argv[SIZEOF_ARG] = {
6269                 "git", "apply", "--whitespace=nowarn", NULL
6270         };
6271         struct line *diff_hdr;
6272         struct io io;
6273         int argc = 3;
6275         diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
6276         if (!diff_hdr)
6277                 return FALSE;
6279         if (!revert)
6280                 apply_argv[argc++] = "--cached";
6281         if (revert || stage_line_type == LINE_STAT_STAGED)
6282                 apply_argv[argc++] = "-R";
6283         apply_argv[argc++] = "-";
6284         apply_argv[argc++] = NULL;
6285         if (!io_run(&io, IO_WR, opt_cdup, apply_argv))
6286                 return FALSE;
6288         if (!stage_diff_write(&io, diff_hdr, chunk) ||
6289             !stage_diff_write(&io, chunk, view->line + view->lines))
6290                 chunk = NULL;
6292         io_done(&io);
6293         io_run_bg(update_index_argv);
6295         return chunk ? TRUE : FALSE;
6298 static bool
6299 stage_update(struct view *view, struct line *line)
6301         struct line *chunk = NULL;
6303         if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
6304                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6306         if (chunk) {
6307                 if (!stage_apply_chunk(view, chunk, FALSE)) {
6308                         report("Failed to apply chunk");
6309                         return FALSE;
6310                 }
6312         } else if (!stage_status.status) {
6313                 view = VIEW(REQ_VIEW_STATUS);
6315                 for (line = view->line; line < view->line + view->lines; line++)
6316                         if (line->type == stage_line_type)
6317                                 break;
6319                 if (!status_update_files(view, line + 1)) {
6320                         report("Failed to update files");
6321                         return FALSE;
6322                 }
6324         } else if (!status_update_file(&stage_status, stage_line_type)) {
6325                 report("Failed to update file");
6326                 return FALSE;
6327         }
6329         return TRUE;
6332 static bool
6333 stage_revert(struct view *view, struct line *line)
6335         struct line *chunk = NULL;
6337         if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
6338                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6340         if (chunk) {
6341                 if (!prompt_yesno("Are you sure you want to revert changes?"))
6342                         return FALSE;
6344                 if (!stage_apply_chunk(view, chunk, TRUE)) {
6345                         report("Failed to revert chunk");
6346                         return FALSE;
6347                 }
6348                 return TRUE;
6350         } else {
6351                 return status_revert(stage_status.status ? &stage_status : NULL,
6352                                      stage_line_type, FALSE);
6353         }
6357 static void
6358 stage_next(struct view *view, struct line *line)
6360         int i;
6362         if (!stage_chunks) {
6363                 for (line = view->line; line < view->line + view->lines; line++) {
6364                         if (line->type != LINE_DIFF_CHUNK)
6365                                 continue;
6367                         if (!realloc_ints(&stage_chunk, stage_chunks, 1)) {
6368                                 report("Allocation failure");
6369                                 return;
6370                         }
6372                         stage_chunk[stage_chunks++] = line - view->line;
6373                 }
6374         }
6376         for (i = 0; i < stage_chunks; i++) {
6377                 if (stage_chunk[i] > view->lineno) {
6378                         do_scroll_view(view, stage_chunk[i] - view->lineno);
6379                         report("Chunk %d of %d", i + 1, stage_chunks);
6380                         return;
6381                 }
6382         }
6384         report("No next chunk found");
6387 static enum request
6388 stage_request(struct view *view, enum request request, struct line *line)
6390         switch (request) {
6391         case REQ_STATUS_UPDATE:
6392                 if (!stage_update(view, line))
6393                         return REQ_NONE;
6394                 break;
6396         case REQ_STATUS_REVERT:
6397                 if (!stage_revert(view, line))
6398                         return REQ_NONE;
6399                 break;
6401         case REQ_STAGE_NEXT:
6402                 if (stage_line_type == LINE_STAT_UNTRACKED) {
6403                         report("File is untracked; press %s to add",
6404                                get_key(KEYMAP_STAGE, REQ_STATUS_UPDATE));
6405                         return REQ_NONE;
6406                 }
6407                 stage_next(view, line);
6408                 return REQ_NONE;
6410         case REQ_EDIT:
6411                 if (!stage_status.new.name[0])
6412                         return request;
6413                 if (stage_status.status == 'D') {
6414                         report("File has been deleted.");
6415                         return REQ_NONE;
6416                 }
6418                 open_editor(stage_status.new.name);
6419                 break;
6421         case REQ_REFRESH:
6422                 /* Reload everything ... */
6423                 break;
6425         case REQ_VIEW_BLAME:
6426                 if (stage_status.new.name[0]) {
6427                         string_copy(opt_file, stage_status.new.name);
6428                         opt_ref[0] = 0;
6429                 }
6430                 return request;
6432         case REQ_ENTER:
6433                 return pager_request(view, request, line);
6435         default:
6436                 return request;
6437         }
6439         VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
6440         open_view(view, REQ_VIEW_STATUS, OPEN_REFRESH);
6442         /* Check whether the staged entry still exists, and close the
6443          * stage view if it doesn't. */
6444         if (!status_exists(&stage_status, stage_line_type)) {
6445                 status_restore(VIEW(REQ_VIEW_STATUS));
6446                 return REQ_VIEW_CLOSE;
6447         }
6449         if (stage_line_type == LINE_STAT_UNTRACKED) {
6450                 if (!suffixcmp(stage_status.new.name, -1, "/")) {
6451                         report("Cannot display a directory");
6452                         return REQ_NONE;
6453                 }
6455                 if (!prepare_update_file(view, stage_status.new.name)) {
6456                         report("Failed to open file: %s", strerror(errno));
6457                         return REQ_NONE;
6458                 }
6459         }
6460         open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
6462         return REQ_NONE;
6465 static struct view_ops stage_ops = {
6466         "line",
6467         NULL,
6468         NULL,
6469         pager_read,
6470         pager_draw,
6471         stage_request,
6472         pager_grep,
6473         pager_select,
6474 };
6477 /*
6478  * Revision graph
6479  */
6481 struct commit {
6482         char id[SIZEOF_REV];            /* SHA1 ID. */
6483         char title[128];                /* First line of the commit message. */
6484         const char *author;             /* Author of the commit. */
6485         struct time time;               /* Date from the author ident. */
6486         struct ref_list *refs;          /* Repository references. */
6487         chtype graph[SIZEOF_REVGRAPH];  /* Ancestry chain graphics. */
6488         size_t graph_size;              /* The width of the graph array. */
6489         bool has_parents;               /* Rewritten --parents seen. */
6490 };
6492 /* Size of rev graph with no  "padding" columns */
6493 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
6495 struct rev_graph {
6496         struct rev_graph *prev, *next, *parents;
6497         char rev[SIZEOF_REVITEMS][SIZEOF_REV];
6498         size_t size;
6499         struct commit *commit;
6500         size_t pos;
6501         unsigned int boundary:1;
6502 };
6504 /* Parents of the commit being visualized. */
6505 static struct rev_graph graph_parents[4];
6507 /* The current stack of revisions on the graph. */
6508 static struct rev_graph graph_stacks[4] = {
6509         { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
6510         { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
6511         { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
6512         { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
6513 };
6515 static inline bool
6516 graph_parent_is_merge(struct rev_graph *graph)
6518         return graph->parents->size > 1;
6521 static inline void
6522 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
6524         struct commit *commit = graph->commit;
6526         if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
6527                 commit->graph[commit->graph_size++] = symbol;
6530 static void
6531 clear_rev_graph(struct rev_graph *graph)
6533         graph->boundary = 0;
6534         graph->size = graph->pos = 0;
6535         graph->commit = NULL;
6536         memset(graph->parents, 0, sizeof(*graph->parents));
6539 static void
6540 done_rev_graph(struct rev_graph *graph)
6542         if (graph_parent_is_merge(graph) &&
6543             graph->pos < graph->size - 1 &&
6544             graph->next->size == graph->size + graph->parents->size - 1) {
6545                 size_t i = graph->pos + graph->parents->size - 1;
6547                 graph->commit->graph_size = i * 2;
6548                 while (i < graph->next->size - 1) {
6549                         append_to_rev_graph(graph, ' ');
6550                         append_to_rev_graph(graph, '\\');
6551                         i++;
6552                 }
6553         }
6555         clear_rev_graph(graph);
6558 static void
6559 push_rev_graph(struct rev_graph *graph, const char *parent)
6561         int i;
6563         /* "Collapse" duplicate parents lines.
6564          *
6565          * FIXME: This needs to also update update the drawn graph but
6566          * for now it just serves as a method for pruning graph lines. */
6567         for (i = 0; i < graph->size; i++)
6568                 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
6569                         return;
6571         if (graph->size < SIZEOF_REVITEMS) {
6572                 string_copy_rev(graph->rev[graph->size++], parent);
6573         }
6576 static chtype
6577 get_rev_graph_symbol(struct rev_graph *graph)
6579         chtype symbol;
6581         if (graph->boundary)
6582                 symbol = REVGRAPH_BOUND;
6583         else if (graph->parents->size == 0)
6584                 symbol = REVGRAPH_INIT;
6585         else if (graph_parent_is_merge(graph))
6586                 symbol = REVGRAPH_MERGE;
6587         else if (graph->pos >= graph->size)
6588                 symbol = REVGRAPH_BRANCH;
6589         else
6590                 symbol = REVGRAPH_COMMIT;
6592         return symbol;
6595 static void
6596 draw_rev_graph(struct rev_graph *graph)
6598         struct rev_filler {
6599                 chtype separator, line;
6600         };
6601         enum { DEFAULT, RSHARP, RDIAG, LDIAG };
6602         static struct rev_filler fillers[] = {
6603                 { ' ',  '|' },
6604                 { '`',  '.' },
6605                 { '\'', ' ' },
6606                 { '/',  ' ' },
6607         };
6608         chtype symbol = get_rev_graph_symbol(graph);
6609         struct rev_filler *filler;
6610         size_t i;
6612         fillers[DEFAULT].line = opt_line_graphics ? ACS_VLINE : '|';
6613         filler = &fillers[DEFAULT];
6615         for (i = 0; i < graph->pos; i++) {
6616                 append_to_rev_graph(graph, filler->line);
6617                 if (graph_parent_is_merge(graph->prev) &&
6618                     graph->prev->pos == i)
6619                         filler = &fillers[RSHARP];
6621                 append_to_rev_graph(graph, filler->separator);
6622         }
6624         /* Place the symbol for this revision. */
6625         append_to_rev_graph(graph, symbol);
6627         if (graph->prev->size > graph->size)
6628                 filler = &fillers[RDIAG];
6629         else
6630                 filler = &fillers[DEFAULT];
6632         i++;
6634         for (; i < graph->size; i++) {
6635                 append_to_rev_graph(graph, filler->separator);
6636                 append_to_rev_graph(graph, filler->line);
6637                 if (graph_parent_is_merge(graph->prev) &&
6638                     i < graph->prev->pos + graph->parents->size)
6639                         filler = &fillers[RSHARP];
6640                 if (graph->prev->size > graph->size)
6641                         filler = &fillers[LDIAG];
6642         }
6644         if (graph->prev->size > graph->size) {
6645                 append_to_rev_graph(graph, filler->separator);
6646                 if (filler->line != ' ')
6647                         append_to_rev_graph(graph, filler->line);
6648         }
6651 /* Prepare the next rev graph */
6652 static void
6653 prepare_rev_graph(struct rev_graph *graph)
6655         size_t i;
6657         /* First, traverse all lines of revisions up to the active one. */
6658         for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
6659                 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
6660                         break;
6662                 push_rev_graph(graph->next, graph->rev[graph->pos]);
6663         }
6665         /* Interleave the new revision parent(s). */
6666         for (i = 0; !graph->boundary && i < graph->parents->size; i++)
6667                 push_rev_graph(graph->next, graph->parents->rev[i]);
6669         /* Lastly, put any remaining revisions. */
6670         for (i = graph->pos + 1; i < graph->size; i++)
6671                 push_rev_graph(graph->next, graph->rev[i]);
6674 static void
6675 update_rev_graph(struct view *view, struct rev_graph *graph)
6677         /* If this is the finalizing update ... */
6678         if (graph->commit)
6679                 prepare_rev_graph(graph);
6681         /* Graph visualization needs a one rev look-ahead,
6682          * so the first update doesn't visualize anything. */
6683         if (!graph->prev->commit)
6684                 return;
6686         if (view->lines > 2)
6687                 view->line[view->lines - 3].dirty = 1;
6688         if (view->lines > 1)
6689                 view->line[view->lines - 2].dirty = 1;
6690         draw_rev_graph(graph->prev);
6691         done_rev_graph(graph->prev->prev);
6695 /*
6696  * Main view backend
6697  */
6699 static const char *main_argv[SIZEOF_ARG] = {
6700         "git", "log", "--no-color", "--pretty=raw", "--parents",
6701                       "--topo-order", "%(head)", NULL
6702 };
6704 static bool
6705 main_draw(struct view *view, struct line *line, unsigned int lineno)
6707         struct commit *commit = line->data;
6709         if (!commit->author)
6710                 return FALSE;
6712         if (opt_date && draw_date(view, &commit->time))
6713                 return TRUE;
6715         if (opt_author && draw_author(view, commit->author))
6716                 return TRUE;
6718         if (opt_rev_graph && commit->graph_size &&
6719             draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
6720                 return TRUE;
6722         if (opt_show_refs && commit->refs) {
6723                 size_t i;
6725                 for (i = 0; i < commit->refs->size; i++) {
6726                         struct ref *ref = commit->refs->refs[i];
6727                         enum line_type type;
6729                         if (ref->head)
6730                                 type = LINE_MAIN_HEAD;
6731                         else if (ref->ltag)
6732                                 type = LINE_MAIN_LOCAL_TAG;
6733                         else if (ref->tag)
6734                                 type = LINE_MAIN_TAG;
6735                         else if (ref->tracked)
6736                                 type = LINE_MAIN_TRACKED;
6737                         else if (ref->remote)
6738                                 type = LINE_MAIN_REMOTE;
6739                         else
6740                                 type = LINE_MAIN_REF;
6742                         if (draw_text(view, type, "[", TRUE) ||
6743                             draw_text(view, type, ref->name, TRUE) ||
6744                             draw_text(view, type, "]", TRUE))
6745                                 return TRUE;
6747                         if (draw_text(view, LINE_DEFAULT, " ", TRUE))
6748                                 return TRUE;
6749                 }
6750         }
6752         draw_text(view, LINE_DEFAULT, commit->title, TRUE);
6753         return TRUE;
6756 /* Reads git log --pretty=raw output and parses it into the commit struct. */
6757 static bool
6758 main_read(struct view *view, char *line)
6760         static struct rev_graph *graph = graph_stacks;
6761         enum line_type type;
6762         struct commit *commit;
6764         if (!line) {
6765                 int i;
6767                 if (!view->lines && !view->prev)
6768                         die("No revisions match the given arguments.");
6769                 if (view->lines > 0) {
6770                         commit = view->line[view->lines - 1].data;
6771                         view->line[view->lines - 1].dirty = 1;
6772                         if (!commit->author) {
6773                                 view->lines--;
6774                                 free(commit);
6775                                 graph->commit = NULL;
6776                         }
6777                 }
6778                 update_rev_graph(view, graph);
6780                 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
6781                         clear_rev_graph(&graph_stacks[i]);
6782                 return TRUE;
6783         }
6785         type = get_line_type(line);
6786         if (type == LINE_COMMIT) {
6787                 commit = calloc(1, sizeof(struct commit));
6788                 if (!commit)
6789                         return FALSE;
6791                 line += STRING_SIZE("commit ");
6792                 if (*line == '-') {
6793                         graph->boundary = 1;
6794                         line++;
6795                 }
6797                 string_copy_rev(commit->id, line);
6798                 commit->refs = get_ref_list(commit->id);
6799                 graph->commit = commit;
6800                 add_line_data(view, commit, LINE_MAIN_COMMIT);
6802                 while ((line = strchr(line, ' '))) {
6803                         line++;
6804                         push_rev_graph(graph->parents, line);
6805                         commit->has_parents = TRUE;
6806                 }
6807                 return TRUE;
6808         }
6810         if (!view->lines)
6811                 return TRUE;
6812         commit = view->line[view->lines - 1].data;
6814         switch (type) {
6815         case LINE_PARENT:
6816                 if (commit->has_parents)
6817                         break;
6818                 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
6819                 break;
6821         case LINE_AUTHOR:
6822                 parse_author_line(line + STRING_SIZE("author "),
6823                                   &commit->author, &commit->time);
6824                 update_rev_graph(view, graph);
6825                 graph = graph->next;
6826                 break;
6828         default:
6829                 /* Fill in the commit title if it has not already been set. */
6830                 if (commit->title[0])
6831                         break;
6833                 /* Require titles to start with a non-space character at the
6834                  * offset used by git log. */
6835                 if (strncmp(line, "    ", 4))
6836                         break;
6837                 line += 4;
6838                 /* Well, if the title starts with a whitespace character,
6839                  * try to be forgiving.  Otherwise we end up with no title. */
6840                 while (isspace(*line))
6841                         line++;
6842                 if (*line == '\0')
6843                         break;
6844                 /* FIXME: More graceful handling of titles; append "..." to
6845                  * shortened titles, etc. */
6847                 string_expand(commit->title, sizeof(commit->title), line, 1);
6848                 view->line[view->lines - 1].dirty = 1;
6849         }
6851         return TRUE;
6854 static enum request
6855 main_request(struct view *view, enum request request, struct line *line)
6857         enum open_flags flags = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
6859         switch (request) {
6860         case REQ_ENTER:
6861                 open_view(view, REQ_VIEW_DIFF, flags);
6862                 break;
6863         case REQ_REFRESH:
6864                 load_refs();
6865                 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
6866                 break;
6867         default:
6868                 return request;
6869         }
6871         return REQ_NONE;
6874 static bool
6875 grep_refs(struct ref_list *list, regex_t *regex)
6877         regmatch_t pmatch;
6878         size_t i;
6880         if (!opt_show_refs || !list)
6881                 return FALSE;
6883         for (i = 0; i < list->size; i++) {
6884                 if (regexec(regex, list->refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
6885                         return TRUE;
6886         }
6888         return FALSE;
6891 static bool
6892 main_grep(struct view *view, struct line *line)
6894         struct commit *commit = line->data;
6895         const char *text[] = {
6896                 commit->title,
6897                 opt_author ? commit->author : "",
6898                 mkdate(&commit->time, opt_date),
6899                 NULL
6900         };
6902         return grep_text(view, text) || grep_refs(commit->refs, view->regex);
6905 static void
6906 main_select(struct view *view, struct line *line)
6908         struct commit *commit = line->data;
6910         string_copy_rev(view->ref, commit->id);
6911         string_copy_rev(ref_commit, view->ref);
6914 static struct view_ops main_ops = {
6915         "commit",
6916         main_argv,
6917         NULL,
6918         main_read,
6919         main_draw,
6920         main_request,
6921         main_grep,
6922         main_select,
6923 };
6926 /*
6927  * Status management
6928  */
6930 /* Whether or not the curses interface has been initialized. */
6931 static bool cursed = FALSE;
6933 /* Terminal hacks and workarounds. */
6934 static bool use_scroll_redrawwin;
6935 static bool use_scroll_status_wclear;
6937 /* The status window is used for polling keystrokes. */
6938 static WINDOW *status_win;
6940 /* Reading from the prompt? */
6941 static bool input_mode = FALSE;
6943 static bool status_empty = FALSE;
6945 /* Update status and title window. */
6946 static void
6947 report(const char *msg, ...)
6949         struct view *view = display[current_view];
6951         if (input_mode)
6952                 return;
6954         if (!view) {
6955                 char buf[SIZEOF_STR];
6956                 va_list args;
6958                 va_start(args, msg);
6959                 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
6960                         buf[sizeof(buf) - 1] = 0;
6961                         buf[sizeof(buf) - 2] = '.';
6962                         buf[sizeof(buf) - 3] = '.';
6963                         buf[sizeof(buf) - 4] = '.';
6964                 }
6965                 va_end(args);
6966                 die("%s", buf);
6967         }
6969         if (!status_empty || *msg) {
6970                 va_list args;
6972                 va_start(args, msg);
6974                 wmove(status_win, 0, 0);
6975                 if (view->has_scrolled && use_scroll_status_wclear)
6976                         wclear(status_win);
6977                 if (*msg) {
6978                         vwprintw(status_win, msg, args);
6979                         status_empty = FALSE;
6980                 } else {
6981                         status_empty = TRUE;
6982                 }
6983                 wclrtoeol(status_win);
6984                 wnoutrefresh(status_win);
6986                 va_end(args);
6987         }
6989         update_view_title(view);
6992 static void
6993 init_display(void)
6995         const char *term;
6996         int x, y;
6998         /* Initialize the curses library */
6999         if (isatty(STDIN_FILENO)) {
7000                 cursed = !!initscr();
7001                 opt_tty = stdin;
7002         } else {
7003                 /* Leave stdin and stdout alone when acting as a pager. */
7004                 opt_tty = fopen("/dev/tty", "r+");
7005                 if (!opt_tty)
7006                         die("Failed to open /dev/tty");
7007                 cursed = !!newterm(NULL, opt_tty, opt_tty);
7008         }
7010         if (!cursed)
7011                 die("Failed to initialize curses");
7013         nonl();         /* Disable conversion and detect newlines from input. */
7014         cbreak();       /* Take input chars one at a time, no wait for \n */
7015         noecho();       /* Don't echo input */
7016         leaveok(stdscr, FALSE);
7018         if (has_colors())
7019                 init_colors();
7021         getmaxyx(stdscr, y, x);
7022         status_win = newwin(1, 0, y - 1, 0);
7023         if (!status_win)
7024                 die("Failed to create status window");
7026         /* Enable keyboard mapping */
7027         keypad(status_win, TRUE);
7028         wbkgdset(status_win, get_line_attr(LINE_STATUS));
7030         TABSIZE = opt_tab_size;
7032         term = getenv("XTERM_VERSION") ? NULL : getenv("COLORTERM");
7033         if (term && !strcmp(term, "gnome-terminal")) {
7034                 /* In the gnome-terminal-emulator, the message from
7035                  * scrolling up one line when impossible followed by
7036                  * scrolling down one line causes corruption of the
7037                  * status line. This is fixed by calling wclear. */
7038                 use_scroll_status_wclear = TRUE;
7039                 use_scroll_redrawwin = FALSE;
7041         } else if (term && !strcmp(term, "xrvt-xpm")) {
7042                 /* No problems with full optimizations in xrvt-(unicode)
7043                  * and aterm. */
7044                 use_scroll_status_wclear = use_scroll_redrawwin = FALSE;
7046         } else {
7047                 /* When scrolling in (u)xterm the last line in the
7048                  * scrolling direction will update slowly. */
7049                 use_scroll_redrawwin = TRUE;
7050                 use_scroll_status_wclear = FALSE;
7051         }
7054 static int
7055 get_input(int prompt_position)
7057         struct view *view;
7058         int i, key, cursor_y, cursor_x;
7059         bool loading = FALSE;
7061         if (prompt_position)
7062                 input_mode = TRUE;
7064         while (TRUE) {
7065                 foreach_view (view, i) {
7066                         update_view(view);
7067                         if (view_is_displayed(view) && view->has_scrolled &&
7068                             use_scroll_redrawwin)
7069                                 redrawwin(view->win);
7070                         view->has_scrolled = FALSE;
7071                         if (view->pipe)
7072                                 loading = TRUE;
7073                 }
7075                 /* Update the cursor position. */
7076                 if (prompt_position) {
7077                         getbegyx(status_win, cursor_y, cursor_x);
7078                         cursor_x = prompt_position;
7079                 } else {
7080                         view = display[current_view];
7081                         getbegyx(view->win, cursor_y, cursor_x);
7082                         cursor_x = view->width - 1;
7083                         cursor_y += view->lineno - view->offset;
7084                 }
7085                 setsyx(cursor_y, cursor_x);
7087                 /* Refresh, accept single keystroke of input */
7088                 doupdate();
7089                 nodelay(status_win, loading);
7090                 key = wgetch(status_win);
7092                 /* wgetch() with nodelay() enabled returns ERR when
7093                  * there's no input. */
7094                 if (key == ERR) {
7096                 } else if (key == KEY_RESIZE) {
7097                         int height, width;
7099                         getmaxyx(stdscr, height, width);
7101                         wresize(status_win, 1, width);
7102                         mvwin(status_win, height - 1, 0);
7103                         wnoutrefresh(status_win);
7104                         resize_display();
7105                         redraw_display(TRUE);
7107                 } else {
7108                         input_mode = FALSE;
7109                         return key;
7110                 }
7111         }
7114 static char *
7115 prompt_input(const char *prompt, input_handler handler, void *data)
7117         enum input_status status = INPUT_OK;
7118         static char buf[SIZEOF_STR];
7119         size_t pos = 0;
7121         buf[pos] = 0;
7123         while (status == INPUT_OK || status == INPUT_SKIP) {
7124                 int key;
7126                 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
7127                 wclrtoeol(status_win);
7129                 key = get_input(pos + 1);
7130                 switch (key) {
7131                 case KEY_RETURN:
7132                 case KEY_ENTER:
7133                 case '\n':
7134                         status = pos ? INPUT_STOP : INPUT_CANCEL;
7135                         break;
7137                 case KEY_BACKSPACE:
7138                         if (pos > 0)
7139                                 buf[--pos] = 0;
7140                         else
7141                                 status = INPUT_CANCEL;
7142                         break;
7144                 case KEY_ESC:
7145                         status = INPUT_CANCEL;
7146                         break;
7148                 default:
7149                         if (pos >= sizeof(buf)) {
7150                                 report("Input string too long");
7151                                 return NULL;
7152                         }
7154                         status = handler(data, buf, key);
7155                         if (status == INPUT_OK)
7156                                 buf[pos++] = (char) key;
7157                 }
7158         }
7160         /* Clear the status window */
7161         status_empty = FALSE;
7162         report("");
7164         if (status == INPUT_CANCEL)
7165                 return NULL;
7167         buf[pos++] = 0;
7169         return buf;
7172 static enum input_status
7173 prompt_yesno_handler(void *data, char *buf, int c)
7175         if (c == 'y' || c == 'Y')
7176                 return INPUT_STOP;
7177         if (c == 'n' || c == 'N')
7178                 return INPUT_CANCEL;
7179         return INPUT_SKIP;
7182 static bool
7183 prompt_yesno(const char *prompt)
7185         char prompt2[SIZEOF_STR];
7187         if (!string_format(prompt2, "%s [Yy/Nn]", prompt))
7188                 return FALSE;
7190         return !!prompt_input(prompt2, prompt_yesno_handler, NULL);
7193 static enum input_status
7194 read_prompt_handler(void *data, char *buf, int c)
7196         return isprint(c) ? INPUT_OK : INPUT_SKIP;
7199 static char *
7200 read_prompt(const char *prompt)
7202         return prompt_input(prompt, read_prompt_handler, NULL);
7205 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected)
7207         enum input_status status = INPUT_OK;
7208         int size = 0;
7210         while (items[size].text)
7211                 size++;
7213         while (status == INPUT_OK) {
7214                 const struct menu_item *item = &items[*selected];
7215                 int key;
7216                 int i;
7218                 mvwprintw(status_win, 0, 0, "%s (%d of %d) ",
7219                           prompt, *selected + 1, size);
7220                 if (item->hotkey)
7221                         wprintw(status_win, "[%c] ", (char) item->hotkey);
7222                 wprintw(status_win, "%s", item->text);
7223                 wclrtoeol(status_win);
7225                 key = get_input(COLS - 1);
7226                 switch (key) {
7227                 case KEY_RETURN:
7228                 case KEY_ENTER:
7229                 case '\n':
7230                         status = INPUT_STOP;
7231                         break;
7233                 case KEY_LEFT:
7234                 case KEY_UP:
7235                         *selected = *selected - 1;
7236                         if (*selected < 0)
7237                                 *selected = size - 1;
7238                         break;
7240                 case KEY_RIGHT:
7241                 case KEY_DOWN:
7242                         *selected = (*selected + 1) % size;
7243                         break;
7245                 case KEY_ESC:
7246                         status = INPUT_CANCEL;
7247                         break;
7249                 default:
7250                         for (i = 0; items[i].text; i++)
7251                                 if (items[i].hotkey == key) {
7252                                         *selected = i;
7253                                         status = INPUT_STOP;
7254                                         break;
7255                                 }
7256                 }
7257         }
7259         /* Clear the status window */
7260         status_empty = FALSE;
7261         report("");
7263         return status != INPUT_CANCEL;
7266 /*
7267  * Repository properties
7268  */
7270 static struct ref **refs = NULL;
7271 static size_t refs_size = 0;
7272 static struct ref *refs_head = NULL;
7274 static struct ref_list **ref_lists = NULL;
7275 static size_t ref_lists_size = 0;
7277 DEFINE_ALLOCATOR(realloc_refs, struct ref *, 256)
7278 DEFINE_ALLOCATOR(realloc_refs_list, struct ref *, 8)
7279 DEFINE_ALLOCATOR(realloc_ref_lists, struct ref_list *, 8)
7281 static int
7282 compare_refs(const void *ref1_, const void *ref2_)
7284         const struct ref *ref1 = *(const struct ref **)ref1_;
7285         const struct ref *ref2 = *(const struct ref **)ref2_;
7287         if (ref1->tag != ref2->tag)
7288                 return ref2->tag - ref1->tag;
7289         if (ref1->ltag != ref2->ltag)
7290                 return ref2->ltag - ref2->ltag;
7291         if (ref1->head != ref2->head)
7292                 return ref2->head - ref1->head;
7293         if (ref1->tracked != ref2->tracked)
7294                 return ref2->tracked - ref1->tracked;
7295         if (ref1->remote != ref2->remote)
7296                 return ref2->remote - ref1->remote;
7297         return strcmp(ref1->name, ref2->name);
7300 static void
7301 foreach_ref(bool (*visitor)(void *data, const struct ref *ref), void *data)
7303         size_t i;
7305         for (i = 0; i < refs_size; i++)
7306                 if (!visitor(data, refs[i]))
7307                         break;
7310 static struct ref *
7311 get_ref_head()
7313         return refs_head;
7316 static struct ref_list *
7317 get_ref_list(const char *id)
7319         struct ref_list *list;
7320         size_t i;
7322         for (i = 0; i < ref_lists_size; i++)
7323                 if (!strcmp(id, ref_lists[i]->id))
7324                         return ref_lists[i];
7326         if (!realloc_ref_lists(&ref_lists, ref_lists_size, 1))
7327                 return NULL;
7328         list = calloc(1, sizeof(*list));
7329         if (!list)
7330                 return NULL;
7332         for (i = 0; i < refs_size; i++) {
7333                 if (!strcmp(id, refs[i]->id) &&
7334                     realloc_refs_list(&list->refs, list->size, 1))
7335                         list->refs[list->size++] = refs[i];
7336         }
7338         if (!list->refs) {
7339                 free(list);
7340                 return NULL;
7341         }
7343         qsort(list->refs, list->size, sizeof(*list->refs), compare_refs);
7344         ref_lists[ref_lists_size++] = list;
7345         return list;
7348 static int
7349 read_ref(char *id, size_t idlen, char *name, size_t namelen)
7351         struct ref *ref = NULL;
7352         bool tag = FALSE;
7353         bool ltag = FALSE;
7354         bool remote = FALSE;
7355         bool tracked = FALSE;
7356         bool head = FALSE;
7357         int from = 0, to = refs_size - 1;
7359         if (!prefixcmp(name, "refs/tags/")) {
7360                 if (!suffixcmp(name, namelen, "^{}")) {
7361                         namelen -= 3;
7362                         name[namelen] = 0;
7363                 } else {
7364                         ltag = TRUE;
7365                 }
7367                 tag = TRUE;
7368                 namelen -= STRING_SIZE("refs/tags/");
7369                 name    += STRING_SIZE("refs/tags/");
7371         } else if (!prefixcmp(name, "refs/remotes/")) {
7372                 remote = TRUE;
7373                 namelen -= STRING_SIZE("refs/remotes/");
7374                 name    += STRING_SIZE("refs/remotes/");
7375                 tracked  = !strcmp(opt_remote, name);
7377         } else if (!prefixcmp(name, "refs/heads/")) {
7378                 namelen -= STRING_SIZE("refs/heads/");
7379                 name    += STRING_SIZE("refs/heads/");
7380                 if (!strncmp(opt_head, name, namelen))
7381                         return OK;
7383         } else if (!strcmp(name, "HEAD")) {
7384                 head     = TRUE;
7385                 if (*opt_head) {
7386                         namelen  = strlen(opt_head);
7387                         name     = opt_head;
7388                 }
7389         }
7391         /* If we are reloading or it's an annotated tag, replace the
7392          * previous SHA1 with the resolved commit id; relies on the fact
7393          * git-ls-remote lists the commit id of an annotated tag right
7394          * before the commit id it points to. */
7395         while (from <= to) {
7396                 size_t pos = (to + from) / 2;
7397                 int cmp = strcmp(name, refs[pos]->name);
7399                 if (!cmp) {
7400                         ref = refs[pos];
7401                         break;
7402                 }
7404                 if (cmp < 0)
7405                         to = pos - 1;
7406                 else
7407                         from = pos + 1;
7408         }
7410         if (!ref) {
7411                 if (!realloc_refs(&refs, refs_size, 1))
7412                         return ERR;
7413                 ref = calloc(1, sizeof(*ref) + namelen);
7414                 if (!ref)
7415                         return ERR;
7416                 memmove(refs + from + 1, refs + from,
7417                         (refs_size - from) * sizeof(*refs));
7418                 refs[from] = ref;
7419                 strncpy(ref->name, name, namelen);
7420                 refs_size++;
7421         }
7423         ref->head = head;
7424         ref->tag = tag;
7425         ref->ltag = ltag;
7426         ref->remote = remote;
7427         ref->tracked = tracked;
7428         string_copy_rev(ref->id, id);
7430         if (head)
7431                 refs_head = ref;
7432         return OK;
7435 static int
7436 load_refs(void)
7438         const char *head_argv[] = {
7439                 "git", "symbolic-ref", "HEAD", NULL
7440         };
7441         static const char *ls_remote_argv[SIZEOF_ARG] = {
7442                 "git", "ls-remote", opt_git_dir, NULL
7443         };
7444         static bool init = FALSE;
7445         size_t i;
7447         if (!init) {
7448                 if (!argv_from_env(ls_remote_argv, "TIG_LS_REMOTE"))
7449                         die("TIG_LS_REMOTE contains too many arguments");
7450                 init = TRUE;
7451         }
7453         if (!*opt_git_dir)
7454                 return OK;
7456         if (io_run_buf(head_argv, opt_head, sizeof(opt_head)) &&
7457             !prefixcmp(opt_head, "refs/heads/")) {
7458                 char *offset = opt_head + STRING_SIZE("refs/heads/");
7460                 memmove(opt_head, offset, strlen(offset) + 1);
7461         }
7463         refs_head = NULL;
7464         for (i = 0; i < refs_size; i++)
7465                 refs[i]->id[0] = 0;
7467         if (io_run_load(ls_remote_argv, "\t", read_ref) == ERR)
7468                 return ERR;
7470         /* Update the ref lists to reflect changes. */
7471         for (i = 0; i < ref_lists_size; i++) {
7472                 struct ref_list *list = ref_lists[i];
7473                 size_t old, new;
7475                 for (old = new = 0; old < list->size; old++)
7476                         if (!strcmp(list->id, list->refs[old]->id))
7477                                 list->refs[new++] = list->refs[old];
7478                 list->size = new;
7479         }
7481         return OK;
7484 static void
7485 set_remote_branch(const char *name, const char *value, size_t valuelen)
7487         if (!strcmp(name, ".remote")) {
7488                 string_ncopy(opt_remote, value, valuelen);
7490         } else if (*opt_remote && !strcmp(name, ".merge")) {
7491                 size_t from = strlen(opt_remote);
7493                 if (!prefixcmp(value, "refs/heads/"))
7494                         value += STRING_SIZE("refs/heads/");
7496                 if (!string_format_from(opt_remote, &from, "/%s", value))
7497                         opt_remote[0] = 0;
7498         }
7501 static void
7502 set_repo_config_option(char *name, char *value, int (*cmd)(int, const char **))
7504         const char *argv[SIZEOF_ARG] = { name, "=" };
7505         int argc = 1 + (cmd == option_set_command);
7506         int error = ERR;
7508         if (!argv_from_string(argv, &argc, value))
7509                 config_msg = "Too many option arguments";
7510         else
7511                 error = cmd(argc, argv);
7513         if (error == ERR)
7514                 warn("Option 'tig.%s': %s", name, config_msg);
7517 static bool
7518 set_environment_variable(const char *name, const char *value)
7520         size_t len = strlen(name) + 1 + strlen(value) + 1;
7521         char *env = malloc(len);
7523         if (env &&
7524             string_nformat(env, len, NULL, "%s=%s", name, value) &&
7525             putenv(env) == 0)
7526                 return TRUE;
7527         free(env);
7528         return FALSE;
7531 static void
7532 set_work_tree(const char *value)
7534         char cwd[SIZEOF_STR];
7536         if (!getcwd(cwd, sizeof(cwd)))
7537                 die("Failed to get cwd path: %s", strerror(errno));
7538         if (chdir(opt_git_dir) < 0)
7539                 die("Failed to chdir(%s): %s", strerror(errno));
7540         if (!getcwd(opt_git_dir, sizeof(opt_git_dir)))
7541                 die("Failed to get git path: %s", strerror(errno));
7542         if (chdir(cwd) < 0)
7543                 die("Failed to chdir(%s): %s", cwd, strerror(errno));
7544         if (chdir(value) < 0)
7545                 die("Failed to chdir(%s): %s", value, strerror(errno));
7546         if (!getcwd(cwd, sizeof(cwd)))
7547                 die("Failed to get cwd path: %s", strerror(errno));
7548         if (!set_environment_variable("GIT_WORK_TREE", cwd))
7549                 die("Failed to set GIT_WORK_TREE to '%s'", cwd);
7550         if (!set_environment_variable("GIT_DIR", opt_git_dir))
7551                 die("Failed to set GIT_DIR to '%s'", opt_git_dir);
7552         opt_is_inside_work_tree = TRUE;
7555 static int
7556 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
7558         if (!strcmp(name, "i18n.commitencoding"))
7559                 string_ncopy(opt_encoding, value, valuelen);
7561         else if (!strcmp(name, "core.editor"))
7562                 string_ncopy(opt_editor, value, valuelen);
7564         else if (!strcmp(name, "core.worktree"))
7565                 set_work_tree(value);
7567         else if (!prefixcmp(name, "tig.color."))
7568                 set_repo_config_option(name + 10, value, option_color_command);
7570         else if (!prefixcmp(name, "tig.bind."))
7571                 set_repo_config_option(name + 9, value, option_bind_command);
7573         else if (!prefixcmp(name, "tig."))
7574                 set_repo_config_option(name + 4, value, option_set_command);
7576         else if (*opt_head && !prefixcmp(name, "branch.") &&
7577                  !strncmp(name + 7, opt_head, strlen(opt_head)))
7578                 set_remote_branch(name + 7 + strlen(opt_head), value, valuelen);
7580         return OK;
7583 static int
7584 load_git_config(void)
7586         const char *config_list_argv[] = { "git", "config", "--list", NULL };
7588         return io_run_load(config_list_argv, "=", read_repo_config_option);
7591 static int
7592 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
7594         if (!opt_git_dir[0]) {
7595                 string_ncopy(opt_git_dir, name, namelen);
7597         } else if (opt_is_inside_work_tree == -1) {
7598                 /* This can be 3 different values depending on the
7599                  * version of git being used. If git-rev-parse does not
7600                  * understand --is-inside-work-tree it will simply echo
7601                  * the option else either "true" or "false" is printed.
7602                  * Default to true for the unknown case. */
7603                 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
7605         } else if (*name == '.') {
7606                 string_ncopy(opt_cdup, name, namelen);
7608         } else {
7609                 string_ncopy(opt_prefix, name, namelen);
7610         }
7612         return OK;
7615 static int
7616 load_repo_info(void)
7618         const char *rev_parse_argv[] = {
7619                 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
7620                         "--show-cdup", "--show-prefix", NULL
7621         };
7623         return io_run_load(rev_parse_argv, "=", read_repo_info);
7627 /*
7628  * Main
7629  */
7631 static const char usage[] =
7632 "tig " TIG_VERSION " (" __DATE__ ")\n"
7633 "\n"
7634 "Usage: tig        [options] [revs] [--] [paths]\n"
7635 "   or: tig show   [options] [revs] [--] [paths]\n"
7636 "   or: tig blame  [rev] path\n"
7637 "   or: tig status\n"
7638 "   or: tig <      [git command output]\n"
7639 "\n"
7640 "Options:\n"
7641 "  -v, --version   Show version and exit\n"
7642 "  -h, --help      Show help message and exit";
7644 static void __NORETURN
7645 quit(int sig)
7647         /* XXX: Restore tty modes and let the OS cleanup the rest! */
7648         if (cursed)
7649                 endwin();
7650         exit(0);
7653 static void __NORETURN
7654 die(const char *err, ...)
7656         va_list args;
7658         endwin();
7660         va_start(args, err);
7661         fputs("tig: ", stderr);
7662         vfprintf(stderr, err, args);
7663         fputs("\n", stderr);
7664         va_end(args);
7666         exit(1);
7669 static void
7670 warn(const char *msg, ...)
7672         va_list args;
7674         va_start(args, msg);
7675         fputs("tig warning: ", stderr);
7676         vfprintf(stderr, msg, args);
7677         fputs("\n", stderr);
7678         va_end(args);
7681 static enum request
7682 parse_options(int argc, const char *argv[])
7684         enum request request = REQ_VIEW_MAIN;
7685         const char *subcommand;
7686         bool seen_dashdash = FALSE;
7687         /* XXX: This is vulnerable to the user overriding options
7688          * required for the main view parser. */
7689         const char *custom_argv[SIZEOF_ARG] = {
7690                 "git", "log", "--no-color", "--pretty=raw", "--parents",
7691                         "--topo-order", NULL
7692         };
7693         int i, j = 6;
7695         if (!isatty(STDIN_FILENO)) {
7696                 io_open(&VIEW(REQ_VIEW_PAGER)->io, "");
7697                 return REQ_VIEW_PAGER;
7698         }
7700         if (argc <= 1)
7701                 return REQ_NONE;
7703         subcommand = argv[1];
7704         if (!strcmp(subcommand, "status")) {
7705                 if (argc > 2)
7706                         warn("ignoring arguments after `%s'", subcommand);
7707                 return REQ_VIEW_STATUS;
7709         } else if (!strcmp(subcommand, "blame")) {
7710                 if (argc <= 2 || argc > 4)
7711                         die("invalid number of options to blame\n\n%s", usage);
7713                 i = 2;
7714                 if (argc == 4) {
7715                         string_ncopy(opt_ref, argv[i], strlen(argv[i]));
7716                         i++;
7717                 }
7719                 string_ncopy(opt_file, argv[i], strlen(argv[i]));
7720                 return REQ_VIEW_BLAME;
7722         } else if (!strcmp(subcommand, "show")) {
7723                 request = REQ_VIEW_DIFF;
7725         } else {
7726                 subcommand = NULL;
7727         }
7729         if (subcommand) {
7730                 custom_argv[1] = subcommand;
7731                 j = 2;
7732         }
7734         for (i = 1 + !!subcommand; i < argc; i++) {
7735                 const char *opt = argv[i];
7737                 if (seen_dashdash || !strcmp(opt, "--")) {
7738                         seen_dashdash = TRUE;
7740                 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
7741                         printf("tig version %s\n", TIG_VERSION);
7742                         quit(0);
7744                 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
7745                         printf("%s\n", usage);
7746                         quit(0);
7747                 }
7749                 custom_argv[j++] = opt;
7750                 if (j >= ARRAY_SIZE(custom_argv))
7751                         die("command too long");
7752         }
7754         if (!prepare_update(VIEW(request), custom_argv, NULL))
7755                 die("Failed to format arguments");
7757         return request;
7760 int
7761 main(int argc, const char *argv[])
7763         const char *codeset = "UTF-8";
7764         enum request request = parse_options(argc, argv);
7765         struct view *view;
7766         size_t i;
7768         signal(SIGINT, quit);
7769         signal(SIGPIPE, SIG_IGN);
7771         if (setlocale(LC_ALL, "")) {
7772                 codeset = nl_langinfo(CODESET);
7773         }
7775         if (load_repo_info() == ERR)
7776                 die("Failed to load repo info.");
7778         if (load_options() == ERR)
7779                 die("Failed to load user config.");
7781         if (load_git_config() == ERR)
7782                 die("Failed to load repo config.");
7784         /* Require a git repository unless when running in pager mode. */
7785         if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
7786                 die("Not a git repository");
7788         if (*opt_encoding && strcmp(codeset, "UTF-8")) {
7789                 opt_iconv_in = iconv_open("UTF-8", opt_encoding);
7790                 if (opt_iconv_in == ICONV_NONE)
7791                         die("Failed to initialize character set conversion");
7792         }
7794         if (codeset && strcmp(codeset, "UTF-8")) {
7795                 opt_iconv_out = iconv_open(codeset, "UTF-8");
7796                 if (opt_iconv_out == ICONV_NONE)
7797                         die("Failed to initialize character set conversion");
7798         }
7800         if (load_refs() == ERR)
7801                 die("Failed to load refs.");
7803         foreach_view (view, i)
7804                 if (!argv_from_env(view->ops->argv, view->cmd_env))
7805                         die("Too many arguments in the `%s` environment variable",
7806                             view->cmd_env);
7808         init_display();
7810         if (request != REQ_NONE)
7811                 open_view(NULL, request, OPEN_PREPARED);
7812         request = request == REQ_NONE ? REQ_VIEW_MAIN : REQ_NONE;
7814         while (view_driver(display[current_view], request)) {
7815                 int key = get_input(0);
7817                 view = display[current_view];
7818                 request = get_keybinding(view->keymap, key);
7820                 /* Some low-level request handling. This keeps access to
7821                  * status_win restricted. */
7822                 switch (request) {
7823                 case REQ_NONE:
7824                         report("Unknown key, press %s for help",
7825                                get_key(view->keymap, REQ_VIEW_HELP));
7826                         break;
7827                 case REQ_PROMPT:
7828                 {
7829                         char *cmd = read_prompt(":");
7831                         if (cmd && isdigit(*cmd)) {
7832                                 int lineno = view->lineno + 1;
7834                                 if (parse_int(&lineno, cmd, 1, view->lines + 1) == OK) {
7835                                         select_view_line(view, lineno - 1);
7836                                         report("");
7837                                 } else {
7838                                         report("Unable to parse '%s' as a line number", cmd);
7839                                 }
7841                         } else if (cmd) {
7842                                 struct view *next = VIEW(REQ_VIEW_PAGER);
7843                                 const char *argv[SIZEOF_ARG] = { "git" };
7844                                 int argc = 1;
7846                                 /* When running random commands, initially show the
7847                                  * command in the title. However, it maybe later be
7848                                  * overwritten if a commit line is selected. */
7849                                 string_ncopy(next->ref, cmd, strlen(cmd));
7851                                 if (!argv_from_string(argv, &argc, cmd)) {
7852                                         report("Too many arguments");
7853                                 } else if (!prepare_update(next, argv, NULL)) {
7854                                         report("Failed to format command");
7855                                 } else {
7856                                         open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
7857                                 }
7858                         }
7860                         request = REQ_NONE;
7861                         break;
7862                 }
7863                 case REQ_SEARCH:
7864                 case REQ_SEARCH_BACK:
7865                 {
7866                         const char *prompt = request == REQ_SEARCH ? "/" : "?";
7867                         char *search = read_prompt(prompt);
7869                         if (search)
7870                                 string_ncopy(opt_search, search, strlen(search));
7871                         else if (*opt_search)
7872                                 request = request == REQ_SEARCH ?
7873                                         REQ_FIND_NEXT :
7874                                         REQ_FIND_PREV;
7875                         else
7876                                 request = REQ_NONE;
7877                         break;
7878                 }
7879                 default:
7880                         break;
7881                 }
7882         }
7884         quit(0);
7886         return 0;