Code

Make the branch view always prepare how the main view is loaded
[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         {
5339                 const struct ref *ref = branch->ref;
5340                 const char *all_branches_argv[] = {
5341                         "git", "log", "--no-color", "--pretty=raw", "--parents",
5342                               "--topo-order",
5343                               ref == &branch_all ? "--all" : ref->name, NULL
5344                 };
5345                 struct view *main_view = VIEW(REQ_VIEW_MAIN);
5347                 if (!prepare_update(main_view, all_branches_argv, NULL))
5348                         report("Failed to load view of all branches");
5349                 else
5350                         open_view(view, REQ_VIEW_MAIN, OPEN_PREPARED | OPEN_SPLIT);
5351                 return REQ_NONE;
5352         }
5353         default:
5354                 return request;
5355         }
5358 static bool
5359 branch_read(struct view *view, char *line)
5361         static char id[SIZEOF_REV];
5362         struct branch *reference;
5363         size_t i;
5365         if (!line)
5366                 return TRUE;
5368         switch (get_line_type(line)) {
5369         case LINE_COMMIT:
5370                 string_copy_rev(id, line + STRING_SIZE("commit "));
5371                 return TRUE;
5373         case LINE_AUTHOR:
5374                 for (i = 0, reference = NULL; i < view->lines; i++) {
5375                         struct branch *branch = view->line[i].data;
5377                         if (strcmp(branch->ref->id, id))
5378                                 continue;
5380                         view->line[i].dirty = TRUE;
5381                         if (reference) {
5382                                 branch->author = reference->author;
5383                                 branch->time = reference->time;
5384                                 continue;
5385                         }
5387                         parse_author_line(line + STRING_SIZE("author "),
5388                                           &branch->author, &branch->time);
5389                         reference = branch;
5390                 }
5391                 return TRUE;
5393         default:
5394                 return TRUE;
5395         }
5399 static bool
5400 branch_open_visitor(void *data, const struct ref *ref)
5402         struct view *view = data;
5403         struct branch *branch;
5405         if (ref->tag || ref->ltag || ref->remote)
5406                 return TRUE;
5408         branch = calloc(1, sizeof(*branch));
5409         if (!branch)
5410                 return FALSE;
5412         branch->ref = ref;
5413         return !!add_line_data(view, branch, LINE_DEFAULT);
5416 static bool
5417 branch_open(struct view *view)
5419         const char *branch_log[] = {
5420                 "git", "log", "--no-color", "--pretty=raw",
5421                         "--simplify-by-decoration", "--all", NULL
5422         };
5424         if (!start_update(view, branch_log, NULL)) {
5425                 report("Failed to load branch data");
5426                 return TRUE;
5427         }
5429         setup_update(view, view->id);
5430         branch_open_visitor(view, &branch_all);
5431         foreach_ref(branch_open_visitor, view);
5432         view->p_restore = TRUE;
5434         return TRUE;
5437 static bool
5438 branch_grep(struct view *view, struct line *line)
5440         struct branch *branch = line->data;
5441         const char *text[] = {
5442                 branch->ref->name,
5443                 branch->author,
5444                 NULL
5445         };
5447         return grep_text(view, text);
5450 static void
5451 branch_select(struct view *view, struct line *line)
5453         struct branch *branch = line->data;
5455         string_copy_rev(view->ref, branch->ref->id);
5456         string_copy_rev(ref_commit, branch->ref->id);
5457         string_copy_rev(ref_head, branch->ref->id);
5458         string_copy_rev(ref_branch, branch->ref->name);
5461 static struct view_ops branch_ops = {
5462         "branch",
5463         NULL,
5464         branch_open,
5465         branch_read,
5466         branch_draw,
5467         branch_request,
5468         branch_grep,
5469         branch_select,
5470 };
5472 /*
5473  * Status backend
5474  */
5476 struct status {
5477         char status;
5478         struct {
5479                 mode_t mode;
5480                 char rev[SIZEOF_REV];
5481                 char name[SIZEOF_STR];
5482         } old;
5483         struct {
5484                 mode_t mode;
5485                 char rev[SIZEOF_REV];
5486                 char name[SIZEOF_STR];
5487         } new;
5488 };
5490 static char status_onbranch[SIZEOF_STR];
5491 static struct status stage_status;
5492 static enum line_type stage_line_type;
5493 static size_t stage_chunks;
5494 static int *stage_chunk;
5496 DEFINE_ALLOCATOR(realloc_ints, int, 32)
5498 /* This should work even for the "On branch" line. */
5499 static inline bool
5500 status_has_none(struct view *view, struct line *line)
5502         return line < view->line + view->lines && !line[1].data;
5505 /* Get fields from the diff line:
5506  * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
5507  */
5508 static inline bool
5509 status_get_diff(struct status *file, const char *buf, size_t bufsize)
5511         const char *old_mode = buf +  1;
5512         const char *new_mode = buf +  8;
5513         const char *old_rev  = buf + 15;
5514         const char *new_rev  = buf + 56;
5515         const char *status   = buf + 97;
5517         if (bufsize < 98 ||
5518             old_mode[-1] != ':' ||
5519             new_mode[-1] != ' ' ||
5520             old_rev[-1]  != ' ' ||
5521             new_rev[-1]  != ' ' ||
5522             status[-1]   != ' ')
5523                 return FALSE;
5525         file->status = *status;
5527         string_copy_rev(file->old.rev, old_rev);
5528         string_copy_rev(file->new.rev, new_rev);
5530         file->old.mode = strtoul(old_mode, NULL, 8);
5531         file->new.mode = strtoul(new_mode, NULL, 8);
5533         file->old.name[0] = file->new.name[0] = 0;
5535         return TRUE;
5538 static bool
5539 status_run(struct view *view, const char *argv[], char status, enum line_type type)
5541         struct status *unmerged = NULL;
5542         char *buf;
5543         struct io io;
5545         if (!io_run(&io, IO_RD, opt_cdup, argv))
5546                 return FALSE;
5548         add_line_data(view, NULL, type);
5550         while ((buf = io_get(&io, 0, TRUE))) {
5551                 struct status *file = unmerged;
5553                 if (!file) {
5554                         file = calloc(1, sizeof(*file));
5555                         if (!file || !add_line_data(view, file, type))
5556                                 goto error_out;
5557                 }
5559                 /* Parse diff info part. */
5560                 if (status) {
5561                         file->status = status;
5562                         if (status == 'A')
5563                                 string_copy(file->old.rev, NULL_ID);
5565                 } else if (!file->status || file == unmerged) {
5566                         if (!status_get_diff(file, buf, strlen(buf)))
5567                                 goto error_out;
5569                         buf = io_get(&io, 0, TRUE);
5570                         if (!buf)
5571                                 break;
5573                         /* Collapse all modified entries that follow an
5574                          * associated unmerged entry. */
5575                         if (unmerged == file) {
5576                                 unmerged->status = 'U';
5577                                 unmerged = NULL;
5578                         } else if (file->status == 'U') {
5579                                 unmerged = file;
5580                         }
5581                 }
5583                 /* Grab the old name for rename/copy. */
5584                 if (!*file->old.name &&
5585                     (file->status == 'R' || file->status == 'C')) {
5586                         string_ncopy(file->old.name, buf, strlen(buf));
5588                         buf = io_get(&io, 0, TRUE);
5589                         if (!buf)
5590                                 break;
5591                 }
5593                 /* git-ls-files just delivers a NUL separated list of
5594                  * file names similar to the second half of the
5595                  * git-diff-* output. */
5596                 string_ncopy(file->new.name, buf, strlen(buf));
5597                 if (!*file->old.name)
5598                         string_copy(file->old.name, file->new.name);
5599                 file = NULL;
5600         }
5602         if (io_error(&io)) {
5603 error_out:
5604                 io_done(&io);
5605                 return FALSE;
5606         }
5608         if (!view->line[view->lines - 1].data)
5609                 add_line_data(view, NULL, LINE_STAT_NONE);
5611         io_done(&io);
5612         return TRUE;
5615 /* Don't show unmerged entries in the staged section. */
5616 static const char *status_diff_index_argv[] = {
5617         "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
5618                              "--cached", "-M", "HEAD", NULL
5619 };
5621 static const char *status_diff_files_argv[] = {
5622         "git", "diff-files", "-z", NULL
5623 };
5625 static const char *status_list_other_argv[] = {
5626         "git", "ls-files", "-z", "--others", "--exclude-standard", opt_prefix, NULL
5627 };
5629 static const char *status_list_no_head_argv[] = {
5630         "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
5631 };
5633 static const char *update_index_argv[] = {
5634         "git", "update-index", "-q", "--unmerged", "--refresh", NULL
5635 };
5637 /* Restore the previous line number to stay in the context or select a
5638  * line with something that can be updated. */
5639 static void
5640 status_restore(struct view *view)
5642         if (view->p_lineno >= view->lines)
5643                 view->p_lineno = view->lines - 1;
5644         while (view->p_lineno < view->lines && !view->line[view->p_lineno].data)
5645                 view->p_lineno++;
5646         while (view->p_lineno > 0 && !view->line[view->p_lineno].data)
5647                 view->p_lineno--;
5649         /* If the above fails, always skip the "On branch" line. */
5650         if (view->p_lineno < view->lines)
5651                 view->lineno = view->p_lineno;
5652         else
5653                 view->lineno = 1;
5655         if (view->lineno < view->offset)
5656                 view->offset = view->lineno;
5657         else if (view->offset + view->height <= view->lineno)
5658                 view->offset = view->lineno - view->height + 1;
5660         view->p_restore = FALSE;
5663 static void
5664 status_update_onbranch(void)
5666         static const char *paths[][2] = {
5667                 { "rebase-apply/rebasing",      "Rebasing" },
5668                 { "rebase-apply/applying",      "Applying mailbox" },
5669                 { "rebase-apply/",              "Rebasing mailbox" },
5670                 { "rebase-merge/interactive",   "Interactive rebase" },
5671                 { "rebase-merge/",              "Rebase merge" },
5672                 { "MERGE_HEAD",                 "Merging" },
5673                 { "BISECT_LOG",                 "Bisecting" },
5674                 { "HEAD",                       "On branch" },
5675         };
5676         char buf[SIZEOF_STR];
5677         struct stat stat;
5678         int i;
5680         if (is_initial_commit()) {
5681                 string_copy(status_onbranch, "Initial commit");
5682                 return;
5683         }
5685         for (i = 0; i < ARRAY_SIZE(paths); i++) {
5686                 char *head = opt_head;
5688                 if (!string_format(buf, "%s/%s", opt_git_dir, paths[i][0]) ||
5689                     lstat(buf, &stat) < 0)
5690                         continue;
5692                 if (!*opt_head) {
5693                         struct io io;
5695                         if (io_open(&io, "%s/rebase-merge/head-name", opt_git_dir) &&
5696                             io_read_buf(&io, buf, sizeof(buf))) {
5697                                 head = buf;
5698                                 if (!prefixcmp(head, "refs/heads/"))
5699                                         head += STRING_SIZE("refs/heads/");
5700                         }
5701                 }
5703                 if (!string_format(status_onbranch, "%s %s", paths[i][1], head))
5704                         string_copy(status_onbranch, opt_head);
5705                 return;
5706         }
5708         string_copy(status_onbranch, "Not currently on any branch");
5711 /* First parse staged info using git-diff-index(1), then parse unstaged
5712  * info using git-diff-files(1), and finally untracked files using
5713  * git-ls-files(1). */
5714 static bool
5715 status_open(struct view *view)
5717         reset_view(view);
5719         add_line_data(view, NULL, LINE_STAT_HEAD);
5720         status_update_onbranch();
5722         io_run_bg(update_index_argv);
5724         if (is_initial_commit()) {
5725                 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
5726                         return FALSE;
5727         } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
5728                 return FALSE;
5729         }
5731         if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
5732             !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
5733                 return FALSE;
5735         /* Restore the exact position or use the specialized restore
5736          * mode? */
5737         if (!view->p_restore)
5738                 status_restore(view);
5739         return TRUE;
5742 static bool
5743 status_draw(struct view *view, struct line *line, unsigned int lineno)
5745         struct status *status = line->data;
5746         enum line_type type;
5747         const char *text;
5749         if (!status) {
5750                 switch (line->type) {
5751                 case LINE_STAT_STAGED:
5752                         type = LINE_STAT_SECTION;
5753                         text = "Changes to be committed:";
5754                         break;
5756                 case LINE_STAT_UNSTAGED:
5757                         type = LINE_STAT_SECTION;
5758                         text = "Changed but not updated:";
5759                         break;
5761                 case LINE_STAT_UNTRACKED:
5762                         type = LINE_STAT_SECTION;
5763                         text = "Untracked files:";
5764                         break;
5766                 case LINE_STAT_NONE:
5767                         type = LINE_DEFAULT;
5768                         text = "  (no files)";
5769                         break;
5771                 case LINE_STAT_HEAD:
5772                         type = LINE_STAT_HEAD;
5773                         text = status_onbranch;
5774                         break;
5776                 default:
5777                         return FALSE;
5778                 }
5779         } else {
5780                 static char buf[] = { '?', ' ', ' ', ' ', 0 };
5782                 buf[0] = status->status;
5783                 if (draw_text(view, line->type, buf, TRUE))
5784                         return TRUE;
5785                 type = LINE_DEFAULT;
5786                 text = status->new.name;
5787         }
5789         draw_text(view, type, text, TRUE);
5790         return TRUE;
5793 static enum request
5794 status_load_error(struct view *view, struct view *stage, const char *path)
5796         if (displayed_views() == 2 || display[current_view] != view)
5797                 maximize_view(view);
5798         report("Failed to load '%s': %s", path, io_strerror(&stage->io));
5799         return REQ_NONE;
5802 static enum request
5803 status_enter(struct view *view, struct line *line)
5805         struct status *status = line->data;
5806         const char *oldpath = status ? status->old.name : NULL;
5807         /* Diffs for unmerged entries are empty when passing the new
5808          * path, so leave it empty. */
5809         const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
5810         const char *info;
5811         enum open_flags split;
5812         struct view *stage = VIEW(REQ_VIEW_STAGE);
5814         if (line->type == LINE_STAT_NONE ||
5815             (!status && line[1].type == LINE_STAT_NONE)) {
5816                 report("No file to diff");
5817                 return REQ_NONE;
5818         }
5820         switch (line->type) {
5821         case LINE_STAT_STAGED:
5822                 if (is_initial_commit()) {
5823                         const char *no_head_diff_argv[] = {
5824                                 "git", "diff", "--no-color", "--patch-with-stat",
5825                                         "--", "/dev/null", newpath, NULL
5826                         };
5828                         if (!prepare_update(stage, no_head_diff_argv, opt_cdup))
5829                                 return status_load_error(view, stage, newpath);
5830                 } else {
5831                         const char *index_show_argv[] = {
5832                                 "git", "diff-index", "--root", "--patch-with-stat",
5833                                         "-C", "-M", "--cached", "HEAD", "--",
5834                                         oldpath, newpath, NULL
5835                         };
5837                         if (!prepare_update(stage, index_show_argv, opt_cdup))
5838                                 return status_load_error(view, stage, newpath);
5839                 }
5841                 if (status)
5842                         info = "Staged changes to %s";
5843                 else
5844                         info = "Staged changes";
5845                 break;
5847         case LINE_STAT_UNSTAGED:
5848         {
5849                 const char *files_show_argv[] = {
5850                         "git", "diff-files", "--root", "--patch-with-stat",
5851                                 "-C", "-M", "--", oldpath, newpath, NULL
5852                 };
5854                 if (!prepare_update(stage, files_show_argv, opt_cdup))
5855                         return status_load_error(view, stage, newpath);
5856                 if (status)
5857                         info = "Unstaged changes to %s";
5858                 else
5859                         info = "Unstaged changes";
5860                 break;
5861         }
5862         case LINE_STAT_UNTRACKED:
5863                 if (!newpath) {
5864                         report("No file to show");
5865                         return REQ_NONE;
5866                 }
5868                 if (!suffixcmp(status->new.name, -1, "/")) {
5869                         report("Cannot display a directory");
5870                         return REQ_NONE;
5871                 }
5873                 if (!prepare_update_file(stage, newpath))
5874                         return status_load_error(view, stage, newpath);
5875                 info = "Untracked file %s";
5876                 break;
5878         case LINE_STAT_HEAD:
5879                 return REQ_NONE;
5881         default:
5882                 die("line type %d not handled in switch", line->type);
5883         }
5885         split = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
5886         open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split);
5887         if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
5888                 if (status) {
5889                         stage_status = *status;
5890                 } else {
5891                         memset(&stage_status, 0, sizeof(stage_status));
5892                 }
5894                 stage_line_type = line->type;
5895                 stage_chunks = 0;
5896                 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
5897         }
5899         return REQ_NONE;
5902 static bool
5903 status_exists(struct status *status, enum line_type type)
5905         struct view *view = VIEW(REQ_VIEW_STATUS);
5906         unsigned long lineno;
5908         for (lineno = 0; lineno < view->lines; lineno++) {
5909                 struct line *line = &view->line[lineno];
5910                 struct status *pos = line->data;
5912                 if (line->type != type)
5913                         continue;
5914                 if (!pos && (!status || !status->status) && line[1].data) {
5915                         select_view_line(view, lineno);
5916                         return TRUE;
5917                 }
5918                 if (pos && !strcmp(status->new.name, pos->new.name)) {
5919                         select_view_line(view, lineno);
5920                         return TRUE;
5921                 }
5922         }
5924         return FALSE;
5928 static bool
5929 status_update_prepare(struct io *io, enum line_type type)
5931         const char *staged_argv[] = {
5932                 "git", "update-index", "-z", "--index-info", NULL
5933         };
5934         const char *others_argv[] = {
5935                 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
5936         };
5938         switch (type) {
5939         case LINE_STAT_STAGED:
5940                 return io_run(io, IO_WR, opt_cdup, staged_argv);
5942         case LINE_STAT_UNSTAGED:
5943         case LINE_STAT_UNTRACKED:
5944                 return io_run(io, IO_WR, opt_cdup, others_argv);
5946         default:
5947                 die("line type %d not handled in switch", type);
5948                 return FALSE;
5949         }
5952 static bool
5953 status_update_write(struct io *io, struct status *status, enum line_type type)
5955         char buf[SIZEOF_STR];
5956         size_t bufsize = 0;
5958         switch (type) {
5959         case LINE_STAT_STAGED:
5960                 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
5961                                         status->old.mode,
5962                                         status->old.rev,
5963                                         status->old.name, 0))
5964                         return FALSE;
5965                 break;
5967         case LINE_STAT_UNSTAGED:
5968         case LINE_STAT_UNTRACKED:
5969                 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
5970                         return FALSE;
5971                 break;
5973         default:
5974                 die("line type %d not handled in switch", type);
5975         }
5977         return io_write(io, buf, bufsize);
5980 static bool
5981 status_update_file(struct status *status, enum line_type type)
5983         struct io io;
5984         bool result;
5986         if (!status_update_prepare(&io, type))
5987                 return FALSE;
5989         result = status_update_write(&io, status, type);
5990         return io_done(&io) && result;
5993 static bool
5994 status_update_files(struct view *view, struct line *line)
5996         char buf[sizeof(view->ref)];
5997         struct io io;
5998         bool result = TRUE;
5999         struct line *pos = view->line + view->lines;
6000         int files = 0;
6001         int file, done;
6002         int cursor_y = -1, cursor_x = -1;
6004         if (!status_update_prepare(&io, line->type))
6005                 return FALSE;
6007         for (pos = line; pos < view->line + view->lines && pos->data; pos++)
6008                 files++;
6010         string_copy(buf, view->ref);
6011         getsyx(cursor_y, cursor_x);
6012         for (file = 0, done = 5; result && file < files; line++, file++) {
6013                 int almost_done = file * 100 / files;
6015                 if (almost_done > done) {
6016                         done = almost_done;
6017                         string_format(view->ref, "updating file %u of %u (%d%% done)",
6018                                       file, files, done);
6019                         update_view_title(view);
6020                         setsyx(cursor_y, cursor_x);
6021                         doupdate();
6022                 }
6023                 result = status_update_write(&io, line->data, line->type);
6024         }
6025         string_copy(view->ref, buf);
6027         return io_done(&io) && result;
6030 static bool
6031 status_update(struct view *view)
6033         struct line *line = &view->line[view->lineno];
6035         assert(view->lines);
6037         if (!line->data) {
6038                 /* This should work even for the "On branch" line. */
6039                 if (line < view->line + view->lines && !line[1].data) {
6040                         report("Nothing to update");
6041                         return FALSE;
6042                 }
6044                 if (!status_update_files(view, line + 1)) {
6045                         report("Failed to update file status");
6046                         return FALSE;
6047                 }
6049         } else if (!status_update_file(line->data, line->type)) {
6050                 report("Failed to update file status");
6051                 return FALSE;
6052         }
6054         return TRUE;
6057 static bool
6058 status_revert(struct status *status, enum line_type type, bool has_none)
6060         if (!status || type != LINE_STAT_UNSTAGED) {
6061                 if (type == LINE_STAT_STAGED) {
6062                         report("Cannot revert changes to staged files");
6063                 } else if (type == LINE_STAT_UNTRACKED) {
6064                         report("Cannot revert changes to untracked files");
6065                 } else if (has_none) {
6066                         report("Nothing to revert");
6067                 } else {
6068                         report("Cannot revert changes to multiple files");
6069                 }
6071         } else if (prompt_yesno("Are you sure you want to revert changes?")) {
6072                 char mode[10] = "100644";
6073                 const char *reset_argv[] = {
6074                         "git", "update-index", "--cacheinfo", mode,
6075                                 status->old.rev, status->old.name, NULL
6076                 };
6077                 const char *checkout_argv[] = {
6078                         "git", "checkout", "--", status->old.name, NULL
6079                 };
6081                 if (status->status == 'U') {
6082                         string_format(mode, "%5o", status->old.mode);
6084                         if (status->old.mode == 0 && status->new.mode == 0) {
6085                                 reset_argv[2] = "--force-remove";
6086                                 reset_argv[3] = status->old.name;
6087                                 reset_argv[4] = NULL;
6088                         }
6090                         if (!io_run_fg(reset_argv, opt_cdup))
6091                                 return FALSE;
6092                         if (status->old.mode == 0 && status->new.mode == 0)
6093                                 return TRUE;
6094                 }
6096                 return io_run_fg(checkout_argv, opt_cdup);
6097         }
6099         return FALSE;
6102 static enum request
6103 status_request(struct view *view, enum request request, struct line *line)
6105         struct status *status = line->data;
6107         switch (request) {
6108         case REQ_STATUS_UPDATE:
6109                 if (!status_update(view))
6110                         return REQ_NONE;
6111                 break;
6113         case REQ_STATUS_REVERT:
6114                 if (!status_revert(status, line->type, status_has_none(view, line)))
6115                         return REQ_NONE;
6116                 break;
6118         case REQ_STATUS_MERGE:
6119                 if (!status || status->status != 'U') {
6120                         report("Merging only possible for files with unmerged status ('U').");
6121                         return REQ_NONE;
6122                 }
6123                 open_mergetool(status->new.name);
6124                 break;
6126         case REQ_EDIT:
6127                 if (!status)
6128                         return request;
6129                 if (status->status == 'D') {
6130                         report("File has been deleted.");
6131                         return REQ_NONE;
6132                 }
6134                 open_editor(status->new.name);
6135                 break;
6137         case REQ_VIEW_BLAME:
6138                 if (status)
6139                         opt_ref[0] = 0;
6140                 return request;
6142         case REQ_ENTER:
6143                 /* After returning the status view has been split to
6144                  * show the stage view. No further reloading is
6145                  * necessary. */
6146                 return status_enter(view, line);
6148         case REQ_REFRESH:
6149                 /* Simply reload the view. */
6150                 break;
6152         default:
6153                 return request;
6154         }
6156         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
6158         return REQ_NONE;
6161 static void
6162 status_select(struct view *view, struct line *line)
6164         struct status *status = line->data;
6165         char file[SIZEOF_STR] = "all files";
6166         const char *text;
6167         const char *key;
6169         if (status && !string_format(file, "'%s'", status->new.name))
6170                 return;
6172         if (!status && line[1].type == LINE_STAT_NONE)
6173                 line++;
6175         switch (line->type) {
6176         case LINE_STAT_STAGED:
6177                 text = "Press %s to unstage %s for commit";
6178                 break;
6180         case LINE_STAT_UNSTAGED:
6181                 text = "Press %s to stage %s for commit";
6182                 break;
6184         case LINE_STAT_UNTRACKED:
6185                 text = "Press %s to stage %s for addition";
6186                 break;
6188         case LINE_STAT_HEAD:
6189         case LINE_STAT_NONE:
6190                 text = "Nothing to update";
6191                 break;
6193         default:
6194                 die("line type %d not handled in switch", line->type);
6195         }
6197         if (status && status->status == 'U') {
6198                 text = "Press %s to resolve conflict in %s";
6199                 key = get_key(KEYMAP_STATUS, REQ_STATUS_MERGE);
6201         } else {
6202                 key = get_key(KEYMAP_STATUS, REQ_STATUS_UPDATE);
6203         }
6205         string_format(view->ref, text, key, file);
6206         if (status)
6207                 string_copy(opt_file, status->new.name);
6210 static bool
6211 status_grep(struct view *view, struct line *line)
6213         struct status *status = line->data;
6215         if (status) {
6216                 const char buf[2] = { status->status, 0 };
6217                 const char *text[] = { status->new.name, buf, NULL };
6219                 return grep_text(view, text);
6220         }
6222         return FALSE;
6225 static struct view_ops status_ops = {
6226         "file",
6227         NULL,
6228         status_open,
6229         NULL,
6230         status_draw,
6231         status_request,
6232         status_grep,
6233         status_select,
6234 };
6237 static bool
6238 stage_diff_write(struct io *io, struct line *line, struct line *end)
6240         while (line < end) {
6241                 if (!io_write(io, line->data, strlen(line->data)) ||
6242                     !io_write(io, "\n", 1))
6243                         return FALSE;
6244                 line++;
6245                 if (line->type == LINE_DIFF_CHUNK ||
6246                     line->type == LINE_DIFF_HEADER)
6247                         break;
6248         }
6250         return TRUE;
6253 static struct line *
6254 stage_diff_find(struct view *view, struct line *line, enum line_type type)
6256         for (; view->line < line; line--)
6257                 if (line->type == type)
6258                         return line;
6260         return NULL;
6263 static bool
6264 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
6266         const char *apply_argv[SIZEOF_ARG] = {
6267                 "git", "apply", "--whitespace=nowarn", NULL
6268         };
6269         struct line *diff_hdr;
6270         struct io io;
6271         int argc = 3;
6273         diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
6274         if (!diff_hdr)
6275                 return FALSE;
6277         if (!revert)
6278                 apply_argv[argc++] = "--cached";
6279         if (revert || stage_line_type == LINE_STAT_STAGED)
6280                 apply_argv[argc++] = "-R";
6281         apply_argv[argc++] = "-";
6282         apply_argv[argc++] = NULL;
6283         if (!io_run(&io, IO_WR, opt_cdup, apply_argv))
6284                 return FALSE;
6286         if (!stage_diff_write(&io, diff_hdr, chunk) ||
6287             !stage_diff_write(&io, chunk, view->line + view->lines))
6288                 chunk = NULL;
6290         io_done(&io);
6291         io_run_bg(update_index_argv);
6293         return chunk ? TRUE : FALSE;
6296 static bool
6297 stage_update(struct view *view, struct line *line)
6299         struct line *chunk = NULL;
6301         if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
6302                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6304         if (chunk) {
6305                 if (!stage_apply_chunk(view, chunk, FALSE)) {
6306                         report("Failed to apply chunk");
6307                         return FALSE;
6308                 }
6310         } else if (!stage_status.status) {
6311                 view = VIEW(REQ_VIEW_STATUS);
6313                 for (line = view->line; line < view->line + view->lines; line++)
6314                         if (line->type == stage_line_type)
6315                                 break;
6317                 if (!status_update_files(view, line + 1)) {
6318                         report("Failed to update files");
6319                         return FALSE;
6320                 }
6322         } else if (!status_update_file(&stage_status, stage_line_type)) {
6323                 report("Failed to update file");
6324                 return FALSE;
6325         }
6327         return TRUE;
6330 static bool
6331 stage_revert(struct view *view, struct line *line)
6333         struct line *chunk = NULL;
6335         if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
6336                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6338         if (chunk) {
6339                 if (!prompt_yesno("Are you sure you want to revert changes?"))
6340                         return FALSE;
6342                 if (!stage_apply_chunk(view, chunk, TRUE)) {
6343                         report("Failed to revert chunk");
6344                         return FALSE;
6345                 }
6346                 return TRUE;
6348         } else {
6349                 return status_revert(stage_status.status ? &stage_status : NULL,
6350                                      stage_line_type, FALSE);
6351         }
6355 static void
6356 stage_next(struct view *view, struct line *line)
6358         int i;
6360         if (!stage_chunks) {
6361                 for (line = view->line; line < view->line + view->lines; line++) {
6362                         if (line->type != LINE_DIFF_CHUNK)
6363                                 continue;
6365                         if (!realloc_ints(&stage_chunk, stage_chunks, 1)) {
6366                                 report("Allocation failure");
6367                                 return;
6368                         }
6370                         stage_chunk[stage_chunks++] = line - view->line;
6371                 }
6372         }
6374         for (i = 0; i < stage_chunks; i++) {
6375                 if (stage_chunk[i] > view->lineno) {
6376                         do_scroll_view(view, stage_chunk[i] - view->lineno);
6377                         report("Chunk %d of %d", i + 1, stage_chunks);
6378                         return;
6379                 }
6380         }
6382         report("No next chunk found");
6385 static enum request
6386 stage_request(struct view *view, enum request request, struct line *line)
6388         switch (request) {
6389         case REQ_STATUS_UPDATE:
6390                 if (!stage_update(view, line))
6391                         return REQ_NONE;
6392                 break;
6394         case REQ_STATUS_REVERT:
6395                 if (!stage_revert(view, line))
6396                         return REQ_NONE;
6397                 break;
6399         case REQ_STAGE_NEXT:
6400                 if (stage_line_type == LINE_STAT_UNTRACKED) {
6401                         report("File is untracked; press %s to add",
6402                                get_key(KEYMAP_STAGE, REQ_STATUS_UPDATE));
6403                         return REQ_NONE;
6404                 }
6405                 stage_next(view, line);
6406                 return REQ_NONE;
6408         case REQ_EDIT:
6409                 if (!stage_status.new.name[0])
6410                         return request;
6411                 if (stage_status.status == 'D') {
6412                         report("File has been deleted.");
6413                         return REQ_NONE;
6414                 }
6416                 open_editor(stage_status.new.name);
6417                 break;
6419         case REQ_REFRESH:
6420                 /* Reload everything ... */
6421                 break;
6423         case REQ_VIEW_BLAME:
6424                 if (stage_status.new.name[0]) {
6425                         string_copy(opt_file, stage_status.new.name);
6426                         opt_ref[0] = 0;
6427                 }
6428                 return request;
6430         case REQ_ENTER:
6431                 return pager_request(view, request, line);
6433         default:
6434                 return request;
6435         }
6437         VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
6438         open_view(view, REQ_VIEW_STATUS, OPEN_REFRESH);
6440         /* Check whether the staged entry still exists, and close the
6441          * stage view if it doesn't. */
6442         if (!status_exists(&stage_status, stage_line_type)) {
6443                 status_restore(VIEW(REQ_VIEW_STATUS));
6444                 return REQ_VIEW_CLOSE;
6445         }
6447         if (stage_line_type == LINE_STAT_UNTRACKED) {
6448                 if (!suffixcmp(stage_status.new.name, -1, "/")) {
6449                         report("Cannot display a directory");
6450                         return REQ_NONE;
6451                 }
6453                 if (!prepare_update_file(view, stage_status.new.name)) {
6454                         report("Failed to open file: %s", strerror(errno));
6455                         return REQ_NONE;
6456                 }
6457         }
6458         open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
6460         return REQ_NONE;
6463 static struct view_ops stage_ops = {
6464         "line",
6465         NULL,
6466         NULL,
6467         pager_read,
6468         pager_draw,
6469         stage_request,
6470         pager_grep,
6471         pager_select,
6472 };
6475 /*
6476  * Revision graph
6477  */
6479 struct commit {
6480         char id[SIZEOF_REV];            /* SHA1 ID. */
6481         char title[128];                /* First line of the commit message. */
6482         const char *author;             /* Author of the commit. */
6483         struct time time;               /* Date from the author ident. */
6484         struct ref_list *refs;          /* Repository references. */
6485         chtype graph[SIZEOF_REVGRAPH];  /* Ancestry chain graphics. */
6486         size_t graph_size;              /* The width of the graph array. */
6487         bool has_parents;               /* Rewritten --parents seen. */
6488 };
6490 /* Size of rev graph with no  "padding" columns */
6491 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
6493 struct rev_graph {
6494         struct rev_graph *prev, *next, *parents;
6495         char rev[SIZEOF_REVITEMS][SIZEOF_REV];
6496         size_t size;
6497         struct commit *commit;
6498         size_t pos;
6499         unsigned int boundary:1;
6500 };
6502 /* Parents of the commit being visualized. */
6503 static struct rev_graph graph_parents[4];
6505 /* The current stack of revisions on the graph. */
6506 static struct rev_graph graph_stacks[4] = {
6507         { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
6508         { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
6509         { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
6510         { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
6511 };
6513 static inline bool
6514 graph_parent_is_merge(struct rev_graph *graph)
6516         return graph->parents->size > 1;
6519 static inline void
6520 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
6522         struct commit *commit = graph->commit;
6524         if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
6525                 commit->graph[commit->graph_size++] = symbol;
6528 static void
6529 clear_rev_graph(struct rev_graph *graph)
6531         graph->boundary = 0;
6532         graph->size = graph->pos = 0;
6533         graph->commit = NULL;
6534         memset(graph->parents, 0, sizeof(*graph->parents));
6537 static void
6538 done_rev_graph(struct rev_graph *graph)
6540         if (graph_parent_is_merge(graph) &&
6541             graph->pos < graph->size - 1 &&
6542             graph->next->size == graph->size + graph->parents->size - 1) {
6543                 size_t i = graph->pos + graph->parents->size - 1;
6545                 graph->commit->graph_size = i * 2;
6546                 while (i < graph->next->size - 1) {
6547                         append_to_rev_graph(graph, ' ');
6548                         append_to_rev_graph(graph, '\\');
6549                         i++;
6550                 }
6551         }
6553         clear_rev_graph(graph);
6556 static void
6557 push_rev_graph(struct rev_graph *graph, const char *parent)
6559         int i;
6561         /* "Collapse" duplicate parents lines.
6562          *
6563          * FIXME: This needs to also update update the drawn graph but
6564          * for now it just serves as a method for pruning graph lines. */
6565         for (i = 0; i < graph->size; i++)
6566                 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
6567                         return;
6569         if (graph->size < SIZEOF_REVITEMS) {
6570                 string_copy_rev(graph->rev[graph->size++], parent);
6571         }
6574 static chtype
6575 get_rev_graph_symbol(struct rev_graph *graph)
6577         chtype symbol;
6579         if (graph->boundary)
6580                 symbol = REVGRAPH_BOUND;
6581         else if (graph->parents->size == 0)
6582                 symbol = REVGRAPH_INIT;
6583         else if (graph_parent_is_merge(graph))
6584                 symbol = REVGRAPH_MERGE;
6585         else if (graph->pos >= graph->size)
6586                 symbol = REVGRAPH_BRANCH;
6587         else
6588                 symbol = REVGRAPH_COMMIT;
6590         return symbol;
6593 static void
6594 draw_rev_graph(struct rev_graph *graph)
6596         struct rev_filler {
6597                 chtype separator, line;
6598         };
6599         enum { DEFAULT, RSHARP, RDIAG, LDIAG };
6600         static struct rev_filler fillers[] = {
6601                 { ' ',  '|' },
6602                 { '`',  '.' },
6603                 { '\'', ' ' },
6604                 { '/',  ' ' },
6605         };
6606         chtype symbol = get_rev_graph_symbol(graph);
6607         struct rev_filler *filler;
6608         size_t i;
6610         fillers[DEFAULT].line = opt_line_graphics ? ACS_VLINE : '|';
6611         filler = &fillers[DEFAULT];
6613         for (i = 0; i < graph->pos; i++) {
6614                 append_to_rev_graph(graph, filler->line);
6615                 if (graph_parent_is_merge(graph->prev) &&
6616                     graph->prev->pos == i)
6617                         filler = &fillers[RSHARP];
6619                 append_to_rev_graph(graph, filler->separator);
6620         }
6622         /* Place the symbol for this revision. */
6623         append_to_rev_graph(graph, symbol);
6625         if (graph->prev->size > graph->size)
6626                 filler = &fillers[RDIAG];
6627         else
6628                 filler = &fillers[DEFAULT];
6630         i++;
6632         for (; i < graph->size; i++) {
6633                 append_to_rev_graph(graph, filler->separator);
6634                 append_to_rev_graph(graph, filler->line);
6635                 if (graph_parent_is_merge(graph->prev) &&
6636                     i < graph->prev->pos + graph->parents->size)
6637                         filler = &fillers[RSHARP];
6638                 if (graph->prev->size > graph->size)
6639                         filler = &fillers[LDIAG];
6640         }
6642         if (graph->prev->size > graph->size) {
6643                 append_to_rev_graph(graph, filler->separator);
6644                 if (filler->line != ' ')
6645                         append_to_rev_graph(graph, filler->line);
6646         }
6649 /* Prepare the next rev graph */
6650 static void
6651 prepare_rev_graph(struct rev_graph *graph)
6653         size_t i;
6655         /* First, traverse all lines of revisions up to the active one. */
6656         for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
6657                 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
6658                         break;
6660                 push_rev_graph(graph->next, graph->rev[graph->pos]);
6661         }
6663         /* Interleave the new revision parent(s). */
6664         for (i = 0; !graph->boundary && i < graph->parents->size; i++)
6665                 push_rev_graph(graph->next, graph->parents->rev[i]);
6667         /* Lastly, put any remaining revisions. */
6668         for (i = graph->pos + 1; i < graph->size; i++)
6669                 push_rev_graph(graph->next, graph->rev[i]);
6672 static void
6673 update_rev_graph(struct view *view, struct rev_graph *graph)
6675         /* If this is the finalizing update ... */
6676         if (graph->commit)
6677                 prepare_rev_graph(graph);
6679         /* Graph visualization needs a one rev look-ahead,
6680          * so the first update doesn't visualize anything. */
6681         if (!graph->prev->commit)
6682                 return;
6684         if (view->lines > 2)
6685                 view->line[view->lines - 3].dirty = 1;
6686         if (view->lines > 1)
6687                 view->line[view->lines - 2].dirty = 1;
6688         draw_rev_graph(graph->prev);
6689         done_rev_graph(graph->prev->prev);
6693 /*
6694  * Main view backend
6695  */
6697 static const char *main_argv[SIZEOF_ARG] = {
6698         "git", "log", "--no-color", "--pretty=raw", "--parents",
6699                       "--topo-order", "%(head)", NULL
6700 };
6702 static bool
6703 main_draw(struct view *view, struct line *line, unsigned int lineno)
6705         struct commit *commit = line->data;
6707         if (!commit->author)
6708                 return FALSE;
6710         if (opt_date && draw_date(view, &commit->time))
6711                 return TRUE;
6713         if (opt_author && draw_author(view, commit->author))
6714                 return TRUE;
6716         if (opt_rev_graph && commit->graph_size &&
6717             draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
6718                 return TRUE;
6720         if (opt_show_refs && commit->refs) {
6721                 size_t i;
6723                 for (i = 0; i < commit->refs->size; i++) {
6724                         struct ref *ref = commit->refs->refs[i];
6725                         enum line_type type;
6727                         if (ref->head)
6728                                 type = LINE_MAIN_HEAD;
6729                         else if (ref->ltag)
6730                                 type = LINE_MAIN_LOCAL_TAG;
6731                         else if (ref->tag)
6732                                 type = LINE_MAIN_TAG;
6733                         else if (ref->tracked)
6734                                 type = LINE_MAIN_TRACKED;
6735                         else if (ref->remote)
6736                                 type = LINE_MAIN_REMOTE;
6737                         else
6738                                 type = LINE_MAIN_REF;
6740                         if (draw_text(view, type, "[", TRUE) ||
6741                             draw_text(view, type, ref->name, TRUE) ||
6742                             draw_text(view, type, "]", TRUE))
6743                                 return TRUE;
6745                         if (draw_text(view, LINE_DEFAULT, " ", TRUE))
6746                                 return TRUE;
6747                 }
6748         }
6750         draw_text(view, LINE_DEFAULT, commit->title, TRUE);
6751         return TRUE;
6754 /* Reads git log --pretty=raw output and parses it into the commit struct. */
6755 static bool
6756 main_read(struct view *view, char *line)
6758         static struct rev_graph *graph = graph_stacks;
6759         enum line_type type;
6760         struct commit *commit;
6762         if (!line) {
6763                 int i;
6765                 if (!view->lines && !view->prev)
6766                         die("No revisions match the given arguments.");
6767                 if (view->lines > 0) {
6768                         commit = view->line[view->lines - 1].data;
6769                         view->line[view->lines - 1].dirty = 1;
6770                         if (!commit->author) {
6771                                 view->lines--;
6772                                 free(commit);
6773                                 graph->commit = NULL;
6774                         }
6775                 }
6776                 update_rev_graph(view, graph);
6778                 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
6779                         clear_rev_graph(&graph_stacks[i]);
6780                 return TRUE;
6781         }
6783         type = get_line_type(line);
6784         if (type == LINE_COMMIT) {
6785                 commit = calloc(1, sizeof(struct commit));
6786                 if (!commit)
6787                         return FALSE;
6789                 line += STRING_SIZE("commit ");
6790                 if (*line == '-') {
6791                         graph->boundary = 1;
6792                         line++;
6793                 }
6795                 string_copy_rev(commit->id, line);
6796                 commit->refs = get_ref_list(commit->id);
6797                 graph->commit = commit;
6798                 add_line_data(view, commit, LINE_MAIN_COMMIT);
6800                 while ((line = strchr(line, ' '))) {
6801                         line++;
6802                         push_rev_graph(graph->parents, line);
6803                         commit->has_parents = TRUE;
6804                 }
6805                 return TRUE;
6806         }
6808         if (!view->lines)
6809                 return TRUE;
6810         commit = view->line[view->lines - 1].data;
6812         switch (type) {
6813         case LINE_PARENT:
6814                 if (commit->has_parents)
6815                         break;
6816                 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
6817                 break;
6819         case LINE_AUTHOR:
6820                 parse_author_line(line + STRING_SIZE("author "),
6821                                   &commit->author, &commit->time);
6822                 update_rev_graph(view, graph);
6823                 graph = graph->next;
6824                 break;
6826         default:
6827                 /* Fill in the commit title if it has not already been set. */
6828                 if (commit->title[0])
6829                         break;
6831                 /* Require titles to start with a non-space character at the
6832                  * offset used by git log. */
6833                 if (strncmp(line, "    ", 4))
6834                         break;
6835                 line += 4;
6836                 /* Well, if the title starts with a whitespace character,
6837                  * try to be forgiving.  Otherwise we end up with no title. */
6838                 while (isspace(*line))
6839                         line++;
6840                 if (*line == '\0')
6841                         break;
6842                 /* FIXME: More graceful handling of titles; append "..." to
6843                  * shortened titles, etc. */
6845                 string_expand(commit->title, sizeof(commit->title), line, 1);
6846                 view->line[view->lines - 1].dirty = 1;
6847         }
6849         return TRUE;
6852 static enum request
6853 main_request(struct view *view, enum request request, struct line *line)
6855         enum open_flags flags = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
6857         switch (request) {
6858         case REQ_ENTER:
6859                 open_view(view, REQ_VIEW_DIFF, flags);
6860                 break;
6861         case REQ_REFRESH:
6862                 load_refs();
6863                 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
6864                 break;
6865         default:
6866                 return request;
6867         }
6869         return REQ_NONE;
6872 static bool
6873 grep_refs(struct ref_list *list, regex_t *regex)
6875         regmatch_t pmatch;
6876         size_t i;
6878         if (!opt_show_refs || !list)
6879                 return FALSE;
6881         for (i = 0; i < list->size; i++) {
6882                 if (regexec(regex, list->refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
6883                         return TRUE;
6884         }
6886         return FALSE;
6889 static bool
6890 main_grep(struct view *view, struct line *line)
6892         struct commit *commit = line->data;
6893         const char *text[] = {
6894                 commit->title,
6895                 opt_author ? commit->author : "",
6896                 mkdate(&commit->time, opt_date),
6897                 NULL
6898         };
6900         return grep_text(view, text) || grep_refs(commit->refs, view->regex);
6903 static void
6904 main_select(struct view *view, struct line *line)
6906         struct commit *commit = line->data;
6908         string_copy_rev(view->ref, commit->id);
6909         string_copy_rev(ref_commit, view->ref);
6912 static struct view_ops main_ops = {
6913         "commit",
6914         main_argv,
6915         NULL,
6916         main_read,
6917         main_draw,
6918         main_request,
6919         main_grep,
6920         main_select,
6921 };
6924 /*
6925  * Status management
6926  */
6928 /* Whether or not the curses interface has been initialized. */
6929 static bool cursed = FALSE;
6931 /* Terminal hacks and workarounds. */
6932 static bool use_scroll_redrawwin;
6933 static bool use_scroll_status_wclear;
6935 /* The status window is used for polling keystrokes. */
6936 static WINDOW *status_win;
6938 /* Reading from the prompt? */
6939 static bool input_mode = FALSE;
6941 static bool status_empty = FALSE;
6943 /* Update status and title window. */
6944 static void
6945 report(const char *msg, ...)
6947         struct view *view = display[current_view];
6949         if (input_mode)
6950                 return;
6952         if (!view) {
6953                 char buf[SIZEOF_STR];
6954                 va_list args;
6956                 va_start(args, msg);
6957                 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
6958                         buf[sizeof(buf) - 1] = 0;
6959                         buf[sizeof(buf) - 2] = '.';
6960                         buf[sizeof(buf) - 3] = '.';
6961                         buf[sizeof(buf) - 4] = '.';
6962                 }
6963                 va_end(args);
6964                 die("%s", buf);
6965         }
6967         if (!status_empty || *msg) {
6968                 va_list args;
6970                 va_start(args, msg);
6972                 wmove(status_win, 0, 0);
6973                 if (view->has_scrolled && use_scroll_status_wclear)
6974                         wclear(status_win);
6975                 if (*msg) {
6976                         vwprintw(status_win, msg, args);
6977                         status_empty = FALSE;
6978                 } else {
6979                         status_empty = TRUE;
6980                 }
6981                 wclrtoeol(status_win);
6982                 wnoutrefresh(status_win);
6984                 va_end(args);
6985         }
6987         update_view_title(view);
6990 static void
6991 init_display(void)
6993         const char *term;
6994         int x, y;
6996         /* Initialize the curses library */
6997         if (isatty(STDIN_FILENO)) {
6998                 cursed = !!initscr();
6999                 opt_tty = stdin;
7000         } else {
7001                 /* Leave stdin and stdout alone when acting as a pager. */
7002                 opt_tty = fopen("/dev/tty", "r+");
7003                 if (!opt_tty)
7004                         die("Failed to open /dev/tty");
7005                 cursed = !!newterm(NULL, opt_tty, opt_tty);
7006         }
7008         if (!cursed)
7009                 die("Failed to initialize curses");
7011         nonl();         /* Disable conversion and detect newlines from input. */
7012         cbreak();       /* Take input chars one at a time, no wait for \n */
7013         noecho();       /* Don't echo input */
7014         leaveok(stdscr, FALSE);
7016         if (has_colors())
7017                 init_colors();
7019         getmaxyx(stdscr, y, x);
7020         status_win = newwin(1, 0, y - 1, 0);
7021         if (!status_win)
7022                 die("Failed to create status window");
7024         /* Enable keyboard mapping */
7025         keypad(status_win, TRUE);
7026         wbkgdset(status_win, get_line_attr(LINE_STATUS));
7028         TABSIZE = opt_tab_size;
7030         term = getenv("XTERM_VERSION") ? NULL : getenv("COLORTERM");
7031         if (term && !strcmp(term, "gnome-terminal")) {
7032                 /* In the gnome-terminal-emulator, the message from
7033                  * scrolling up one line when impossible followed by
7034                  * scrolling down one line causes corruption of the
7035                  * status line. This is fixed by calling wclear. */
7036                 use_scroll_status_wclear = TRUE;
7037                 use_scroll_redrawwin = FALSE;
7039         } else if (term && !strcmp(term, "xrvt-xpm")) {
7040                 /* No problems with full optimizations in xrvt-(unicode)
7041                  * and aterm. */
7042                 use_scroll_status_wclear = use_scroll_redrawwin = FALSE;
7044         } else {
7045                 /* When scrolling in (u)xterm the last line in the
7046                  * scrolling direction will update slowly. */
7047                 use_scroll_redrawwin = TRUE;
7048                 use_scroll_status_wclear = FALSE;
7049         }
7052 static int
7053 get_input(int prompt_position)
7055         struct view *view;
7056         int i, key, cursor_y, cursor_x;
7057         bool loading = FALSE;
7059         if (prompt_position)
7060                 input_mode = TRUE;
7062         while (TRUE) {
7063                 foreach_view (view, i) {
7064                         update_view(view);
7065                         if (view_is_displayed(view) && view->has_scrolled &&
7066                             use_scroll_redrawwin)
7067                                 redrawwin(view->win);
7068                         view->has_scrolled = FALSE;
7069                         if (view->pipe)
7070                                 loading = TRUE;
7071                 }
7073                 /* Update the cursor position. */
7074                 if (prompt_position) {
7075                         getbegyx(status_win, cursor_y, cursor_x);
7076                         cursor_x = prompt_position;
7077                 } else {
7078                         view = display[current_view];
7079                         getbegyx(view->win, cursor_y, cursor_x);
7080                         cursor_x = view->width - 1;
7081                         cursor_y += view->lineno - view->offset;
7082                 }
7083                 setsyx(cursor_y, cursor_x);
7085                 /* Refresh, accept single keystroke of input */
7086                 doupdate();
7087                 nodelay(status_win, loading);
7088                 key = wgetch(status_win);
7090                 /* wgetch() with nodelay() enabled returns ERR when
7091                  * there's no input. */
7092                 if (key == ERR) {
7094                 } else if (key == KEY_RESIZE) {
7095                         int height, width;
7097                         getmaxyx(stdscr, height, width);
7099                         wresize(status_win, 1, width);
7100                         mvwin(status_win, height - 1, 0);
7101                         wnoutrefresh(status_win);
7102                         resize_display();
7103                         redraw_display(TRUE);
7105                 } else {
7106                         input_mode = FALSE;
7107                         return key;
7108                 }
7109         }
7112 static char *
7113 prompt_input(const char *prompt, input_handler handler, void *data)
7115         enum input_status status = INPUT_OK;
7116         static char buf[SIZEOF_STR];
7117         size_t pos = 0;
7119         buf[pos] = 0;
7121         while (status == INPUT_OK || status == INPUT_SKIP) {
7122                 int key;
7124                 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
7125                 wclrtoeol(status_win);
7127                 key = get_input(pos + 1);
7128                 switch (key) {
7129                 case KEY_RETURN:
7130                 case KEY_ENTER:
7131                 case '\n':
7132                         status = pos ? INPUT_STOP : INPUT_CANCEL;
7133                         break;
7135                 case KEY_BACKSPACE:
7136                         if (pos > 0)
7137                                 buf[--pos] = 0;
7138                         else
7139                                 status = INPUT_CANCEL;
7140                         break;
7142                 case KEY_ESC:
7143                         status = INPUT_CANCEL;
7144                         break;
7146                 default:
7147                         if (pos >= sizeof(buf)) {
7148                                 report("Input string too long");
7149                                 return NULL;
7150                         }
7152                         status = handler(data, buf, key);
7153                         if (status == INPUT_OK)
7154                                 buf[pos++] = (char) key;
7155                 }
7156         }
7158         /* Clear the status window */
7159         status_empty = FALSE;
7160         report("");
7162         if (status == INPUT_CANCEL)
7163                 return NULL;
7165         buf[pos++] = 0;
7167         return buf;
7170 static enum input_status
7171 prompt_yesno_handler(void *data, char *buf, int c)
7173         if (c == 'y' || c == 'Y')
7174                 return INPUT_STOP;
7175         if (c == 'n' || c == 'N')
7176                 return INPUT_CANCEL;
7177         return INPUT_SKIP;
7180 static bool
7181 prompt_yesno(const char *prompt)
7183         char prompt2[SIZEOF_STR];
7185         if (!string_format(prompt2, "%s [Yy/Nn]", prompt))
7186                 return FALSE;
7188         return !!prompt_input(prompt2, prompt_yesno_handler, NULL);
7191 static enum input_status
7192 read_prompt_handler(void *data, char *buf, int c)
7194         return isprint(c) ? INPUT_OK : INPUT_SKIP;
7197 static char *
7198 read_prompt(const char *prompt)
7200         return prompt_input(prompt, read_prompt_handler, NULL);
7203 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected)
7205         enum input_status status = INPUT_OK;
7206         int size = 0;
7208         while (items[size].text)
7209                 size++;
7211         while (status == INPUT_OK) {
7212                 const struct menu_item *item = &items[*selected];
7213                 int key;
7214                 int i;
7216                 mvwprintw(status_win, 0, 0, "%s (%d of %d) ",
7217                           prompt, *selected + 1, size);
7218                 if (item->hotkey)
7219                         wprintw(status_win, "[%c] ", (char) item->hotkey);
7220                 wprintw(status_win, "%s", item->text);
7221                 wclrtoeol(status_win);
7223                 key = get_input(COLS - 1);
7224                 switch (key) {
7225                 case KEY_RETURN:
7226                 case KEY_ENTER:
7227                 case '\n':
7228                         status = INPUT_STOP;
7229                         break;
7231                 case KEY_LEFT:
7232                 case KEY_UP:
7233                         *selected = *selected - 1;
7234                         if (*selected < 0)
7235                                 *selected = size - 1;
7236                         break;
7238                 case KEY_RIGHT:
7239                 case KEY_DOWN:
7240                         *selected = (*selected + 1) % size;
7241                         break;
7243                 case KEY_ESC:
7244                         status = INPUT_CANCEL;
7245                         break;
7247                 default:
7248                         for (i = 0; items[i].text; i++)
7249                                 if (items[i].hotkey == key) {
7250                                         *selected = i;
7251                                         status = INPUT_STOP;
7252                                         break;
7253                                 }
7254                 }
7255         }
7257         /* Clear the status window */
7258         status_empty = FALSE;
7259         report("");
7261         return status != INPUT_CANCEL;
7264 /*
7265  * Repository properties
7266  */
7268 static struct ref **refs = NULL;
7269 static size_t refs_size = 0;
7270 static struct ref *refs_head = NULL;
7272 static struct ref_list **ref_lists = NULL;
7273 static size_t ref_lists_size = 0;
7275 DEFINE_ALLOCATOR(realloc_refs, struct ref *, 256)
7276 DEFINE_ALLOCATOR(realloc_refs_list, struct ref *, 8)
7277 DEFINE_ALLOCATOR(realloc_ref_lists, struct ref_list *, 8)
7279 static int
7280 compare_refs(const void *ref1_, const void *ref2_)
7282         const struct ref *ref1 = *(const struct ref **)ref1_;
7283         const struct ref *ref2 = *(const struct ref **)ref2_;
7285         if (ref1->tag != ref2->tag)
7286                 return ref2->tag - ref1->tag;
7287         if (ref1->ltag != ref2->ltag)
7288                 return ref2->ltag - ref2->ltag;
7289         if (ref1->head != ref2->head)
7290                 return ref2->head - ref1->head;
7291         if (ref1->tracked != ref2->tracked)
7292                 return ref2->tracked - ref1->tracked;
7293         if (ref1->remote != ref2->remote)
7294                 return ref2->remote - ref1->remote;
7295         return strcmp(ref1->name, ref2->name);
7298 static void
7299 foreach_ref(bool (*visitor)(void *data, const struct ref *ref), void *data)
7301         size_t i;
7303         for (i = 0; i < refs_size; i++)
7304                 if (!visitor(data, refs[i]))
7305                         break;
7308 static struct ref *
7309 get_ref_head()
7311         return refs_head;
7314 static struct ref_list *
7315 get_ref_list(const char *id)
7317         struct ref_list *list;
7318         size_t i;
7320         for (i = 0; i < ref_lists_size; i++)
7321                 if (!strcmp(id, ref_lists[i]->id))
7322                         return ref_lists[i];
7324         if (!realloc_ref_lists(&ref_lists, ref_lists_size, 1))
7325                 return NULL;
7326         list = calloc(1, sizeof(*list));
7327         if (!list)
7328                 return NULL;
7330         for (i = 0; i < refs_size; i++) {
7331                 if (!strcmp(id, refs[i]->id) &&
7332                     realloc_refs_list(&list->refs, list->size, 1))
7333                         list->refs[list->size++] = refs[i];
7334         }
7336         if (!list->refs) {
7337                 free(list);
7338                 return NULL;
7339         }
7341         qsort(list->refs, list->size, sizeof(*list->refs), compare_refs);
7342         ref_lists[ref_lists_size++] = list;
7343         return list;
7346 static int
7347 read_ref(char *id, size_t idlen, char *name, size_t namelen)
7349         struct ref *ref = NULL;
7350         bool tag = FALSE;
7351         bool ltag = FALSE;
7352         bool remote = FALSE;
7353         bool tracked = FALSE;
7354         bool head = FALSE;
7355         int from = 0, to = refs_size - 1;
7357         if (!prefixcmp(name, "refs/tags/")) {
7358                 if (!suffixcmp(name, namelen, "^{}")) {
7359                         namelen -= 3;
7360                         name[namelen] = 0;
7361                 } else {
7362                         ltag = TRUE;
7363                 }
7365                 tag = TRUE;
7366                 namelen -= STRING_SIZE("refs/tags/");
7367                 name    += STRING_SIZE("refs/tags/");
7369         } else if (!prefixcmp(name, "refs/remotes/")) {
7370                 remote = TRUE;
7371                 namelen -= STRING_SIZE("refs/remotes/");
7372                 name    += STRING_SIZE("refs/remotes/");
7373                 tracked  = !strcmp(opt_remote, name);
7375         } else if (!prefixcmp(name, "refs/heads/")) {
7376                 namelen -= STRING_SIZE("refs/heads/");
7377                 name    += STRING_SIZE("refs/heads/");
7378                 if (!strncmp(opt_head, name, namelen))
7379                         return OK;
7381         } else if (!strcmp(name, "HEAD")) {
7382                 head     = TRUE;
7383                 if (*opt_head) {
7384                         namelen  = strlen(opt_head);
7385                         name     = opt_head;
7386                 }
7387         }
7389         /* If we are reloading or it's an annotated tag, replace the
7390          * previous SHA1 with the resolved commit id; relies on the fact
7391          * git-ls-remote lists the commit id of an annotated tag right
7392          * before the commit id it points to. */
7393         while (from <= to) {
7394                 size_t pos = (to + from) / 2;
7395                 int cmp = strcmp(name, refs[pos]->name);
7397                 if (!cmp) {
7398                         ref = refs[pos];
7399                         break;
7400                 }
7402                 if (cmp < 0)
7403                         to = pos - 1;
7404                 else
7405                         from = pos + 1;
7406         }
7408         if (!ref) {
7409                 if (!realloc_refs(&refs, refs_size, 1))
7410                         return ERR;
7411                 ref = calloc(1, sizeof(*ref) + namelen);
7412                 if (!ref)
7413                         return ERR;
7414                 memmove(refs + from + 1, refs + from,
7415                         (refs_size - from) * sizeof(*refs));
7416                 refs[from] = ref;
7417                 strncpy(ref->name, name, namelen);
7418                 refs_size++;
7419         }
7421         ref->head = head;
7422         ref->tag = tag;
7423         ref->ltag = ltag;
7424         ref->remote = remote;
7425         ref->tracked = tracked;
7426         string_copy_rev(ref->id, id);
7428         if (head)
7429                 refs_head = ref;
7430         return OK;
7433 static int
7434 load_refs(void)
7436         const char *head_argv[] = {
7437                 "git", "symbolic-ref", "HEAD", NULL
7438         };
7439         static const char *ls_remote_argv[SIZEOF_ARG] = {
7440                 "git", "ls-remote", opt_git_dir, NULL
7441         };
7442         static bool init = FALSE;
7443         size_t i;
7445         if (!init) {
7446                 if (!argv_from_env(ls_remote_argv, "TIG_LS_REMOTE"))
7447                         die("TIG_LS_REMOTE contains too many arguments");
7448                 init = TRUE;
7449         }
7451         if (!*opt_git_dir)
7452                 return OK;
7454         if (io_run_buf(head_argv, opt_head, sizeof(opt_head)) &&
7455             !prefixcmp(opt_head, "refs/heads/")) {
7456                 char *offset = opt_head + STRING_SIZE("refs/heads/");
7458                 memmove(opt_head, offset, strlen(offset) + 1);
7459         }
7461         refs_head = NULL;
7462         for (i = 0; i < refs_size; i++)
7463                 refs[i]->id[0] = 0;
7465         if (io_run_load(ls_remote_argv, "\t", read_ref) == ERR)
7466                 return ERR;
7468         /* Update the ref lists to reflect changes. */
7469         for (i = 0; i < ref_lists_size; i++) {
7470                 struct ref_list *list = ref_lists[i];
7471                 size_t old, new;
7473                 for (old = new = 0; old < list->size; old++)
7474                         if (!strcmp(list->id, list->refs[old]->id))
7475                                 list->refs[new++] = list->refs[old];
7476                 list->size = new;
7477         }
7479         return OK;
7482 static void
7483 set_remote_branch(const char *name, const char *value, size_t valuelen)
7485         if (!strcmp(name, ".remote")) {
7486                 string_ncopy(opt_remote, value, valuelen);
7488         } else if (*opt_remote && !strcmp(name, ".merge")) {
7489                 size_t from = strlen(opt_remote);
7491                 if (!prefixcmp(value, "refs/heads/"))
7492                         value += STRING_SIZE("refs/heads/");
7494                 if (!string_format_from(opt_remote, &from, "/%s", value))
7495                         opt_remote[0] = 0;
7496         }
7499 static void
7500 set_repo_config_option(char *name, char *value, int (*cmd)(int, const char **))
7502         const char *argv[SIZEOF_ARG] = { name, "=" };
7503         int argc = 1 + (cmd == option_set_command);
7504         int error = ERR;
7506         if (!argv_from_string(argv, &argc, value))
7507                 config_msg = "Too many option arguments";
7508         else
7509                 error = cmd(argc, argv);
7511         if (error == ERR)
7512                 warn("Option 'tig.%s': %s", name, config_msg);
7515 static bool
7516 set_environment_variable(const char *name, const char *value)
7518         size_t len = strlen(name) + 1 + strlen(value) + 1;
7519         char *env = malloc(len);
7521         if (env &&
7522             string_nformat(env, len, NULL, "%s=%s", name, value) &&
7523             putenv(env) == 0)
7524                 return TRUE;
7525         free(env);
7526         return FALSE;
7529 static void
7530 set_work_tree(const char *value)
7532         char cwd[SIZEOF_STR];
7534         if (!getcwd(cwd, sizeof(cwd)))
7535                 die("Failed to get cwd path: %s", strerror(errno));
7536         if (chdir(opt_git_dir) < 0)
7537                 die("Failed to chdir(%s): %s", strerror(errno));
7538         if (!getcwd(opt_git_dir, sizeof(opt_git_dir)))
7539                 die("Failed to get git path: %s", strerror(errno));
7540         if (chdir(cwd) < 0)
7541                 die("Failed to chdir(%s): %s", cwd, strerror(errno));
7542         if (chdir(value) < 0)
7543                 die("Failed to chdir(%s): %s", value, strerror(errno));
7544         if (!getcwd(cwd, sizeof(cwd)))
7545                 die("Failed to get cwd path: %s", strerror(errno));
7546         if (!set_environment_variable("GIT_WORK_TREE", cwd))
7547                 die("Failed to set GIT_WORK_TREE to '%s'", cwd);
7548         if (!set_environment_variable("GIT_DIR", opt_git_dir))
7549                 die("Failed to set GIT_DIR to '%s'", opt_git_dir);
7550         opt_is_inside_work_tree = TRUE;
7553 static int
7554 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
7556         if (!strcmp(name, "i18n.commitencoding"))
7557                 string_ncopy(opt_encoding, value, valuelen);
7559         else if (!strcmp(name, "core.editor"))
7560                 string_ncopy(opt_editor, value, valuelen);
7562         else if (!strcmp(name, "core.worktree"))
7563                 set_work_tree(value);
7565         else if (!prefixcmp(name, "tig.color."))
7566                 set_repo_config_option(name + 10, value, option_color_command);
7568         else if (!prefixcmp(name, "tig.bind."))
7569                 set_repo_config_option(name + 9, value, option_bind_command);
7571         else if (!prefixcmp(name, "tig."))
7572                 set_repo_config_option(name + 4, value, option_set_command);
7574         else if (*opt_head && !prefixcmp(name, "branch.") &&
7575                  !strncmp(name + 7, opt_head, strlen(opt_head)))
7576                 set_remote_branch(name + 7 + strlen(opt_head), value, valuelen);
7578         return OK;
7581 static int
7582 load_git_config(void)
7584         const char *config_list_argv[] = { "git", "config", "--list", NULL };
7586         return io_run_load(config_list_argv, "=", read_repo_config_option);
7589 static int
7590 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
7592         if (!opt_git_dir[0]) {
7593                 string_ncopy(opt_git_dir, name, namelen);
7595         } else if (opt_is_inside_work_tree == -1) {
7596                 /* This can be 3 different values depending on the
7597                  * version of git being used. If git-rev-parse does not
7598                  * understand --is-inside-work-tree it will simply echo
7599                  * the option else either "true" or "false" is printed.
7600                  * Default to true for the unknown case. */
7601                 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
7603         } else if (*name == '.') {
7604                 string_ncopy(opt_cdup, name, namelen);
7606         } else {
7607                 string_ncopy(opt_prefix, name, namelen);
7608         }
7610         return OK;
7613 static int
7614 load_repo_info(void)
7616         const char *rev_parse_argv[] = {
7617                 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
7618                         "--show-cdup", "--show-prefix", NULL
7619         };
7621         return io_run_load(rev_parse_argv, "=", read_repo_info);
7625 /*
7626  * Main
7627  */
7629 static const char usage[] =
7630 "tig " TIG_VERSION " (" __DATE__ ")\n"
7631 "\n"
7632 "Usage: tig        [options] [revs] [--] [paths]\n"
7633 "   or: tig show   [options] [revs] [--] [paths]\n"
7634 "   or: tig blame  [rev] path\n"
7635 "   or: tig status\n"
7636 "   or: tig <      [git command output]\n"
7637 "\n"
7638 "Options:\n"
7639 "  -v, --version   Show version and exit\n"
7640 "  -h, --help      Show help message and exit";
7642 static void __NORETURN
7643 quit(int sig)
7645         /* XXX: Restore tty modes and let the OS cleanup the rest! */
7646         if (cursed)
7647                 endwin();
7648         exit(0);
7651 static void __NORETURN
7652 die(const char *err, ...)
7654         va_list args;
7656         endwin();
7658         va_start(args, err);
7659         fputs("tig: ", stderr);
7660         vfprintf(stderr, err, args);
7661         fputs("\n", stderr);
7662         va_end(args);
7664         exit(1);
7667 static void
7668 warn(const char *msg, ...)
7670         va_list args;
7672         va_start(args, msg);
7673         fputs("tig warning: ", stderr);
7674         vfprintf(stderr, msg, args);
7675         fputs("\n", stderr);
7676         va_end(args);
7679 static enum request
7680 parse_options(int argc, const char *argv[])
7682         enum request request = REQ_VIEW_MAIN;
7683         const char *subcommand;
7684         bool seen_dashdash = FALSE;
7685         /* XXX: This is vulnerable to the user overriding options
7686          * required for the main view parser. */
7687         const char *custom_argv[SIZEOF_ARG] = {
7688                 "git", "log", "--no-color", "--pretty=raw", "--parents",
7689                         "--topo-order", NULL
7690         };
7691         int i, j = 6;
7693         if (!isatty(STDIN_FILENO)) {
7694                 io_open(&VIEW(REQ_VIEW_PAGER)->io, "");
7695                 return REQ_VIEW_PAGER;
7696         }
7698         if (argc <= 1)
7699                 return REQ_NONE;
7701         subcommand = argv[1];
7702         if (!strcmp(subcommand, "status")) {
7703                 if (argc > 2)
7704                         warn("ignoring arguments after `%s'", subcommand);
7705                 return REQ_VIEW_STATUS;
7707         } else if (!strcmp(subcommand, "blame")) {
7708                 if (argc <= 2 || argc > 4)
7709                         die("invalid number of options to blame\n\n%s", usage);
7711                 i = 2;
7712                 if (argc == 4) {
7713                         string_ncopy(opt_ref, argv[i], strlen(argv[i]));
7714                         i++;
7715                 }
7717                 string_ncopy(opt_file, argv[i], strlen(argv[i]));
7718                 return REQ_VIEW_BLAME;
7720         } else if (!strcmp(subcommand, "show")) {
7721                 request = REQ_VIEW_DIFF;
7723         } else {
7724                 subcommand = NULL;
7725         }
7727         if (subcommand) {
7728                 custom_argv[1] = subcommand;
7729                 j = 2;
7730         }
7732         for (i = 1 + !!subcommand; i < argc; i++) {
7733                 const char *opt = argv[i];
7735                 if (seen_dashdash || !strcmp(opt, "--")) {
7736                         seen_dashdash = TRUE;
7738                 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
7739                         printf("tig version %s\n", TIG_VERSION);
7740                         quit(0);
7742                 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
7743                         printf("%s\n", usage);
7744                         quit(0);
7745                 }
7747                 custom_argv[j++] = opt;
7748                 if (j >= ARRAY_SIZE(custom_argv))
7749                         die("command too long");
7750         }
7752         if (!prepare_update(VIEW(request), custom_argv, NULL))
7753                 die("Failed to format arguments");
7755         return request;
7758 int
7759 main(int argc, const char *argv[])
7761         const char *codeset = "UTF-8";
7762         enum request request = parse_options(argc, argv);
7763         struct view *view;
7764         size_t i;
7766         signal(SIGINT, quit);
7767         signal(SIGPIPE, SIG_IGN);
7769         if (setlocale(LC_ALL, "")) {
7770                 codeset = nl_langinfo(CODESET);
7771         }
7773         if (load_repo_info() == ERR)
7774                 die("Failed to load repo info.");
7776         if (load_options() == ERR)
7777                 die("Failed to load user config.");
7779         if (load_git_config() == ERR)
7780                 die("Failed to load repo config.");
7782         /* Require a git repository unless when running in pager mode. */
7783         if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
7784                 die("Not a git repository");
7786         if (*opt_encoding && strcmp(codeset, "UTF-8")) {
7787                 opt_iconv_in = iconv_open("UTF-8", opt_encoding);
7788                 if (opt_iconv_in == ICONV_NONE)
7789                         die("Failed to initialize character set conversion");
7790         }
7792         if (codeset && strcmp(codeset, "UTF-8")) {
7793                 opt_iconv_out = iconv_open(codeset, "UTF-8");
7794                 if (opt_iconv_out == ICONV_NONE)
7795                         die("Failed to initialize character set conversion");
7796         }
7798         if (load_refs() == ERR)
7799                 die("Failed to load refs.");
7801         foreach_view (view, i)
7802                 if (!argv_from_env(view->ops->argv, view->cmd_env))
7803                         die("Too many arguments in the `%s` environment variable",
7804                             view->cmd_env);
7806         init_display();
7808         if (request != REQ_NONE)
7809                 open_view(NULL, request, OPEN_PREPARED);
7810         request = request == REQ_NONE ? REQ_VIEW_MAIN : REQ_NONE;
7812         while (view_driver(display[current_view], request)) {
7813                 int key = get_input(0);
7815                 view = display[current_view];
7816                 request = get_keybinding(view->keymap, key);
7818                 /* Some low-level request handling. This keeps access to
7819                  * status_win restricted. */
7820                 switch (request) {
7821                 case REQ_NONE:
7822                         report("Unknown key, press %s for help",
7823                                get_key(view->keymap, REQ_VIEW_HELP));
7824                         break;
7825                 case REQ_PROMPT:
7826                 {
7827                         char *cmd = read_prompt(":");
7829                         if (cmd && isdigit(*cmd)) {
7830                                 int lineno = view->lineno + 1;
7832                                 if (parse_int(&lineno, cmd, 1, view->lines + 1) == OK) {
7833                                         select_view_line(view, lineno - 1);
7834                                         report("");
7835                                 } else {
7836                                         report("Unable to parse '%s' as a line number", cmd);
7837                                 }
7839                         } else if (cmd) {
7840                                 struct view *next = VIEW(REQ_VIEW_PAGER);
7841                                 const char *argv[SIZEOF_ARG] = { "git" };
7842                                 int argc = 1;
7844                                 /* When running random commands, initially show the
7845                                  * command in the title. However, it maybe later be
7846                                  * overwritten if a commit line is selected. */
7847                                 string_ncopy(next->ref, cmd, strlen(cmd));
7849                                 if (!argv_from_string(argv, &argc, cmd)) {
7850                                         report("Too many arguments");
7851                                 } else if (!prepare_update(next, argv, NULL)) {
7852                                         report("Failed to format command");
7853                                 } else {
7854                                         open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
7855                                 }
7856                         }
7858                         request = REQ_NONE;
7859                         break;
7860                 }
7861                 case REQ_SEARCH:
7862                 case REQ_SEARCH_BACK:
7863                 {
7864                         const char *prompt = request == REQ_SEARCH ? "/" : "?";
7865                         char *search = read_prompt(prompt);
7867                         if (search)
7868                                 string_ncopy(opt_search, search, strlen(search));
7869                         else if (*opt_search)
7870                                 request = request == REQ_SEARCH ?
7871                                         REQ_FIND_NEXT :
7872                                         REQ_FIND_PREV;
7873                         else
7874                                 request = REQ_NONE;
7875                         break;
7876                 }
7877                 default:
7878                         break;
7879                 }
7880         }
7882         quit(0);
7884         return 0;