Code

io: fix comment in io struct
[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, ...);
71 static size_t utf8_length(const char **start, size_t skip, int *width, size_t max_width, int *trimmed, bool reserve, int tab_size);
72 static inline unsigned char utf8_char_length(const char *string, const char *end);
74 #define ABS(x)          ((x) >= 0  ? (x) : -(x))
75 #define MIN(x, y)       ((x) < (y) ? (x) :  (y))
76 #define MAX(x, y)       ((x) > (y) ? (x) :  (y))
78 #define ARRAY_SIZE(x)   (sizeof(x) / sizeof(x[0]))
79 #define STRING_SIZE(x)  (sizeof(x) - 1)
81 #define SIZEOF_STR      1024    /* Default string size. */
82 #define SIZEOF_REF      256     /* Size of symbolic or SHA1 ID. */
83 #define SIZEOF_REV      41      /* Holds a SHA-1 and an ending NUL. */
84 #define SIZEOF_ARG      32      /* Default argument array size. */
86 /* Revision graph */
88 #define REVGRAPH_INIT   'I'
89 #define REVGRAPH_MERGE  'M'
90 #define REVGRAPH_BRANCH '+'
91 #define REVGRAPH_COMMIT '*'
92 #define REVGRAPH_BOUND  '^'
94 #define SIZEOF_REVGRAPH 19      /* Size of revision ancestry graphics. */
96 /* This color name can be used to refer to the default term colors. */
97 #define COLOR_DEFAULT   (-1)
99 #define ICONV_NONE      ((iconv_t) -1)
100 #ifndef ICONV_CONST
101 #define ICONV_CONST     /* nothing */
102 #endif
104 /* The format and size of the date column in the main view. */
105 #define DATE_FORMAT     "%Y-%m-%d %H:%M"
106 #define DATE_COLS       STRING_SIZE("2006-04-29 14:21 ")
107 #define DATE_SHORT_COLS STRING_SIZE("2006-04-29 ")
109 #define ID_COLS         8
110 #define AUTHOR_COLS     19
112 #define MIN_VIEW_HEIGHT 4
114 #define NULL_ID         "0000000000000000000000000000000000000000"
116 #define S_ISGITLINK(mode) (((mode) & S_IFMT) == 0160000)
118 /* Some ASCII-shorthands fitted into the ncurses namespace. */
119 #define KEY_TAB         '\t'
120 #define KEY_RETURN      '\r'
121 #define KEY_ESC         27
124 struct ref {
125         char id[SIZEOF_REV];    /* Commit SHA1 ID */
126         unsigned int head:1;    /* Is it the current HEAD? */
127         unsigned int tag:1;     /* Is it a tag? */
128         unsigned int ltag:1;    /* If so, is the tag local? */
129         unsigned int remote:1;  /* Is it a remote ref? */
130         unsigned int tracked:1; /* Is it the remote for the current HEAD? */
131         char name[1];           /* Ref name; tag or head names are shortened. */
132 };
134 struct ref_list {
135         char id[SIZEOF_REV];    /* Commit SHA1 ID */
136         size_t size;            /* Number of refs. */
137         struct ref **refs;      /* References for this ID. */
138 };
140 static struct ref *get_ref_head();
141 static struct ref_list *get_ref_list(const char *id);
142 static void foreach_ref(bool (*visitor)(void *data, const struct ref *ref), void *data);
143 static int load_refs(void);
145 enum format_flags {
146         FORMAT_ALL,             /* Perform replacement in all arguments. */
147         FORMAT_DASH,            /* Perform replacement up until "--". */
148         FORMAT_NONE             /* No replacement should be performed. */
149 };
151 static bool format_argv(const char *dst[], const char *src[], enum format_flags flags);
153 enum input_status {
154         INPUT_OK,
155         INPUT_SKIP,
156         INPUT_STOP,
157         INPUT_CANCEL
158 };
160 typedef enum input_status (*input_handler)(void *data, char *buf, int c);
162 static char *prompt_input(const char *prompt, input_handler handler, void *data);
163 static bool prompt_yesno(const char *prompt);
165 struct menu_item {
166         int hotkey;
167         const char *text;
168         void *data;
169 };
171 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected);
173 /*
174  * Allocation helpers ... Entering macro hell to never be seen again.
175  */
177 #define DEFINE_ALLOCATOR(name, type, chunk_size)                                \
178 static type *                                                                   \
179 name(type **mem, size_t size, size_t increase)                                  \
180 {                                                                               \
181         size_t num_chunks = (size + chunk_size - 1) / chunk_size;               \
182         size_t num_chunks_new = (size + increase + chunk_size - 1) / chunk_size;\
183         type *tmp = *mem;                                                       \
184                                                                                 \
185         if (mem == NULL || num_chunks != num_chunks_new) {                      \
186                 tmp = realloc(tmp, num_chunks_new * chunk_size * sizeof(type)); \
187                 if (tmp)                                                        \
188                         *mem = tmp;                                             \
189         }                                                                       \
190                                                                                 \
191         return tmp;                                                             \
194 /*
195  * String helpers
196  */
198 static inline void
199 string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t srclen)
201         if (srclen > dstlen - 1)
202                 srclen = dstlen - 1;
204         strncpy(dst, src, srclen);
205         dst[srclen] = 0;
208 /* Shorthands for safely copying into a fixed buffer. */
210 #define string_copy(dst, src) \
211         string_ncopy_do(dst, sizeof(dst), src, sizeof(src))
213 #define string_ncopy(dst, src, srclen) \
214         string_ncopy_do(dst, sizeof(dst), src, srclen)
216 #define string_copy_rev(dst, src) \
217         string_ncopy_do(dst, SIZEOF_REV, src, SIZEOF_REV - 1)
219 #define string_add(dst, from, src) \
220         string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src))
222 static void
223 string_expand(char *dst, size_t dstlen, const char *src, int tabsize)
225         size_t size, pos;
227         for (size = pos = 0; size < dstlen - 1 && src[pos]; pos++) {
228                 if (src[pos] == '\t') {
229                         size_t expanded = tabsize - (size % tabsize);
231                         if (expanded + size >= dstlen - 1)
232                                 expanded = dstlen - size - 1;
233                         memcpy(dst + size, "        ", expanded);
234                         size += expanded;
235                 } else {
236                         dst[size++] = src[pos];
237                 }
238         }
240         dst[size] = 0;
243 static char *
244 chomp_string(char *name)
246         int namelen;
248         while (isspace(*name))
249                 name++;
251         namelen = strlen(name) - 1;
252         while (namelen > 0 && isspace(name[namelen]))
253                 name[namelen--] = 0;
255         return name;
258 static bool
259 string_nformat(char *buf, size_t bufsize, size_t *bufpos, const char *fmt, ...)
261         va_list args;
262         size_t pos = bufpos ? *bufpos : 0;
264         va_start(args, fmt);
265         pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
266         va_end(args);
268         if (bufpos)
269                 *bufpos = pos;
271         return pos >= bufsize ? FALSE : TRUE;
274 #define string_format(buf, fmt, args...) \
275         string_nformat(buf, sizeof(buf), NULL, fmt, args)
277 #define string_format_from(buf, from, fmt, args...) \
278         string_nformat(buf, sizeof(buf), from, fmt, args)
280 static int
281 string_enum_compare(const char *str1, const char *str2, int len)
283         size_t i;
285 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
287         /* Diff-Header == DIFF_HEADER */
288         for (i = 0; i < len; i++) {
289                 if (toupper(str1[i]) == toupper(str2[i]))
290                         continue;
292                 if (string_enum_sep(str1[i]) &&
293                     string_enum_sep(str2[i]))
294                         continue;
296                 return str1[i] - str2[i];
297         }
299         return 0;
302 #define enum_equals(entry, str, len) \
303         ((entry).namelen == (len) && !string_enum_compare((entry).name, str, len))
305 struct enum_map {
306         const char *name;
307         int namelen;
308         int value;
309 };
311 #define ENUM_MAP(name, value) { name, STRING_SIZE(name), value }
313 static char *
314 enum_map_name(const char *name, size_t namelen)
316         static char buf[SIZEOF_STR];
317         int bufpos;
319         for (bufpos = 0; bufpos <= namelen; bufpos++) {
320                 buf[bufpos] = tolower(name[bufpos]);
321                 if (buf[bufpos] == '_')
322                         buf[bufpos] = '-';
323         }
325         buf[bufpos] = 0;
326         return buf;
329 #define enum_name(entry) enum_map_name((entry).name, (entry).namelen)
331 static bool
332 map_enum_do(const struct enum_map *map, size_t map_size, int *value, const char *name)
334         size_t namelen = strlen(name);
335         int i;
337         for (i = 0; i < map_size; i++)
338                 if (enum_equals(map[i], name, namelen)) {
339                         *value = map[i].value;
340                         return TRUE;
341                 }
343         return FALSE;
346 #define map_enum(attr, map, name) \
347         map_enum_do(map, ARRAY_SIZE(map), attr, name)
349 #define prefixcmp(str1, str2) \
350         strncmp(str1, str2, STRING_SIZE(str2))
352 static inline int
353 suffixcmp(const char *str, int slen, const char *suffix)
355         size_t len = slen >= 0 ? slen : strlen(str);
356         size_t suffixlen = strlen(suffix);
358         return suffixlen < len ? strcmp(str + len - suffixlen, suffix) : -1;
362 #define DATE_INFO \
363         DATE_(NO), \
364         DATE_(DEFAULT), \
365         DATE_(RELATIVE), \
366         DATE_(SHORT)
368 enum date {
369 #define DATE_(name) DATE_##name
370         DATE_INFO
371 #undef  DATE_
372 };
374 static const struct enum_map date_map[] = {
375 #define DATE_(name) ENUM_MAP(#name, DATE_##name)
376         DATE_INFO
377 #undef  DATE_
378 };
380 struct time {
381         time_t sec;
382         int tz;
383 };
385 static inline int timecmp(const struct time *t1, const struct time *t2)
387         return t1->sec - t2->sec;
390 static const char *
391 mkdate(const struct time *time, enum date date)
393         static char buf[DATE_COLS + 1];
394         static const struct enum_map reldate[] = {
395                 { "second", 1,                  60 * 2 },
396                 { "minute", 60,                 60 * 60 * 2 },
397                 { "hour",   60 * 60,            60 * 60 * 24 * 2 },
398                 { "day",    60 * 60 * 24,       60 * 60 * 24 * 7 * 2 },
399                 { "week",   60 * 60 * 24 * 7,   60 * 60 * 24 * 7 * 5 },
400                 { "month",  60 * 60 * 24 * 30,  60 * 60 * 24 * 30 * 12 },
401         };
402         struct tm tm;
404         if (!date || !time || !time->sec)
405                 return "";
407         if (date == DATE_RELATIVE) {
408                 struct timeval now;
409                 time_t date = time->sec + time->tz;
410                 time_t seconds;
411                 int i;
413                 gettimeofday(&now, NULL);
414                 seconds = now.tv_sec < date ? date - now.tv_sec : now.tv_sec - date;
415                 for (i = 0; i < ARRAY_SIZE(reldate); i++) {
416                         if (seconds >= reldate[i].value)
417                                 continue;
419                         seconds /= reldate[i].namelen;
420                         if (!string_format(buf, "%ld %s%s %s",
421                                            seconds, reldate[i].name,
422                                            seconds > 1 ? "s" : "",
423                                            now.tv_sec >= date ? "ago" : "ahead"))
424                                 break;
425                         return buf;
426                 }
427         }
429         gmtime_r(&time->sec, &tm);
430         return strftime(buf, sizeof(buf), DATE_FORMAT, &tm) ? buf : NULL;
434 #define AUTHOR_VALUES \
435         AUTHOR_(NO), \
436         AUTHOR_(FULL), \
437         AUTHOR_(ABBREVIATED)
439 enum author {
440 #define AUTHOR_(name) AUTHOR_##name
441         AUTHOR_VALUES,
442 #undef  AUTHOR_
443         AUTHOR_DEFAULT = AUTHOR_FULL
444 };
446 static const struct enum_map author_map[] = {
447 #define AUTHOR_(name) ENUM_MAP(#name, AUTHOR_##name)
448         AUTHOR_VALUES
449 #undef  AUTHOR_
450 };
452 static const char *
453 get_author_initials(const char *author)
455         static char initials[AUTHOR_COLS * 6 + 1];
456         size_t pos = 0;
457         const char *end = strchr(author, '\0');
459 #define is_initial_sep(c) (isspace(c) || ispunct(c) || (c) == '@' || (c) == '-')
461         memset(initials, 0, sizeof(initials));
462         while (author < end) {
463                 unsigned char bytes;
464                 size_t i;
466                 while (is_initial_sep(*author))
467                         author++;
469                 bytes = utf8_char_length(author, end);
470                 if (bytes < sizeof(initials) - 1 - pos) {
471                         while (bytes--) {
472                                 initials[pos++] = *author++;
473                         }
474                 }
476                 for (i = pos; author < end && !is_initial_sep(*author); author++) {
477                         if (i < sizeof(initials) - 1)
478                                 initials[i++] = *author;
479                 }
481                 initials[i++] = 0;
482         }
484         return initials;
488 static bool
489 argv_from_string(const char *argv[SIZEOF_ARG], int *argc, char *cmd)
491         int valuelen;
493         while (*cmd && *argc < SIZEOF_ARG && (valuelen = strcspn(cmd, " \t"))) {
494                 bool advance = cmd[valuelen] != 0;
496                 cmd[valuelen] = 0;
497                 argv[(*argc)++] = chomp_string(cmd);
498                 cmd = chomp_string(cmd + valuelen + advance);
499         }
501         if (*argc < SIZEOF_ARG)
502                 argv[*argc] = NULL;
503         return *argc < SIZEOF_ARG;
506 static void
507 argv_from_env(const char **argv, const char *name)
509         char *env = argv ? getenv(name) : NULL;
510         int argc = 0;
512         if (env && *env)
513                 env = strdup(env);
514         if (env && !argv_from_string(argv, &argc, env))
515                 die("Too many arguments in the `%s` environment variable", name);
519 /*
520  * Executing external commands.
521  */
523 enum io_type {
524         IO_FD,                  /* File descriptor based IO. */
525         IO_BG,                  /* Execute command in the background. */
526         IO_FG,                  /* Execute command with same std{in,out,err}. */
527         IO_RD,                  /* Read only fork+exec IO. */
528         IO_WR,                  /* Write only fork+exec IO. */
529         IO_AP,                  /* Append fork+exec output to file. */
530 };
532 struct io {
533         enum io_type type;      /* The requested type of pipe. */
534         const char *dir;        /* Directory from which to execute. */
535         pid_t pid;              /* PID of spawned process. */
536         int pipe;               /* Pipe end for reading or writing. */
537         int error;              /* Error status. */
538         const char *argv[SIZEOF_ARG];   /* Shell command arguments. */
539         char *buf;              /* Read buffer. */
540         size_t bufalloc;        /* Allocated buffer size. */
541         size_t bufsize;         /* Buffer content size. */
542         char *bufpos;           /* Current buffer position. */
543         unsigned int eof:1;     /* Has end of file been reached. */
544 };
546 static void
547 reset_io(struct io *io)
549         io->pipe = -1;
550         io->pid = 0;
551         io->buf = io->bufpos = NULL;
552         io->bufalloc = io->bufsize = 0;
553         io->error = 0;
554         io->eof = 0;
557 static void
558 init_io(struct io *io, const char *dir, enum io_type type)
560         reset_io(io);
561         io->type = type;
562         io->dir = dir;
565 static bool
566 init_io_rd(struct io *io, const char *argv[], const char *dir,
567                 enum format_flags flags)
569         init_io(io, dir, IO_RD);
570         return format_argv(io->argv, argv, flags);
573 static bool
574 io_open(struct io *io, const char *fmt, ...)
576         char name[SIZEOF_STR] = "";
577         bool fits;
578         va_list args;
580         init_io(io, NULL, IO_FD);
582         va_start(args, fmt);
583         fits = vsnprintf(name, sizeof(name), fmt, args) < sizeof(name);
584         va_end(args);
586         if (!fits) {
587                 io->error = ENAMETOOLONG;
588                 return FALSE;
589         }
590         io->pipe = *name ? open(name, O_RDONLY) : STDIN_FILENO;
591         if (io->pipe == -1)
592                 io->error = errno;
593         return io->pipe != -1;
596 static bool
597 kill_io(struct io *io)
599         return io->pid == 0 || kill(io->pid, SIGKILL) != -1;
602 static bool
603 done_io(struct io *io)
605         pid_t pid = io->pid;
607         if (io->pipe != -1)
608                 close(io->pipe);
609         free(io->buf);
610         reset_io(io);
612         while (pid > 0) {
613                 int status;
614                 pid_t waiting = waitpid(pid, &status, 0);
616                 if (waiting < 0) {
617                         if (errno == EINTR)
618                                 continue;
619                         report("waitpid failed (%s)", strerror(errno));
620                         return FALSE;
621                 }
623                 return waiting == pid &&
624                        !WIFSIGNALED(status) &&
625                        WIFEXITED(status) &&
626                        !WEXITSTATUS(status);
627         }
629         return TRUE;
632 static bool
633 start_io(struct io *io)
635         int pipefds[2] = { -1, -1 };
637         if (io->type == IO_FD)
638                 return TRUE;
640         if ((io->type == IO_RD || io->type == IO_WR) &&
641             pipe(pipefds) < 0)
642                 return FALSE;
643         else if (io->type == IO_AP)
644                 pipefds[1] = io->pipe;
646         if ((io->pid = fork())) {
647                 if (pipefds[!(io->type == IO_WR)] != -1)
648                         close(pipefds[!(io->type == IO_WR)]);
649                 if (io->pid != -1) {
650                         io->pipe = pipefds[!!(io->type == IO_WR)];
651                         return TRUE;
652                 }
654         } else {
655                 if (io->type != IO_FG) {
656                         int devnull = open("/dev/null", O_RDWR);
657                         int readfd  = io->type == IO_WR ? pipefds[0] : devnull;
658                         int writefd = (io->type == IO_RD || io->type == IO_AP)
659                                                         ? pipefds[1] : devnull;
661                         dup2(readfd,  STDIN_FILENO);
662                         dup2(writefd, STDOUT_FILENO);
663                         dup2(devnull, STDERR_FILENO);
665                         close(devnull);
666                         if (pipefds[0] != -1)
667                                 close(pipefds[0]);
668                         if (pipefds[1] != -1)
669                                 close(pipefds[1]);
670                 }
672                 if (io->dir && *io->dir && chdir(io->dir) == -1)
673                         die("Failed to change directory: %s", strerror(errno));
675                 execvp(io->argv[0], (char *const*) io->argv);
676                 die("Failed to execute program: %s", strerror(errno));
677         }
679         if (pipefds[!!(io->type == IO_WR)] != -1)
680                 close(pipefds[!!(io->type == IO_WR)]);
681         return FALSE;
684 static bool
685 run_io(struct io *io, const char **argv, const char *dir, enum io_type type)
687         init_io(io, dir, type);
688         if (!format_argv(io->argv, argv, FORMAT_NONE))
689                 return FALSE;
690         return start_io(io);
693 static int
694 run_io_do(struct io *io)
696         return start_io(io) && done_io(io);
699 static int
700 run_io_bg(const char **argv)
702         struct io io = {};
704         init_io(&io, NULL, IO_BG);
705         if (!format_argv(io.argv, argv, FORMAT_NONE))
706                 return FALSE;
707         return run_io_do(&io);
710 static bool
711 run_io_fg(const char **argv, const char *dir)
713         struct io io = {};
715         init_io(&io, dir, IO_FG);
716         if (!format_argv(io.argv, argv, FORMAT_NONE))
717                 return FALSE;
718         return run_io_do(&io);
721 static bool
722 run_io_append(const char **argv, enum format_flags flags, int fd)
724         struct io io = {};
726         init_io(&io, NULL, IO_AP);
727         io.pipe = fd;
728         if (format_argv(io.argv, argv, flags))
729                 return run_io_do(&io);
730         close(fd);
731         return FALSE;
734 static bool
735 run_io_rd(struct io *io, const char **argv, const char *dir, enum format_flags flags)
737         return init_io_rd(io, argv, dir, flags) && start_io(io);
740 static bool
741 io_eof(struct io *io)
743         return io->eof;
746 static int
747 io_error(struct io *io)
749         return io->error;
752 static char *
753 io_strerror(struct io *io)
755         return strerror(io->error);
758 static bool
759 io_can_read(struct io *io)
761         struct timeval tv = { 0, 500 };
762         fd_set fds;
764         FD_ZERO(&fds);
765         FD_SET(io->pipe, &fds);
767         return select(io->pipe + 1, &fds, NULL, NULL, &tv) > 0;
770 static ssize_t
771 io_read(struct io *io, void *buf, size_t bufsize)
773         do {
774                 ssize_t readsize = read(io->pipe, buf, bufsize);
776                 if (readsize < 0 && (errno == EAGAIN || errno == EINTR))
777                         continue;
778                 else if (readsize == -1)
779                         io->error = errno;
780                 else if (readsize == 0)
781                         io->eof = 1;
782                 return readsize;
783         } while (1);
786 DEFINE_ALLOCATOR(realloc_io_buf, char, BUFSIZ)
788 static char *
789 io_get(struct io *io, int c, bool can_read)
791         char *eol;
792         ssize_t readsize;
794         while (TRUE) {
795                 if (io->bufsize > 0) {
796                         eol = memchr(io->bufpos, c, io->bufsize);
797                         if (eol) {
798                                 char *line = io->bufpos;
800                                 *eol = 0;
801                                 io->bufpos = eol + 1;
802                                 io->bufsize -= io->bufpos - line;
803                                 return line;
804                         }
805                 }
807                 if (io_eof(io)) {
808                         if (io->bufsize) {
809                                 io->bufpos[io->bufsize] = 0;
810                                 io->bufsize = 0;
811                                 return io->bufpos;
812                         }
813                         return NULL;
814                 }
816                 if (!can_read)
817                         return NULL;
819                 if (io->bufsize > 0 && io->bufpos > io->buf)
820                         memmove(io->buf, io->bufpos, io->bufsize);
822                 if (io->bufalloc == io->bufsize) {
823                         if (!realloc_io_buf(&io->buf, io->bufalloc, BUFSIZ))
824                                 return NULL;
825                         io->bufalloc += BUFSIZ;
826                 }
828                 io->bufpos = io->buf;
829                 readsize = io_read(io, io->buf + io->bufsize, io->bufalloc - io->bufsize);
830                 if (io_error(io))
831                         return NULL;
832                 io->bufsize += readsize;
833         }
836 static bool
837 io_write(struct io *io, const void *buf, size_t bufsize)
839         size_t written = 0;
841         while (!io_error(io) && written < bufsize) {
842                 ssize_t size;
844                 size = write(io->pipe, buf + written, bufsize - written);
845                 if (size < 0 && (errno == EAGAIN || errno == EINTR))
846                         continue;
847                 else if (size == -1)
848                         io->error = errno;
849                 else
850                         written += size;
851         }
853         return written == bufsize;
856 static bool
857 io_read_buf(struct io *io, char buf[], size_t bufsize)
859         char *result = io_get(io, '\n', TRUE);
861         if (result) {
862                 result = chomp_string(result);
863                 string_ncopy_do(buf, bufsize, result, strlen(result));
864         }
866         return done_io(io) && result;
869 static bool
870 run_io_buf(const char **argv, char buf[], size_t bufsize)
872         struct io io = {};
874         return run_io_rd(&io, argv, NULL, FORMAT_NONE)
875             && io_read_buf(&io, buf, bufsize);
878 static int
879 io_load(struct io *io, const char *separators,
880         int (*read_property)(char *, size_t, char *, size_t))
882         char *name;
883         int state = OK;
885         if (!start_io(io))
886                 return ERR;
888         while (state == OK && (name = io_get(io, '\n', TRUE))) {
889                 char *value;
890                 size_t namelen;
891                 size_t valuelen;
893                 name = chomp_string(name);
894                 namelen = strcspn(name, separators);
896                 if (name[namelen]) {
897                         name[namelen] = 0;
898                         value = chomp_string(name + namelen + 1);
899                         valuelen = strlen(value);
901                 } else {
902                         value = "";
903                         valuelen = 0;
904                 }
906                 state = read_property(name, namelen, value, valuelen);
907         }
909         if (state != ERR && io_error(io))
910                 state = ERR;
911         done_io(io);
913         return state;
916 static int
917 run_io_load(const char **argv, const char *separators,
918             int (*read_property)(char *, size_t, char *, size_t))
920         struct io io = {};
922         return init_io_rd(&io, argv, NULL, FORMAT_NONE)
923                 ? io_load(&io, separators, read_property) : ERR;
927 /*
928  * User requests
929  */
931 #define REQ_INFO \
932         /* XXX: Keep the view request first and in sync with views[]. */ \
933         REQ_GROUP("View switching") \
934         REQ_(VIEW_MAIN,         "Show main view"), \
935         REQ_(VIEW_DIFF,         "Show diff view"), \
936         REQ_(VIEW_LOG,          "Show log view"), \
937         REQ_(VIEW_TREE,         "Show tree view"), \
938         REQ_(VIEW_BLOB,         "Show blob view"), \
939         REQ_(VIEW_BLAME,        "Show blame view"), \
940         REQ_(VIEW_BRANCH,       "Show branch view"), \
941         REQ_(VIEW_HELP,         "Show help page"), \
942         REQ_(VIEW_PAGER,        "Show pager view"), \
943         REQ_(VIEW_STATUS,       "Show status view"), \
944         REQ_(VIEW_STAGE,        "Show stage view"), \
945         \
946         REQ_GROUP("View manipulation") \
947         REQ_(ENTER,             "Enter current line and scroll"), \
948         REQ_(NEXT,              "Move to next"), \
949         REQ_(PREVIOUS,          "Move to previous"), \
950         REQ_(PARENT,            "Move to parent"), \
951         REQ_(VIEW_NEXT,         "Move focus to next view"), \
952         REQ_(REFRESH,           "Reload and refresh"), \
953         REQ_(MAXIMIZE,          "Maximize the current view"), \
954         REQ_(VIEW_CLOSE,        "Close the current view"), \
955         REQ_(QUIT,              "Close all views and quit"), \
956         \
957         REQ_GROUP("View specific requests") \
958         REQ_(STATUS_UPDATE,     "Update file status"), \
959         REQ_(STATUS_REVERT,     "Revert file changes"), \
960         REQ_(STATUS_MERGE,      "Merge file using external tool"), \
961         REQ_(STAGE_NEXT,        "Find next chunk to stage"), \
962         \
963         REQ_GROUP("Cursor navigation") \
964         REQ_(MOVE_UP,           "Move cursor one line up"), \
965         REQ_(MOVE_DOWN,         "Move cursor one line down"), \
966         REQ_(MOVE_PAGE_DOWN,    "Move cursor one page down"), \
967         REQ_(MOVE_PAGE_UP,      "Move cursor one page up"), \
968         REQ_(MOVE_FIRST_LINE,   "Move cursor to first line"), \
969         REQ_(MOVE_LAST_LINE,    "Move cursor to last line"), \
970         \
971         REQ_GROUP("Scrolling") \
972         REQ_(SCROLL_LEFT,       "Scroll two columns left"), \
973         REQ_(SCROLL_RIGHT,      "Scroll two columns right"), \
974         REQ_(SCROLL_LINE_UP,    "Scroll one line up"), \
975         REQ_(SCROLL_LINE_DOWN,  "Scroll one line down"), \
976         REQ_(SCROLL_PAGE_UP,    "Scroll one page up"), \
977         REQ_(SCROLL_PAGE_DOWN,  "Scroll one page down"), \
978         \
979         REQ_GROUP("Searching") \
980         REQ_(SEARCH,            "Search the view"), \
981         REQ_(SEARCH_BACK,       "Search backwards in the view"), \
982         REQ_(FIND_NEXT,         "Find next search match"), \
983         REQ_(FIND_PREV,         "Find previous search match"), \
984         \
985         REQ_GROUP("Option manipulation") \
986         REQ_(OPTIONS,           "Open option menu"), \
987         REQ_(TOGGLE_LINENO,     "Toggle line numbers"), \
988         REQ_(TOGGLE_DATE,       "Toggle date display"), \
989         REQ_(TOGGLE_DATE_SHORT, "Toggle short (date-only) dates"), \
990         REQ_(TOGGLE_AUTHOR,     "Toggle author display"), \
991         REQ_(TOGGLE_REV_GRAPH,  "Toggle revision graph visualization"), \
992         REQ_(TOGGLE_REFS,       "Toggle reference display (tags/branches)"), \
993         REQ_(TOGGLE_SORT_ORDER, "Toggle ascending/descending sort order"), \
994         REQ_(TOGGLE_SORT_FIELD, "Toggle field to sort by"), \
995         \
996         REQ_GROUP("Misc") \
997         REQ_(PROMPT,            "Bring up the prompt"), \
998         REQ_(SCREEN_REDRAW,     "Redraw the screen"), \
999         REQ_(SHOW_VERSION,      "Show version information"), \
1000         REQ_(STOP_LOADING,      "Stop all loading views"), \
1001         REQ_(EDIT,              "Open in editor"), \
1002         REQ_(NONE,              "Do nothing")
1005 /* User action requests. */
1006 enum request {
1007 #define REQ_GROUP(help)
1008 #define REQ_(req, help) REQ_##req
1010         /* Offset all requests to avoid conflicts with ncurses getch values. */
1011         REQ_OFFSET = KEY_MAX + 1,
1012         REQ_INFO
1014 #undef  REQ_GROUP
1015 #undef  REQ_
1016 };
1018 struct request_info {
1019         enum request request;
1020         const char *name;
1021         int namelen;
1022         const char *help;
1023 };
1025 static const struct request_info req_info[] = {
1026 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
1027 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
1028         REQ_INFO
1029 #undef  REQ_GROUP
1030 #undef  REQ_
1031 };
1033 static enum request
1034 get_request(const char *name)
1036         int namelen = strlen(name);
1037         int i;
1039         for (i = 0; i < ARRAY_SIZE(req_info); i++)
1040                 if (enum_equals(req_info[i], name, namelen))
1041                         return req_info[i].request;
1043         return REQ_NONE;
1047 /*
1048  * Options
1049  */
1051 /* Option and state variables. */
1052 static enum date opt_date               = DATE_DEFAULT;
1053 static enum author opt_author           = AUTHOR_DEFAULT;
1054 static bool opt_line_number             = FALSE;
1055 static bool opt_line_graphics           = TRUE;
1056 static bool opt_rev_graph               = FALSE;
1057 static bool opt_show_refs               = TRUE;
1058 static int opt_num_interval             = 5;
1059 static double opt_hscroll               = 0.50;
1060 static double opt_scale_split_view      = 2.0 / 3.0;
1061 static int opt_tab_size                 = 8;
1062 static int opt_author_cols              = AUTHOR_COLS;
1063 static char opt_path[SIZEOF_STR]        = "";
1064 static char opt_file[SIZEOF_STR]        = "";
1065 static char opt_ref[SIZEOF_REF]         = "";
1066 static char opt_head[SIZEOF_REF]        = "";
1067 static char opt_remote[SIZEOF_REF]      = "";
1068 static char opt_encoding[20]            = "UTF-8";
1069 static iconv_t opt_iconv_in             = ICONV_NONE;
1070 static iconv_t opt_iconv_out            = ICONV_NONE;
1071 static char opt_search[SIZEOF_STR]      = "";
1072 static char opt_cdup[SIZEOF_STR]        = "";
1073 static char opt_prefix[SIZEOF_STR]      = "";
1074 static char opt_git_dir[SIZEOF_STR]     = "";
1075 static signed char opt_is_inside_work_tree      = -1; /* set to TRUE or FALSE */
1076 static char opt_editor[SIZEOF_STR]      = "";
1077 static FILE *opt_tty                    = NULL;
1079 #define is_initial_commit()     (!get_ref_head())
1080 #define is_head_commit(rev)     (!strcmp((rev), "HEAD") || (get_ref_head() && !strcmp(rev, get_ref_head()->id)))
1083 /*
1084  * Line-oriented content detection.
1085  */
1087 #define LINE_INFO \
1088 LINE(DIFF_HEADER,  "diff --git ",       COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1089 LINE(DIFF_CHUNK,   "@@",                COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1090 LINE(DIFF_ADD,     "+",                 COLOR_GREEN,    COLOR_DEFAULT,  0), \
1091 LINE(DIFF_DEL,     "-",                 COLOR_RED,      COLOR_DEFAULT,  0), \
1092 LINE(DIFF_INDEX,        "index ",         COLOR_BLUE,   COLOR_DEFAULT,  0), \
1093 LINE(DIFF_OLDMODE,      "old file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
1094 LINE(DIFF_NEWMODE,      "new file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
1095 LINE(DIFF_COPY_FROM,    "copy from",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
1096 LINE(DIFF_COPY_TO,      "copy to",        COLOR_YELLOW, COLOR_DEFAULT,  0), \
1097 LINE(DIFF_RENAME_FROM,  "rename from",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
1098 LINE(DIFF_RENAME_TO,    "rename to",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
1099 LINE(DIFF_SIMILARITY,   "similarity ",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
1100 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
1101 LINE(DIFF_TREE,         "diff-tree ",     COLOR_BLUE,   COLOR_DEFAULT,  0), \
1102 LINE(PP_AUTHOR,    "Author: ",          COLOR_CYAN,     COLOR_DEFAULT,  0), \
1103 LINE(PP_COMMIT,    "Commit: ",          COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1104 LINE(PP_MERGE,     "Merge: ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
1105 LINE(PP_DATE,      "Date:   ",          COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1106 LINE(PP_ADATE,     "AuthorDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1107 LINE(PP_CDATE,     "CommitDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1108 LINE(PP_REFS,      "Refs: ",            COLOR_RED,      COLOR_DEFAULT,  0), \
1109 LINE(COMMIT,       "commit ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
1110 LINE(PARENT,       "parent ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
1111 LINE(TREE,         "tree ",             COLOR_BLUE,     COLOR_DEFAULT,  0), \
1112 LINE(AUTHOR,       "author ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
1113 LINE(COMMITTER,    "committer ",        COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1114 LINE(SIGNOFF,      "    Signed-off-by", COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1115 LINE(ACKED,        "    Acked-by",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1116 LINE(DEFAULT,      "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
1117 LINE(CURSOR,       "",                  COLOR_WHITE,    COLOR_GREEN,    A_BOLD), \
1118 LINE(STATUS,       "",                  COLOR_GREEN,    COLOR_DEFAULT,  0), \
1119 LINE(DELIMITER,    "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1120 LINE(DATE,         "",                  COLOR_BLUE,     COLOR_DEFAULT,  0), \
1121 LINE(MODE,         "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1122 LINE(LINE_NUMBER,  "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1123 LINE(TITLE_BLUR,   "",                  COLOR_WHITE,    COLOR_BLUE,     0), \
1124 LINE(TITLE_FOCUS,  "",                  COLOR_WHITE,    COLOR_BLUE,     A_BOLD), \
1125 LINE(MAIN_COMMIT,  "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
1126 LINE(MAIN_TAG,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  A_BOLD), \
1127 LINE(MAIN_LOCAL_TAG,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1128 LINE(MAIN_REMOTE,  "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1129 LINE(MAIN_TRACKED, "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_BOLD), \
1130 LINE(MAIN_REF,     "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1131 LINE(MAIN_HEAD,    "",                  COLOR_CYAN,     COLOR_DEFAULT,  A_BOLD), \
1132 LINE(MAIN_REVGRAPH,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1133 LINE(TREE_HEAD,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_BOLD), \
1134 LINE(TREE_DIR,     "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_NORMAL), \
1135 LINE(TREE_FILE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
1136 LINE(STAT_HEAD,    "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1137 LINE(STAT_SECTION, "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1138 LINE(STAT_NONE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
1139 LINE(STAT_STAGED,  "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1140 LINE(STAT_UNSTAGED,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1141 LINE(STAT_UNTRACKED,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1142 LINE(HELP_KEYMAP,  "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1143 LINE(HELP_GROUP,   "",                  COLOR_BLUE,     COLOR_DEFAULT,  0), \
1144 LINE(BLAME_ID,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0)
1146 enum line_type {
1147 #define LINE(type, line, fg, bg, attr) \
1148         LINE_##type
1149         LINE_INFO,
1150         LINE_NONE
1151 #undef  LINE
1152 };
1154 struct line_info {
1155         const char *name;       /* Option name. */
1156         int namelen;            /* Size of option name. */
1157         const char *line;       /* The start of line to match. */
1158         int linelen;            /* Size of string to match. */
1159         int fg, bg, attr;       /* Color and text attributes for the lines. */
1160 };
1162 static struct line_info line_info[] = {
1163 #define LINE(type, line, fg, bg, attr) \
1164         { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
1165         LINE_INFO
1166 #undef  LINE
1167 };
1169 static enum line_type
1170 get_line_type(const char *line)
1172         int linelen = strlen(line);
1173         enum line_type type;
1175         for (type = 0; type < ARRAY_SIZE(line_info); type++)
1176                 /* Case insensitive search matches Signed-off-by lines better. */
1177                 if (linelen >= line_info[type].linelen &&
1178                     !strncasecmp(line_info[type].line, line, line_info[type].linelen))
1179                         return type;
1181         return LINE_DEFAULT;
1184 static inline int
1185 get_line_attr(enum line_type type)
1187         assert(type < ARRAY_SIZE(line_info));
1188         return COLOR_PAIR(type) | line_info[type].attr;
1191 static struct line_info *
1192 get_line_info(const char *name)
1194         size_t namelen = strlen(name);
1195         enum line_type type;
1197         for (type = 0; type < ARRAY_SIZE(line_info); type++)
1198                 if (enum_equals(line_info[type], name, namelen))
1199                         return &line_info[type];
1201         return NULL;
1204 static void
1205 init_colors(void)
1207         int default_bg = line_info[LINE_DEFAULT].bg;
1208         int default_fg = line_info[LINE_DEFAULT].fg;
1209         enum line_type type;
1211         start_color();
1213         if (assume_default_colors(default_fg, default_bg) == ERR) {
1214                 default_bg = COLOR_BLACK;
1215                 default_fg = COLOR_WHITE;
1216         }
1218         for (type = 0; type < ARRAY_SIZE(line_info); type++) {
1219                 struct line_info *info = &line_info[type];
1220                 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
1221                 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
1223                 init_pair(type, fg, bg);
1224         }
1227 struct line {
1228         enum line_type type;
1230         /* State flags */
1231         unsigned int selected:1;
1232         unsigned int dirty:1;
1233         unsigned int cleareol:1;
1234         unsigned int other:16;
1236         void *data;             /* User data */
1237 };
1240 /*
1241  * Keys
1242  */
1244 struct keybinding {
1245         int alias;
1246         enum request request;
1247 };
1249 static const struct keybinding default_keybindings[] = {
1250         /* View switching */
1251         { 'm',          REQ_VIEW_MAIN },
1252         { 'd',          REQ_VIEW_DIFF },
1253         { 'l',          REQ_VIEW_LOG },
1254         { 't',          REQ_VIEW_TREE },
1255         { 'f',          REQ_VIEW_BLOB },
1256         { 'B',          REQ_VIEW_BLAME },
1257         { 'H',          REQ_VIEW_BRANCH },
1258         { 'p',          REQ_VIEW_PAGER },
1259         { 'h',          REQ_VIEW_HELP },
1260         { 'S',          REQ_VIEW_STATUS },
1261         { 'c',          REQ_VIEW_STAGE },
1263         /* View manipulation */
1264         { 'q',          REQ_VIEW_CLOSE },
1265         { KEY_TAB,      REQ_VIEW_NEXT },
1266         { KEY_RETURN,   REQ_ENTER },
1267         { KEY_UP,       REQ_PREVIOUS },
1268         { KEY_DOWN,     REQ_NEXT },
1269         { 'R',          REQ_REFRESH },
1270         { KEY_F(5),     REQ_REFRESH },
1271         { 'O',          REQ_MAXIMIZE },
1273         /* Cursor navigation */
1274         { 'k',          REQ_MOVE_UP },
1275         { 'j',          REQ_MOVE_DOWN },
1276         { KEY_HOME,     REQ_MOVE_FIRST_LINE },
1277         { KEY_END,      REQ_MOVE_LAST_LINE },
1278         { KEY_NPAGE,    REQ_MOVE_PAGE_DOWN },
1279         { ' ',          REQ_MOVE_PAGE_DOWN },
1280         { KEY_PPAGE,    REQ_MOVE_PAGE_UP },
1281         { 'b',          REQ_MOVE_PAGE_UP },
1282         { '-',          REQ_MOVE_PAGE_UP },
1284         /* Scrolling */
1285         { KEY_LEFT,     REQ_SCROLL_LEFT },
1286         { KEY_RIGHT,    REQ_SCROLL_RIGHT },
1287         { KEY_IC,       REQ_SCROLL_LINE_UP },
1288         { KEY_DC,       REQ_SCROLL_LINE_DOWN },
1289         { 'w',          REQ_SCROLL_PAGE_UP },
1290         { 's',          REQ_SCROLL_PAGE_DOWN },
1292         /* Searching */
1293         { '/',          REQ_SEARCH },
1294         { '?',          REQ_SEARCH_BACK },
1295         { 'n',          REQ_FIND_NEXT },
1296         { 'N',          REQ_FIND_PREV },
1298         /* Misc */
1299         { 'Q',          REQ_QUIT },
1300         { 'z',          REQ_STOP_LOADING },
1301         { 'v',          REQ_SHOW_VERSION },
1302         { 'r',          REQ_SCREEN_REDRAW },
1303         { 'o',          REQ_OPTIONS },
1304         { '.',          REQ_TOGGLE_LINENO },
1305         { 'D',          REQ_TOGGLE_DATE },
1306         { 'A',          REQ_TOGGLE_AUTHOR },
1307         { 'g',          REQ_TOGGLE_REV_GRAPH },
1308         { 'F',          REQ_TOGGLE_REFS },
1309         { 'I',          REQ_TOGGLE_SORT_ORDER },
1310         { 'i',          REQ_TOGGLE_SORT_FIELD },
1311         { ':',          REQ_PROMPT },
1312         { 'u',          REQ_STATUS_UPDATE },
1313         { '!',          REQ_STATUS_REVERT },
1314         { 'M',          REQ_STATUS_MERGE },
1315         { '@',          REQ_STAGE_NEXT },
1316         { ',',          REQ_PARENT },
1317         { 'e',          REQ_EDIT },
1318 };
1320 #define KEYMAP_INFO \
1321         KEYMAP_(GENERIC), \
1322         KEYMAP_(MAIN), \
1323         KEYMAP_(DIFF), \
1324         KEYMAP_(LOG), \
1325         KEYMAP_(TREE), \
1326         KEYMAP_(BLOB), \
1327         KEYMAP_(BLAME), \
1328         KEYMAP_(BRANCH), \
1329         KEYMAP_(PAGER), \
1330         KEYMAP_(HELP), \
1331         KEYMAP_(STATUS), \
1332         KEYMAP_(STAGE)
1334 enum keymap {
1335 #define KEYMAP_(name) KEYMAP_##name
1336         KEYMAP_INFO
1337 #undef  KEYMAP_
1338 };
1340 static const struct enum_map keymap_table[] = {
1341 #define KEYMAP_(name) ENUM_MAP(#name, KEYMAP_##name)
1342         KEYMAP_INFO
1343 #undef  KEYMAP_
1344 };
1346 #define set_keymap(map, name) map_enum(map, keymap_table, name)
1348 struct keybinding_table {
1349         struct keybinding *data;
1350         size_t size;
1351 };
1353 static struct keybinding_table keybindings[ARRAY_SIZE(keymap_table)];
1355 static void
1356 add_keybinding(enum keymap keymap, enum request request, int key)
1358         struct keybinding_table *table = &keybindings[keymap];
1360         table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data));
1361         if (!table->data)
1362                 die("Failed to allocate keybinding");
1363         table->data[table->size].alias = key;
1364         table->data[table->size++].request = request;
1367 /* Looks for a key binding first in the given map, then in the generic map, and
1368  * lastly in the default keybindings. */
1369 static enum request
1370 get_keybinding(enum keymap keymap, int key)
1372         size_t i;
1374         for (i = 0; i < keybindings[keymap].size; i++)
1375                 if (keybindings[keymap].data[i].alias == key)
1376                         return keybindings[keymap].data[i].request;
1378         for (i = 0; i < keybindings[KEYMAP_GENERIC].size; i++)
1379                 if (keybindings[KEYMAP_GENERIC].data[i].alias == key)
1380                         return keybindings[KEYMAP_GENERIC].data[i].request;
1382         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1383                 if (default_keybindings[i].alias == key)
1384                         return default_keybindings[i].request;
1386         return (enum request) key;
1390 struct key {
1391         const char *name;
1392         int value;
1393 };
1395 static const struct key key_table[] = {
1396         { "Enter",      KEY_RETURN },
1397         { "Space",      ' ' },
1398         { "Backspace",  KEY_BACKSPACE },
1399         { "Tab",        KEY_TAB },
1400         { "Escape",     KEY_ESC },
1401         { "Left",       KEY_LEFT },
1402         { "Right",      KEY_RIGHT },
1403         { "Up",         KEY_UP },
1404         { "Down",       KEY_DOWN },
1405         { "Insert",     KEY_IC },
1406         { "Delete",     KEY_DC },
1407         { "Hash",       '#' },
1408         { "Home",       KEY_HOME },
1409         { "End",        KEY_END },
1410         { "PageUp",     KEY_PPAGE },
1411         { "PageDown",   KEY_NPAGE },
1412         { "F1",         KEY_F(1) },
1413         { "F2",         KEY_F(2) },
1414         { "F3",         KEY_F(3) },
1415         { "F4",         KEY_F(4) },
1416         { "F5",         KEY_F(5) },
1417         { "F6",         KEY_F(6) },
1418         { "F7",         KEY_F(7) },
1419         { "F8",         KEY_F(8) },
1420         { "F9",         KEY_F(9) },
1421         { "F10",        KEY_F(10) },
1422         { "F11",        KEY_F(11) },
1423         { "F12",        KEY_F(12) },
1424 };
1426 static int
1427 get_key_value(const char *name)
1429         int i;
1431         for (i = 0; i < ARRAY_SIZE(key_table); i++)
1432                 if (!strcasecmp(key_table[i].name, name))
1433                         return key_table[i].value;
1435         if (strlen(name) == 1 && isprint(*name))
1436                 return (int) *name;
1438         return ERR;
1441 static const char *
1442 get_key_name(int key_value)
1444         static char key_char[] = "'X'";
1445         const char *seq = NULL;
1446         int key;
1448         for (key = 0; key < ARRAY_SIZE(key_table); key++)
1449                 if (key_table[key].value == key_value)
1450                         seq = key_table[key].name;
1452         if (seq == NULL &&
1453             key_value < 127 &&
1454             isprint(key_value)) {
1455                 key_char[1] = (char) key_value;
1456                 seq = key_char;
1457         }
1459         return seq ? seq : "(no key)";
1462 static bool
1463 append_key(char *buf, size_t *pos, const struct keybinding *keybinding)
1465         const char *sep = *pos > 0 ? ", " : "";
1466         const char *keyname = get_key_name(keybinding->alias);
1468         return string_nformat(buf, BUFSIZ, pos, "%s%s", sep, keyname);
1471 static bool
1472 append_keymap_request_keys(char *buf, size_t *pos, enum request request,
1473                            enum keymap keymap, bool all)
1475         int i;
1477         for (i = 0; i < keybindings[keymap].size; i++) {
1478                 if (keybindings[keymap].data[i].request == request) {
1479                         if (!append_key(buf, pos, &keybindings[keymap].data[i]))
1480                                 return FALSE;
1481                         if (!all)
1482                                 break;
1483                 }
1484         }
1486         return TRUE;
1489 #define get_key(keymap, request) get_keys(keymap, request, FALSE)
1491 static const char *
1492 get_keys(enum keymap keymap, enum request request, bool all)
1494         static char buf[BUFSIZ];
1495         size_t pos = 0;
1496         int i;
1498         buf[pos] = 0;
1500         if (!append_keymap_request_keys(buf, &pos, request, keymap, all))
1501                 return "Too many keybindings!";
1502         if (pos > 0 && !all)
1503                 return buf;
1505         if (keymap != KEYMAP_GENERIC) {
1506                 /* Only the generic keymap includes the default keybindings when
1507                  * listing all keys. */
1508                 if (all)
1509                         return buf;
1511                 if (!append_keymap_request_keys(buf, &pos, request, KEYMAP_GENERIC, all))
1512                         return "Too many keybindings!";
1513                 if (pos)
1514                         return buf;
1515         }
1517         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1518                 if (default_keybindings[i].request == request) {
1519                         if (!append_key(buf, &pos, &default_keybindings[i]))
1520                                 return "Too many keybindings!";
1521                         if (!all)
1522                                 return buf;
1523                 }
1524         }
1526         return buf;
1529 struct run_request {
1530         enum keymap keymap;
1531         int key;
1532         const char *argv[SIZEOF_ARG];
1533 };
1535 static struct run_request *run_request;
1536 static size_t run_requests;
1538 DEFINE_ALLOCATOR(realloc_run_requests, struct run_request, 8)
1540 static enum request
1541 add_run_request(enum keymap keymap, int key, int argc, const char **argv)
1543         struct run_request *req;
1545         if (argc >= ARRAY_SIZE(req->argv) - 1)
1546                 return REQ_NONE;
1548         if (!realloc_run_requests(&run_request, run_requests, 1))
1549                 return REQ_NONE;
1551         req = &run_request[run_requests];
1552         req->keymap = keymap;
1553         req->key = key;
1554         req->argv[0] = NULL;
1556         if (!format_argv(req->argv, argv, FORMAT_NONE))
1557                 return REQ_NONE;
1559         return REQ_NONE + ++run_requests;
1562 static struct run_request *
1563 get_run_request(enum request request)
1565         if (request <= REQ_NONE)
1566                 return NULL;
1567         return &run_request[request - REQ_NONE - 1];
1570 static void
1571 add_builtin_run_requests(void)
1573         const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1574         const char *commit[] = { "git", "commit", NULL };
1575         const char *gc[] = { "git", "gc", NULL };
1576         struct {
1577                 enum keymap keymap;
1578                 int key;
1579                 int argc;
1580                 const char **argv;
1581         } reqs[] = {
1582                 { KEYMAP_MAIN,    'C', ARRAY_SIZE(cherry_pick) - 1, cherry_pick },
1583                 { KEYMAP_STATUS,  'C', ARRAY_SIZE(commit) - 1, commit },
1584                 { KEYMAP_GENERIC, 'G', ARRAY_SIZE(gc) - 1, gc },
1585         };
1586         int i;
1588         for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1589                 enum request req;
1591                 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argc, reqs[i].argv);
1592                 if (req != REQ_NONE)
1593                         add_keybinding(reqs[i].keymap, req, reqs[i].key);
1594         }
1597 /*
1598  * User config file handling.
1599  */
1601 static int   config_lineno;
1602 static bool  config_errors;
1603 static const char *config_msg;
1605 static const struct enum_map color_map[] = {
1606 #define COLOR_MAP(name) ENUM_MAP(#name, COLOR_##name)
1607         COLOR_MAP(DEFAULT),
1608         COLOR_MAP(BLACK),
1609         COLOR_MAP(BLUE),
1610         COLOR_MAP(CYAN),
1611         COLOR_MAP(GREEN),
1612         COLOR_MAP(MAGENTA),
1613         COLOR_MAP(RED),
1614         COLOR_MAP(WHITE),
1615         COLOR_MAP(YELLOW),
1616 };
1618 static const struct enum_map attr_map[] = {
1619 #define ATTR_MAP(name) ENUM_MAP(#name, A_##name)
1620         ATTR_MAP(NORMAL),
1621         ATTR_MAP(BLINK),
1622         ATTR_MAP(BOLD),
1623         ATTR_MAP(DIM),
1624         ATTR_MAP(REVERSE),
1625         ATTR_MAP(STANDOUT),
1626         ATTR_MAP(UNDERLINE),
1627 };
1629 #define set_attribute(attr, name)       map_enum(attr, attr_map, name)
1631 static int parse_step(double *opt, const char *arg)
1633         *opt = atoi(arg);
1634         if (!strchr(arg, '%'))
1635                 return OK;
1637         /* "Shift down" so 100% and 1 does not conflict. */
1638         *opt = (*opt - 1) / 100;
1639         if (*opt >= 1.0) {
1640                 *opt = 0.99;
1641                 config_msg = "Step value larger than 100%";
1642                 return ERR;
1643         }
1644         if (*opt < 0.0) {
1645                 *opt = 1;
1646                 config_msg = "Invalid step value";
1647                 return ERR;
1648         }
1649         return OK;
1652 static int
1653 parse_int(int *opt, const char *arg, int min, int max)
1655         int value = atoi(arg);
1657         if (min <= value && value <= max) {
1658                 *opt = value;
1659                 return OK;
1660         }
1662         config_msg = "Integer value out of bound";
1663         return ERR;
1666 static bool
1667 set_color(int *color, const char *name)
1669         if (map_enum(color, color_map, name))
1670                 return TRUE;
1671         if (!prefixcmp(name, "color"))
1672                 return parse_int(color, name + 5, 0, 255) == OK;
1673         return FALSE;
1676 /* Wants: object fgcolor bgcolor [attribute] */
1677 static int
1678 option_color_command(int argc, const char *argv[])
1680         struct line_info *info;
1682         if (argc < 3) {
1683                 config_msg = "Wrong number of arguments given to color command";
1684                 return ERR;
1685         }
1687         info = get_line_info(argv[0]);
1688         if (!info) {
1689                 static const struct enum_map obsolete[] = {
1690                         ENUM_MAP("main-delim",  LINE_DELIMITER),
1691                         ENUM_MAP("main-date",   LINE_DATE),
1692                         ENUM_MAP("main-author", LINE_AUTHOR),
1693                 };
1694                 int index;
1696                 if (!map_enum(&index, obsolete, argv[0])) {
1697                         config_msg = "Unknown color name";
1698                         return ERR;
1699                 }
1700                 info = &line_info[index];
1701         }
1703         if (!set_color(&info->fg, argv[1]) ||
1704             !set_color(&info->bg, argv[2])) {
1705                 config_msg = "Unknown color";
1706                 return ERR;
1707         }
1709         info->attr = 0;
1710         while (argc-- > 3) {
1711                 int attr;
1713                 if (!set_attribute(&attr, argv[argc])) {
1714                         config_msg = "Unknown attribute";
1715                         return ERR;
1716                 }
1717                 info->attr |= attr;
1718         }
1720         return OK;
1723 static int parse_bool(bool *opt, const char *arg)
1725         *opt = (!strcmp(arg, "1") || !strcmp(arg, "true") || !strcmp(arg, "yes"))
1726                 ? TRUE : FALSE;
1727         return OK;
1730 static int parse_enum_do(unsigned int *opt, const char *arg,
1731                          const struct enum_map *map, size_t map_size)
1733         bool is_true;
1735         assert(map_size > 1);
1737         if (map_enum_do(map, map_size, (int *) opt, arg))
1738                 return OK;
1740         if (parse_bool(&is_true, arg) != OK)
1741                 return ERR;
1743         *opt = is_true ? map[1].value : map[0].value;
1744         return OK;
1747 #define parse_enum(opt, arg, map) \
1748         parse_enum_do(opt, arg, map, ARRAY_SIZE(map))
1750 static int
1751 parse_string(char *opt, const char *arg, size_t optsize)
1753         int arglen = strlen(arg);
1755         switch (arg[0]) {
1756         case '\"':
1757         case '\'':
1758                 if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1759                         config_msg = "Unmatched quotation";
1760                         return ERR;
1761                 }
1762                 arg += 1; arglen -= 2;
1763         default:
1764                 string_ncopy_do(opt, optsize, arg, arglen);
1765                 return OK;
1766         }
1769 /* Wants: name = value */
1770 static int
1771 option_set_command(int argc, const char *argv[])
1773         if (argc != 3) {
1774                 config_msg = "Wrong number of arguments given to set command";
1775                 return ERR;
1776         }
1778         if (strcmp(argv[1], "=")) {
1779                 config_msg = "No value assigned";
1780                 return ERR;
1781         }
1783         if (!strcmp(argv[0], "show-author"))
1784                 return parse_enum(&opt_author, argv[2], author_map);
1786         if (!strcmp(argv[0], "show-date"))
1787                 return parse_enum(&opt_date, argv[2], date_map);
1789         if (!strcmp(argv[0], "show-rev-graph"))
1790                 return parse_bool(&opt_rev_graph, argv[2]);
1792         if (!strcmp(argv[0], "show-refs"))
1793                 return parse_bool(&opt_show_refs, argv[2]);
1795         if (!strcmp(argv[0], "show-line-numbers"))
1796                 return parse_bool(&opt_line_number, argv[2]);
1798         if (!strcmp(argv[0], "line-graphics"))
1799                 return parse_bool(&opt_line_graphics, argv[2]);
1801         if (!strcmp(argv[0], "line-number-interval"))
1802                 return parse_int(&opt_num_interval, argv[2], 1, 1024);
1804         if (!strcmp(argv[0], "author-width"))
1805                 return parse_int(&opt_author_cols, argv[2], 0, 1024);
1807         if (!strcmp(argv[0], "horizontal-scroll"))
1808                 return parse_step(&opt_hscroll, argv[2]);
1810         if (!strcmp(argv[0], "split-view-height"))
1811                 return parse_step(&opt_scale_split_view, argv[2]);
1813         if (!strcmp(argv[0], "tab-size"))
1814                 return parse_int(&opt_tab_size, argv[2], 1, 1024);
1816         if (!strcmp(argv[0], "commit-encoding"))
1817                 return parse_string(opt_encoding, argv[2], sizeof(opt_encoding));
1819         config_msg = "Unknown variable name";
1820         return ERR;
1823 /* Wants: mode request key */
1824 static int
1825 option_bind_command(int argc, const char *argv[])
1827         enum request request;
1828         int keymap = -1;
1829         int key;
1831         if (argc < 3) {
1832                 config_msg = "Wrong number of arguments given to bind command";
1833                 return ERR;
1834         }
1836         if (set_keymap(&keymap, argv[0]) == ERR) {
1837                 config_msg = "Unknown key map";
1838                 return ERR;
1839         }
1841         key = get_key_value(argv[1]);
1842         if (key == ERR) {
1843                 config_msg = "Unknown key";
1844                 return ERR;
1845         }
1847         request = get_request(argv[2]);
1848         if (request == REQ_NONE) {
1849                 static const struct enum_map obsolete[] = {
1850                         ENUM_MAP("cherry-pick",         REQ_NONE),
1851                         ENUM_MAP("screen-resize",       REQ_NONE),
1852                         ENUM_MAP("tree-parent",         REQ_PARENT),
1853                 };
1854                 int alias;
1856                 if (map_enum(&alias, obsolete, argv[2])) {
1857                         if (alias != REQ_NONE)
1858                                 add_keybinding(keymap, alias, key);
1859                         config_msg = "Obsolete request name";
1860                         return ERR;
1861                 }
1862         }
1863         if (request == REQ_NONE && *argv[2]++ == '!')
1864                 request = add_run_request(keymap, key, argc - 2, argv + 2);
1865         if (request == REQ_NONE) {
1866                 config_msg = "Unknown request name";
1867                 return ERR;
1868         }
1870         add_keybinding(keymap, request, key);
1872         return OK;
1875 static int
1876 set_option(const char *opt, char *value)
1878         const char *argv[SIZEOF_ARG];
1879         int argc = 0;
1881         if (!argv_from_string(argv, &argc, value)) {
1882                 config_msg = "Too many option arguments";
1883                 return ERR;
1884         }
1886         if (!strcmp(opt, "color"))
1887                 return option_color_command(argc, argv);
1889         if (!strcmp(opt, "set"))
1890                 return option_set_command(argc, argv);
1892         if (!strcmp(opt, "bind"))
1893                 return option_bind_command(argc, argv);
1895         config_msg = "Unknown option command";
1896         return ERR;
1899 static int
1900 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
1902         int status = OK;
1904         config_lineno++;
1905         config_msg = "Internal error";
1907         /* Check for comment markers, since read_properties() will
1908          * only ensure opt and value are split at first " \t". */
1909         optlen = strcspn(opt, "#");
1910         if (optlen == 0)
1911                 return OK;
1913         if (opt[optlen] != 0) {
1914                 config_msg = "No option value";
1915                 status = ERR;
1917         }  else {
1918                 /* Look for comment endings in the value. */
1919                 size_t len = strcspn(value, "#");
1921                 if (len < valuelen) {
1922                         valuelen = len;
1923                         value[valuelen] = 0;
1924                 }
1926                 status = set_option(opt, value);
1927         }
1929         if (status == ERR) {
1930                 warn("Error on line %d, near '%.*s': %s",
1931                      config_lineno, (int) optlen, opt, config_msg);
1932                 config_errors = TRUE;
1933         }
1935         /* Always keep going if errors are encountered. */
1936         return OK;
1939 static void
1940 load_option_file(const char *path)
1942         struct io io = {};
1944         /* It's OK that the file doesn't exist. */
1945         if (!io_open(&io, "%s", path))
1946                 return;
1948         config_lineno = 0;
1949         config_errors = FALSE;
1951         if (io_load(&io, " \t", read_option) == ERR ||
1952             config_errors == TRUE)
1953                 warn("Errors while loading %s.", path);
1956 static int
1957 load_options(void)
1959         const char *home = getenv("HOME");
1960         const char *tigrc_user = getenv("TIGRC_USER");
1961         const char *tigrc_system = getenv("TIGRC_SYSTEM");
1962         char buf[SIZEOF_STR];
1964         add_builtin_run_requests();
1966         if (!tigrc_system)
1967                 tigrc_system = SYSCONFDIR "/tigrc";
1968         load_option_file(tigrc_system);
1970         if (!tigrc_user) {
1971                 if (!home || !string_format(buf, "%s/.tigrc", home))
1972                         return ERR;
1973                 tigrc_user = buf;
1974         }
1975         load_option_file(tigrc_user);
1977         return OK;
1981 /*
1982  * The viewer
1983  */
1985 struct view;
1986 struct view_ops;
1988 /* The display array of active views and the index of the current view. */
1989 static struct view *display[2];
1990 static unsigned int current_view;
1992 #define foreach_displayed_view(view, i) \
1993         for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1995 #define displayed_views()       (display[1] != NULL ? 2 : 1)
1997 /* Current head and commit ID */
1998 static char ref_blob[SIZEOF_REF]        = "";
1999 static char ref_commit[SIZEOF_REF]      = "HEAD";
2000 static char ref_head[SIZEOF_REF]        = "HEAD";
2002 struct view {
2003         const char *name;       /* View name */
2004         const char *cmd_env;    /* Command line set via environment */
2005         const char *id;         /* Points to either of ref_{head,commit,blob} */
2007         struct view_ops *ops;   /* View operations */
2009         enum keymap keymap;     /* What keymap does this view have */
2010         bool git_dir;           /* Whether the view requires a git directory. */
2012         char ref[SIZEOF_REF];   /* Hovered commit reference */
2013         char vid[SIZEOF_REF];   /* View ID. Set to id member when updating. */
2015         int height, width;      /* The width and height of the main window */
2016         WINDOW *win;            /* The main window */
2017         WINDOW *title;          /* The title window living below the main window */
2019         /* Navigation */
2020         unsigned long offset;   /* Offset of the window top */
2021         unsigned long yoffset;  /* Offset from the window side. */
2022         unsigned long lineno;   /* Current line number */
2023         unsigned long p_offset; /* Previous offset of the window top */
2024         unsigned long p_yoffset;/* Previous offset from the window side */
2025         unsigned long p_lineno; /* Previous current line number */
2026         bool p_restore;         /* Should the previous position be restored. */
2028         /* Searching */
2029         char grep[SIZEOF_STR];  /* Search string */
2030         regex_t *regex;         /* Pre-compiled regexp */
2032         /* If non-NULL, points to the view that opened this view. If this view
2033          * is closed tig will switch back to the parent view. */
2034         struct view *parent;
2036         /* Buffering */
2037         size_t lines;           /* Total number of lines */
2038         struct line *line;      /* Line index */
2039         unsigned int digits;    /* Number of digits in the lines member. */
2041         /* Drawing */
2042         struct line *curline;   /* Line currently being drawn. */
2043         enum line_type curtype; /* Attribute currently used for drawing. */
2044         unsigned long col;      /* Column when drawing. */
2045         bool has_scrolled;      /* View was scrolled. */
2047         /* Loading */
2048         struct io io;
2049         struct io *pipe;
2050         time_t start_time;
2051         time_t update_secs;
2052 };
2054 struct view_ops {
2055         /* What type of content being displayed. Used in the title bar. */
2056         const char *type;
2057         /* Default command arguments. */
2058         const char **argv;
2059         /* Open and reads in all view content. */
2060         bool (*open)(struct view *view);
2061         /* Read one line; updates view->line. */
2062         bool (*read)(struct view *view, char *data);
2063         /* Draw one line; @lineno must be < view->height. */
2064         bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
2065         /* Depending on view handle a special requests. */
2066         enum request (*request)(struct view *view, enum request request, struct line *line);
2067         /* Search for regexp in a line. */
2068         bool (*grep)(struct view *view, struct line *line);
2069         /* Select line */
2070         void (*select)(struct view *view, struct line *line);
2071         /* Prepare view for loading */
2072         bool (*prepare)(struct view *view);
2073 };
2075 static struct view_ops blame_ops;
2076 static struct view_ops blob_ops;
2077 static struct view_ops diff_ops;
2078 static struct view_ops help_ops;
2079 static struct view_ops log_ops;
2080 static struct view_ops main_ops;
2081 static struct view_ops pager_ops;
2082 static struct view_ops stage_ops;
2083 static struct view_ops status_ops;
2084 static struct view_ops tree_ops;
2085 static struct view_ops branch_ops;
2087 #define VIEW_STR(name, env, ref, ops, map, git) \
2088         { name, #env, ref, ops, map, git }
2090 #define VIEW_(id, name, ops, git, ref) \
2091         VIEW_STR(name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
2094 static struct view views[] = {
2095         VIEW_(MAIN,   "main",   &main_ops,   TRUE,  ref_head),
2096         VIEW_(DIFF,   "diff",   &diff_ops,   TRUE,  ref_commit),
2097         VIEW_(LOG,    "log",    &log_ops,    TRUE,  ref_head),
2098         VIEW_(TREE,   "tree",   &tree_ops,   TRUE,  ref_commit),
2099         VIEW_(BLOB,   "blob",   &blob_ops,   TRUE,  ref_blob),
2100         VIEW_(BLAME,  "blame",  &blame_ops,  TRUE,  ref_commit),
2101         VIEW_(BRANCH, "branch", &branch_ops, TRUE,  ref_head),
2102         VIEW_(HELP,   "help",   &help_ops,   FALSE, ""),
2103         VIEW_(PAGER,  "pager",  &pager_ops,  FALSE, "stdin"),
2104         VIEW_(STATUS, "status", &status_ops, TRUE,  ""),
2105         VIEW_(STAGE,  "stage",  &stage_ops,  TRUE,  ""),
2106 };
2108 #define VIEW(req)       (&views[(req) - REQ_OFFSET - 1])
2109 #define VIEW_REQ(view)  ((view) - views + REQ_OFFSET + 1)
2111 #define foreach_view(view, i) \
2112         for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
2114 #define view_is_displayed(view) \
2115         (view == display[0] || view == display[1])
2118 static inline void
2119 set_view_attr(struct view *view, enum line_type type)
2121         if (!view->curline->selected && view->curtype != type) {
2122                 (void) wattrset(view->win, get_line_attr(type));
2123                 wchgat(view->win, -1, 0, type, NULL);
2124                 view->curtype = type;
2125         }
2128 static int
2129 draw_chars(struct view *view, enum line_type type, const char *string,
2130            int max_len, bool use_tilde)
2132         static char out_buffer[BUFSIZ * 2];
2133         int len = 0;
2134         int col = 0;
2135         int trimmed = FALSE;
2136         size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2138         if (max_len <= 0)
2139                 return 0;
2141         len = utf8_length(&string, skip, &col, max_len, &trimmed, use_tilde, opt_tab_size);
2143         set_view_attr(view, type);
2144         if (len > 0) {
2145                 if (opt_iconv_out != ICONV_NONE) {
2146                         ICONV_CONST char *inbuf = (ICONV_CONST char *) string;
2147                         size_t inlen = len + 1;
2149                         char *outbuf = out_buffer;
2150                         size_t outlen = sizeof(out_buffer);
2152                         size_t ret;
2154                         ret = iconv(opt_iconv_out, &inbuf, &inlen, &outbuf, &outlen);
2155                         if (ret != (size_t) -1) {
2156                                 string = out_buffer;
2157                                 len = sizeof(out_buffer) - outlen;
2158                         }
2159                 }
2161                 waddnstr(view->win, string, len);
2162         }
2163         if (trimmed && use_tilde) {
2164                 set_view_attr(view, LINE_DELIMITER);
2165                 waddch(view->win, '~');
2166                 col++;
2167         }
2169         return col;
2172 static int
2173 draw_space(struct view *view, enum line_type type, int max, int spaces)
2175         static char space[] = "                    ";
2176         int col = 0;
2178         spaces = MIN(max, spaces);
2180         while (spaces > 0) {
2181                 int len = MIN(spaces, sizeof(space) - 1);
2183                 col += draw_chars(view, type, space, len, FALSE);
2184                 spaces -= len;
2185         }
2187         return col;
2190 static bool
2191 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
2193         view->col += draw_chars(view, type, string, view->width + view->yoffset - view->col, trim);
2194         return view->width + view->yoffset <= view->col;
2197 static bool
2198 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
2200         size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2201         int max = view->width + view->yoffset - view->col;
2202         int i;
2204         if (max < size)
2205                 size = max;
2207         set_view_attr(view, type);
2208         /* Using waddch() instead of waddnstr() ensures that
2209          * they'll be rendered correctly for the cursor line. */
2210         for (i = skip; i < size; i++)
2211                 waddch(view->win, graphic[i]);
2213         view->col += size;
2214         if (size < max && skip <= size)
2215                 waddch(view->win, ' ');
2216         view->col++;
2218         return view->width + view->yoffset <= view->col;
2221 static bool
2222 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
2224         int max = MIN(view->width + view->yoffset - view->col, len);
2225         int col;
2227         if (text)
2228                 col = draw_chars(view, type, text, max - 1, trim);
2229         else
2230                 col = draw_space(view, type, max - 1, max - 1);
2232         view->col += col;
2233         view->col += draw_space(view, LINE_DEFAULT, max - col, max - col);
2234         return view->width + view->yoffset <= view->col;
2237 static bool
2238 draw_date(struct view *view, struct time *time)
2240         const char *date = mkdate(time, opt_date);
2241         int cols = opt_date == DATE_SHORT ? DATE_SHORT_COLS : DATE_COLS;
2243         return draw_field(view, LINE_DATE, date, cols, FALSE);
2246 static bool
2247 draw_author(struct view *view, const char *author)
2249         bool trim = opt_author_cols == 0 || opt_author_cols > 5;
2250         bool abbreviate = opt_author == AUTHOR_ABBREVIATED || !trim;
2252         if (abbreviate && author)
2253                 author = get_author_initials(author);
2255         return draw_field(view, LINE_AUTHOR, author, opt_author_cols, trim);
2258 static bool
2259 draw_mode(struct view *view, mode_t mode)
2261         const char *str;
2263         if (S_ISDIR(mode))
2264                 str = "drwxr-xr-x";
2265         else if (S_ISLNK(mode))
2266                 str = "lrwxrwxrwx";
2267         else if (S_ISGITLINK(mode))
2268                 str = "m---------";
2269         else if (S_ISREG(mode) && mode & S_IXUSR)
2270                 str = "-rwxr-xr-x";
2271         else if (S_ISREG(mode))
2272                 str = "-rw-r--r--";
2273         else
2274                 str = "----------";
2276         return draw_field(view, LINE_MODE, str, STRING_SIZE("-rw-r--r-- "), FALSE);
2279 static bool
2280 draw_lineno(struct view *view, unsigned int lineno)
2282         char number[10];
2283         int digits3 = view->digits < 3 ? 3 : view->digits;
2284         int max = MIN(view->width + view->yoffset - view->col, digits3);
2285         char *text = NULL;
2286         chtype separator = opt_line_graphics ? ACS_VLINE : '|';
2288         lineno += view->offset + 1;
2289         if (lineno == 1 || (lineno % opt_num_interval) == 0) {
2290                 static char fmt[] = "%1ld";
2292                 fmt[1] = '0' + (view->digits <= 9 ? digits3 : 1);
2293                 if (string_format(number, fmt, lineno))
2294                         text = number;
2295         }
2296         if (text)
2297                 view->col += draw_chars(view, LINE_LINE_NUMBER, text, max, TRUE);
2298         else
2299                 view->col += draw_space(view, LINE_LINE_NUMBER, max, digits3);
2300         return draw_graphic(view, LINE_DEFAULT, &separator, 1);
2303 static bool
2304 draw_view_line(struct view *view, unsigned int lineno)
2306         struct line *line;
2307         bool selected = (view->offset + lineno == view->lineno);
2309         assert(view_is_displayed(view));
2311         if (view->offset + lineno >= view->lines)
2312                 return FALSE;
2314         line = &view->line[view->offset + lineno];
2316         wmove(view->win, lineno, 0);
2317         if (line->cleareol)
2318                 wclrtoeol(view->win);
2319         view->col = 0;
2320         view->curline = line;
2321         view->curtype = LINE_NONE;
2322         line->selected = FALSE;
2323         line->dirty = line->cleareol = 0;
2325         if (selected) {
2326                 set_view_attr(view, LINE_CURSOR);
2327                 line->selected = TRUE;
2328                 view->ops->select(view, line);
2329         }
2331         return view->ops->draw(view, line, lineno);
2334 static void
2335 redraw_view_dirty(struct view *view)
2337         bool dirty = FALSE;
2338         int lineno;
2340         for (lineno = 0; lineno < view->height; lineno++) {
2341                 if (view->offset + lineno >= view->lines)
2342                         break;
2343                 if (!view->line[view->offset + lineno].dirty)
2344                         continue;
2345                 dirty = TRUE;
2346                 if (!draw_view_line(view, lineno))
2347                         break;
2348         }
2350         if (!dirty)
2351                 return;
2352         wnoutrefresh(view->win);
2355 static void
2356 redraw_view_from(struct view *view, int lineno)
2358         assert(0 <= lineno && lineno < view->height);
2360         for (; lineno < view->height; lineno++) {
2361                 if (!draw_view_line(view, lineno))
2362                         break;
2363         }
2365         wnoutrefresh(view->win);
2368 static void
2369 redraw_view(struct view *view)
2371         werase(view->win);
2372         redraw_view_from(view, 0);
2376 static void
2377 update_view_title(struct view *view)
2379         char buf[SIZEOF_STR];
2380         char state[SIZEOF_STR];
2381         size_t bufpos = 0, statelen = 0;
2383         assert(view_is_displayed(view));
2385         if (view != VIEW(REQ_VIEW_STATUS) && view->lines) {
2386                 unsigned int view_lines = view->offset + view->height;
2387                 unsigned int lines = view->lines
2388                                    ? MIN(view_lines, view->lines) * 100 / view->lines
2389                                    : 0;
2391                 string_format_from(state, &statelen, " - %s %d of %d (%d%%)",
2392                                    view->ops->type,
2393                                    view->lineno + 1,
2394                                    view->lines,
2395                                    lines);
2397         }
2399         if (view->pipe) {
2400                 time_t secs = time(NULL) - view->start_time;
2402                 /* Three git seconds are a long time ... */
2403                 if (secs > 2)
2404                         string_format_from(state, &statelen, " loading %lds", secs);
2405         }
2407         string_format_from(buf, &bufpos, "[%s]", view->name);
2408         if (*view->ref && bufpos < view->width) {
2409                 size_t refsize = strlen(view->ref);
2410                 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2412                 if (minsize < view->width)
2413                         refsize = view->width - minsize + 7;
2414                 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2415         }
2417         if (statelen && bufpos < view->width) {
2418                 string_format_from(buf, &bufpos, "%s", state);
2419         }
2421         if (view == display[current_view])
2422                 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2423         else
2424                 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2426         mvwaddnstr(view->title, 0, 0, buf, bufpos);
2427         wclrtoeol(view->title);
2428         wnoutrefresh(view->title);
2431 static int
2432 apply_step(double step, int value)
2434         if (step >= 1)
2435                 return (int) step;
2436         value *= step + 0.01;
2437         return value ? value : 1;
2440 static void
2441 resize_display(void)
2443         int offset, i;
2444         struct view *base = display[0];
2445         struct view *view = display[1] ? display[1] : display[0];
2447         /* Setup window dimensions */
2449         getmaxyx(stdscr, base->height, base->width);
2451         /* Make room for the status window. */
2452         base->height -= 1;
2454         if (view != base) {
2455                 /* Horizontal split. */
2456                 view->width   = base->width;
2457                 view->height  = apply_step(opt_scale_split_view, base->height);
2458                 view->height  = MAX(view->height, MIN_VIEW_HEIGHT);
2459                 view->height  = MIN(view->height, base->height - MIN_VIEW_HEIGHT);
2460                 base->height -= view->height;
2462                 /* Make room for the title bar. */
2463                 view->height -= 1;
2464         }
2466         /* Make room for the title bar. */
2467         base->height -= 1;
2469         offset = 0;
2471         foreach_displayed_view (view, i) {
2472                 if (!view->win) {
2473                         view->win = newwin(view->height, 0, offset, 0);
2474                         if (!view->win)
2475                                 die("Failed to create %s view", view->name);
2477                         scrollok(view->win, FALSE);
2479                         view->title = newwin(1, 0, offset + view->height, 0);
2480                         if (!view->title)
2481                                 die("Failed to create title window");
2483                 } else {
2484                         wresize(view->win, view->height, view->width);
2485                         mvwin(view->win,   offset, 0);
2486                         mvwin(view->title, offset + view->height, 0);
2487                 }
2489                 offset += view->height + 1;
2490         }
2493 static void
2494 redraw_display(bool clear)
2496         struct view *view;
2497         int i;
2499         foreach_displayed_view (view, i) {
2500                 if (clear)
2501                         wclear(view->win);
2502                 redraw_view(view);
2503                 update_view_title(view);
2504         }
2507 static void
2508 toggle_enum_option_do(unsigned int *opt, const char *help,
2509                       const struct enum_map *map, size_t size)
2511         *opt = (*opt + 1) % size;
2512         redraw_display(FALSE);
2513         report("Displaying %s %s", enum_name(map[*opt]), help);
2516 #define toggle_enum_option(opt, help, map) \
2517         toggle_enum_option_do(opt, help, map, ARRAY_SIZE(map))
2519 #define toggle_date() toggle_enum_option(&opt_date, "dates", date_map)
2520 #define toggle_author() toggle_enum_option(&opt_author, "author names", author_map)
2522 static void
2523 toggle_view_option(bool *option, const char *help)
2525         *option = !*option;
2526         redraw_display(FALSE);
2527         report("%sabling %s", *option ? "En" : "Dis", help);
2530 static void
2531 open_option_menu(void)
2533         const struct menu_item menu[] = {
2534                 { '.', "line numbers", &opt_line_number },
2535                 { 'D', "date display", &opt_date },
2536                 { 'A', "author display", &opt_author },
2537                 { 'g', "revision graph display", &opt_rev_graph },
2538                 { 'F', "reference display", &opt_show_refs },
2539                 { 0 }
2540         };
2541         int selected = 0;
2543         if (prompt_menu("Toggle option", menu, &selected)) {
2544                 if (menu[selected].data == &opt_date)
2545                         toggle_date();
2546                 else if (menu[selected].data == &opt_author)
2547                         toggle_author();
2548                 else
2549                         toggle_view_option(menu[selected].data, menu[selected].text);
2550         }
2553 static void
2554 maximize_view(struct view *view)
2556         memset(display, 0, sizeof(display));
2557         current_view = 0;
2558         display[current_view] = view;
2559         resize_display();
2560         redraw_display(FALSE);
2561         report("");
2565 /*
2566  * Navigation
2567  */
2569 static bool
2570 goto_view_line(struct view *view, unsigned long offset, unsigned long lineno)
2572         if (lineno >= view->lines)
2573                 lineno = view->lines > 0 ? view->lines - 1 : 0;
2575         if (offset > lineno || offset + view->height <= lineno) {
2576                 unsigned long half = view->height / 2;
2578                 if (lineno > half)
2579                         offset = lineno - half;
2580                 else
2581                         offset = 0;
2582         }
2584         if (offset != view->offset || lineno != view->lineno) {
2585                 view->offset = offset;
2586                 view->lineno = lineno;
2587                 return TRUE;
2588         }
2590         return FALSE;
2593 /* Scrolling backend */
2594 static void
2595 do_scroll_view(struct view *view, int lines)
2597         bool redraw_current_line = FALSE;
2599         /* The rendering expects the new offset. */
2600         view->offset += lines;
2602         assert(0 <= view->offset && view->offset < view->lines);
2603         assert(lines);
2605         /* Move current line into the view. */
2606         if (view->lineno < view->offset) {
2607                 view->lineno = view->offset;
2608                 redraw_current_line = TRUE;
2609         } else if (view->lineno >= view->offset + view->height) {
2610                 view->lineno = view->offset + view->height - 1;
2611                 redraw_current_line = TRUE;
2612         }
2614         assert(view->offset <= view->lineno && view->lineno < view->lines);
2616         /* Redraw the whole screen if scrolling is pointless. */
2617         if (view->height < ABS(lines)) {
2618                 redraw_view(view);
2620         } else {
2621                 int line = lines > 0 ? view->height - lines : 0;
2622                 int end = line + ABS(lines);
2624                 scrollok(view->win, TRUE);
2625                 wscrl(view->win, lines);
2626                 scrollok(view->win, FALSE);
2628                 while (line < end && draw_view_line(view, line))
2629                         line++;
2631                 if (redraw_current_line)
2632                         draw_view_line(view, view->lineno - view->offset);
2633                 wnoutrefresh(view->win);
2634         }
2636         view->has_scrolled = TRUE;
2637         report("");
2640 /* Scroll frontend */
2641 static void
2642 scroll_view(struct view *view, enum request request)
2644         int lines = 1;
2646         assert(view_is_displayed(view));
2648         switch (request) {
2649         case REQ_SCROLL_LEFT:
2650                 if (view->yoffset == 0) {
2651                         report("Cannot scroll beyond the first column");
2652                         return;
2653                 }
2654                 if (view->yoffset <= apply_step(opt_hscroll, view->width))
2655                         view->yoffset = 0;
2656                 else
2657                         view->yoffset -= apply_step(opt_hscroll, view->width);
2658                 redraw_view_from(view, 0);
2659                 report("");
2660                 return;
2661         case REQ_SCROLL_RIGHT:
2662                 view->yoffset += apply_step(opt_hscroll, view->width);
2663                 redraw_view(view);
2664                 report("");
2665                 return;
2666         case REQ_SCROLL_PAGE_DOWN:
2667                 lines = view->height;
2668         case REQ_SCROLL_LINE_DOWN:
2669                 if (view->offset + lines > view->lines)
2670                         lines = view->lines - view->offset;
2672                 if (lines == 0 || view->offset + view->height >= view->lines) {
2673                         report("Cannot scroll beyond the last line");
2674                         return;
2675                 }
2676                 break;
2678         case REQ_SCROLL_PAGE_UP:
2679                 lines = view->height;
2680         case REQ_SCROLL_LINE_UP:
2681                 if (lines > view->offset)
2682                         lines = view->offset;
2684                 if (lines == 0) {
2685                         report("Cannot scroll beyond the first line");
2686                         return;
2687                 }
2689                 lines = -lines;
2690                 break;
2692         default:
2693                 die("request %d not handled in switch", request);
2694         }
2696         do_scroll_view(view, lines);
2699 /* Cursor moving */
2700 static void
2701 move_view(struct view *view, enum request request)
2703         int scroll_steps = 0;
2704         int steps;
2706         switch (request) {
2707         case REQ_MOVE_FIRST_LINE:
2708                 steps = -view->lineno;
2709                 break;
2711         case REQ_MOVE_LAST_LINE:
2712                 steps = view->lines - view->lineno - 1;
2713                 break;
2715         case REQ_MOVE_PAGE_UP:
2716                 steps = view->height > view->lineno
2717                       ? -view->lineno : -view->height;
2718                 break;
2720         case REQ_MOVE_PAGE_DOWN:
2721                 steps = view->lineno + view->height >= view->lines
2722                       ? view->lines - view->lineno - 1 : view->height;
2723                 break;
2725         case REQ_MOVE_UP:
2726                 steps = -1;
2727                 break;
2729         case REQ_MOVE_DOWN:
2730                 steps = 1;
2731                 break;
2733         default:
2734                 die("request %d not handled in switch", request);
2735         }
2737         if (steps <= 0 && view->lineno == 0) {
2738                 report("Cannot move beyond the first line");
2739                 return;
2741         } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2742                 report("Cannot move beyond the last line");
2743                 return;
2744         }
2746         /* Move the current line */
2747         view->lineno += steps;
2748         assert(0 <= view->lineno && view->lineno < view->lines);
2750         /* Check whether the view needs to be scrolled */
2751         if (view->lineno < view->offset ||
2752             view->lineno >= view->offset + view->height) {
2753                 scroll_steps = steps;
2754                 if (steps < 0 && -steps > view->offset) {
2755                         scroll_steps = -view->offset;
2757                 } else if (steps > 0) {
2758                         if (view->lineno == view->lines - 1 &&
2759                             view->lines > view->height) {
2760                                 scroll_steps = view->lines - view->offset - 1;
2761                                 if (scroll_steps >= view->height)
2762                                         scroll_steps -= view->height - 1;
2763                         }
2764                 }
2765         }
2767         if (!view_is_displayed(view)) {
2768                 view->offset += scroll_steps;
2769                 assert(0 <= view->offset && view->offset < view->lines);
2770                 view->ops->select(view, &view->line[view->lineno]);
2771                 return;
2772         }
2774         /* Repaint the old "current" line if we be scrolling */
2775         if (ABS(steps) < view->height)
2776                 draw_view_line(view, view->lineno - steps - view->offset);
2778         if (scroll_steps) {
2779                 do_scroll_view(view, scroll_steps);
2780                 return;
2781         }
2783         /* Draw the current line */
2784         draw_view_line(view, view->lineno - view->offset);
2786         wnoutrefresh(view->win);
2787         report("");
2791 /*
2792  * Searching
2793  */
2795 static void search_view(struct view *view, enum request request);
2797 static bool
2798 grep_text(struct view *view, const char *text[])
2800         regmatch_t pmatch;
2801         size_t i;
2803         for (i = 0; text[i]; i++)
2804                 if (*text[i] &&
2805                     regexec(view->regex, text[i], 1, &pmatch, 0) != REG_NOMATCH)
2806                         return TRUE;
2807         return FALSE;
2810 static void
2811 select_view_line(struct view *view, unsigned long lineno)
2813         unsigned long old_lineno = view->lineno;
2814         unsigned long old_offset = view->offset;
2816         if (goto_view_line(view, view->offset, lineno)) {
2817                 if (view_is_displayed(view)) {
2818                         if (old_offset != view->offset) {
2819                                 redraw_view(view);
2820                         } else {
2821                                 draw_view_line(view, old_lineno - view->offset);
2822                                 draw_view_line(view, view->lineno - view->offset);
2823                                 wnoutrefresh(view->win);
2824                         }
2825                 } else {
2826                         view->ops->select(view, &view->line[view->lineno]);
2827                 }
2828         }
2831 static void
2832 find_next(struct view *view, enum request request)
2834         unsigned long lineno = view->lineno;
2835         int direction;
2837         if (!*view->grep) {
2838                 if (!*opt_search)
2839                         report("No previous search");
2840                 else
2841                         search_view(view, request);
2842                 return;
2843         }
2845         switch (request) {
2846         case REQ_SEARCH:
2847         case REQ_FIND_NEXT:
2848                 direction = 1;
2849                 break;
2851         case REQ_SEARCH_BACK:
2852         case REQ_FIND_PREV:
2853                 direction = -1;
2854                 break;
2856         default:
2857                 return;
2858         }
2860         if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
2861                 lineno += direction;
2863         /* Note, lineno is unsigned long so will wrap around in which case it
2864          * will become bigger than view->lines. */
2865         for (; lineno < view->lines; lineno += direction) {
2866                 if (view->ops->grep(view, &view->line[lineno])) {
2867                         select_view_line(view, lineno);
2868                         report("Line %ld matches '%s'", lineno + 1, view->grep);
2869                         return;
2870                 }
2871         }
2873         report("No match found for '%s'", view->grep);
2876 static void
2877 search_view(struct view *view, enum request request)
2879         int regex_err;
2881         if (view->regex) {
2882                 regfree(view->regex);
2883                 *view->grep = 0;
2884         } else {
2885                 view->regex = calloc(1, sizeof(*view->regex));
2886                 if (!view->regex)
2887                         return;
2888         }
2890         regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
2891         if (regex_err != 0) {
2892                 char buf[SIZEOF_STR] = "unknown error";
2894                 regerror(regex_err, view->regex, buf, sizeof(buf));
2895                 report("Search failed: %s", buf);
2896                 return;
2897         }
2899         string_copy(view->grep, opt_search);
2901         find_next(view, request);
2904 /*
2905  * Incremental updating
2906  */
2908 static void
2909 reset_view(struct view *view)
2911         int i;
2913         for (i = 0; i < view->lines; i++)
2914                 free(view->line[i].data);
2915         free(view->line);
2917         view->p_offset = view->offset;
2918         view->p_yoffset = view->yoffset;
2919         view->p_lineno = view->lineno;
2921         view->line = NULL;
2922         view->offset = 0;
2923         view->yoffset = 0;
2924         view->lines  = 0;
2925         view->lineno = 0;
2926         view->vid[0] = 0;
2927         view->update_secs = 0;
2930 static void
2931 free_argv(const char *argv[])
2933         int argc;
2935         for (argc = 0; argv[argc]; argc++)
2936                 free((void *) argv[argc]);
2939 static const char *
2940 format_arg(const char *name)
2942         static struct {
2943                 const char *name;
2944                 size_t namelen;
2945                 const char *value;
2946                 const char *value_if_empty;
2947         } vars[] = {
2948 #define FORMAT_VAR(name, value, value_if_empty) \
2949         { name, STRING_SIZE(name), value, value_if_empty }
2950                 FORMAT_VAR("%(directory)",      opt_path,       ""),
2951                 FORMAT_VAR("%(file)",           opt_file,       ""),
2952                 FORMAT_VAR("%(ref)",            opt_ref,        "HEAD"),
2953                 FORMAT_VAR("%(head)",           ref_head,       ""),
2954                 FORMAT_VAR("%(commit)",         ref_commit,     ""),
2955                 FORMAT_VAR("%(blob)",           ref_blob,       ""),
2956         };
2957         int i;
2959         for (i = 0; i < ARRAY_SIZE(vars); i++)
2960                 if (!strncmp(name, vars[i].name, vars[i].namelen))
2961                         return *vars[i].value ? vars[i].value : vars[i].value_if_empty;
2963         return NULL;
2965 static bool
2966 format_argv(const char *dst_argv[], const char *src_argv[], enum format_flags flags)
2968         char buf[SIZEOF_STR];
2969         int argc;
2970         bool noreplace = flags == FORMAT_NONE;
2972         free_argv(dst_argv);
2974         for (argc = 0; src_argv[argc]; argc++) {
2975                 const char *arg = src_argv[argc];
2976                 size_t bufpos = 0;
2978                 while (arg) {
2979                         char *next = strstr(arg, "%(");
2980                         int len = next - arg;
2981                         const char *value;
2983                         if (!next || noreplace) {
2984                                 if (flags == FORMAT_DASH && !strcmp(arg, "--"))
2985                                         noreplace = TRUE;
2986                                 len = strlen(arg);
2987                                 value = "";
2989                         } else {
2990                                 value = format_arg(next);
2992                                 if (!value) {
2993                                         report("Unknown replacement: `%s`", next);
2994                                         return FALSE;
2995                                 }
2996                         }
2998                         if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
2999                                 return FALSE;
3001                         arg = next && !noreplace ? strchr(next, ')') + 1 : NULL;
3002                 }
3004                 dst_argv[argc] = strdup(buf);
3005                 if (!dst_argv[argc])
3006                         break;
3007         }
3009         dst_argv[argc] = NULL;
3011         return src_argv[argc] == NULL;
3014 static bool
3015 restore_view_position(struct view *view)
3017         if (!view->p_restore || (view->pipe && view->lines <= view->p_lineno))
3018                 return FALSE;
3020         /* Changing the view position cancels the restoring. */
3021         /* FIXME: Changing back to the first line is not detected. */
3022         if (view->offset != 0 || view->lineno != 0) {
3023                 view->p_restore = FALSE;
3024                 return FALSE;
3025         }
3027         if (goto_view_line(view, view->p_offset, view->p_lineno) &&
3028             view_is_displayed(view))
3029                 werase(view->win);
3031         view->yoffset = view->p_yoffset;
3032         view->p_restore = FALSE;
3034         return TRUE;
3037 static void
3038 end_update(struct view *view, bool force)
3040         if (!view->pipe)
3041                 return;
3042         while (!view->ops->read(view, NULL))
3043                 if (!force)
3044                         return;
3045         if (force)
3046                 kill_io(view->pipe);
3047         done_io(view->pipe);
3048         view->pipe = NULL;
3051 static void
3052 setup_update(struct view *view, const char *vid)
3054         reset_view(view);
3055         string_copy_rev(view->vid, vid);
3056         view->pipe = &view->io;
3057         view->start_time = time(NULL);
3060 static bool
3061 prepare_update(struct view *view, const char *argv[], const char *dir,
3062                enum format_flags flags)
3064         if (view->pipe)
3065                 end_update(view, TRUE);
3066         return init_io_rd(&view->io, argv, dir, flags);
3069 static bool
3070 prepare_update_file(struct view *view, const char *name)
3072         if (view->pipe)
3073                 end_update(view, TRUE);
3074         return io_open(&view->io, "%s/%s", opt_cdup[0] ? opt_cdup : ".", name);
3077 static bool
3078 begin_update(struct view *view, bool refresh)
3080         if (view->pipe)
3081                 end_update(view, TRUE);
3083         if (!refresh) {
3084                 if (view->ops->prepare) {
3085                         if (!view->ops->prepare(view))
3086                                 return FALSE;
3087                 } else if (!init_io_rd(&view->io, view->ops->argv, NULL, FORMAT_ALL)) {
3088                         return FALSE;
3089                 }
3091                 /* Put the current ref_* value to the view title ref
3092                  * member. This is needed by the blob view. Most other
3093                  * views sets it automatically after loading because the
3094                  * first line is a commit line. */
3095                 string_copy_rev(view->ref, view->id);
3096         }
3098         if (!start_io(&view->io))
3099                 return FALSE;
3101         setup_update(view, view->id);
3103         return TRUE;
3106 static bool
3107 update_view(struct view *view)
3109         char out_buffer[BUFSIZ * 2];
3110         char *line;
3111         /* Clear the view and redraw everything since the tree sorting
3112          * might have rearranged things. */
3113         bool redraw = view->lines == 0;
3114         bool can_read = TRUE;
3116         if (!view->pipe)
3117                 return TRUE;
3119         if (!io_can_read(view->pipe)) {
3120                 if (view->lines == 0 && view_is_displayed(view)) {
3121                         time_t secs = time(NULL) - view->start_time;
3123                         if (secs > 1 && secs > view->update_secs) {
3124                                 if (view->update_secs == 0)
3125                                         redraw_view(view);
3126                                 update_view_title(view);
3127                                 view->update_secs = secs;
3128                         }
3129                 }
3130                 return TRUE;
3131         }
3133         for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
3134                 if (opt_iconv_in != ICONV_NONE) {
3135                         ICONV_CONST char *inbuf = line;
3136                         size_t inlen = strlen(line) + 1;
3138                         char *outbuf = out_buffer;
3139                         size_t outlen = sizeof(out_buffer);
3141                         size_t ret;
3143                         ret = iconv(opt_iconv_in, &inbuf, &inlen, &outbuf, &outlen);
3144                         if (ret != (size_t) -1)
3145                                 line = out_buffer;
3146                 }
3148                 if (!view->ops->read(view, line)) {
3149                         report("Allocation failure");
3150                         end_update(view, TRUE);
3151                         return FALSE;
3152                 }
3153         }
3155         {
3156                 unsigned long lines = view->lines;
3157                 int digits;
3159                 for (digits = 0; lines; digits++)
3160                         lines /= 10;
3162                 /* Keep the displayed view in sync with line number scaling. */
3163                 if (digits != view->digits) {
3164                         view->digits = digits;
3165                         if (opt_line_number || view == VIEW(REQ_VIEW_BLAME))
3166                                 redraw = TRUE;
3167                 }
3168         }
3170         if (io_error(view->pipe)) {
3171                 report("Failed to read: %s", io_strerror(view->pipe));
3172                 end_update(view, TRUE);
3174         } else if (io_eof(view->pipe)) {
3175                 report("");
3176                 end_update(view, FALSE);
3177         }
3179         if (restore_view_position(view))
3180                 redraw = TRUE;
3182         if (!view_is_displayed(view))
3183                 return TRUE;
3185         if (redraw)
3186                 redraw_view_from(view, 0);
3187         else
3188                 redraw_view_dirty(view);
3190         /* Update the title _after_ the redraw so that if the redraw picks up a
3191          * commit reference in view->ref it'll be available here. */
3192         update_view_title(view);
3193         return TRUE;
3196 DEFINE_ALLOCATOR(realloc_lines, struct line, 256)
3198 static struct line *
3199 add_line_data(struct view *view, void *data, enum line_type type)
3201         struct line *line;
3203         if (!realloc_lines(&view->line, view->lines, 1))
3204                 return NULL;
3206         line = &view->line[view->lines++];
3207         memset(line, 0, sizeof(*line));
3208         line->type = type;
3209         line->data = data;
3210         line->dirty = 1;
3212         return line;
3215 static struct line *
3216 add_line_text(struct view *view, const char *text, enum line_type type)
3218         char *data = text ? strdup(text) : NULL;
3220         return data ? add_line_data(view, data, type) : NULL;
3223 static struct line *
3224 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
3226         char buf[SIZEOF_STR];
3227         va_list args;
3229         va_start(args, fmt);
3230         if (vsnprintf(buf, sizeof(buf), fmt, args) >= sizeof(buf))
3231                 buf[0] = 0;
3232         va_end(args);
3234         return buf[0] ? add_line_text(view, buf, type) : NULL;
3237 /*
3238  * View opening
3239  */
3241 enum open_flags {
3242         OPEN_DEFAULT = 0,       /* Use default view switching. */
3243         OPEN_SPLIT = 1,         /* Split current view. */
3244         OPEN_RELOAD = 4,        /* Reload view even if it is the current. */
3245         OPEN_REFRESH = 16,      /* Refresh view using previous command. */
3246         OPEN_PREPARED = 32,     /* Open already prepared command. */
3247 };
3249 static void
3250 open_view(struct view *prev, enum request request, enum open_flags flags)
3252         bool split = !!(flags & OPEN_SPLIT);
3253         bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
3254         bool nomaximize = !!(flags & OPEN_REFRESH);
3255         struct view *view = VIEW(request);
3256         int nviews = displayed_views();
3257         struct view *base_view = display[0];
3259         if (view == prev && nviews == 1 && !reload) {
3260                 report("Already in %s view", view->name);
3261                 return;
3262         }
3264         if (view->git_dir && !opt_git_dir[0]) {
3265                 report("The %s view is disabled in pager view", view->name);
3266                 return;
3267         }
3269         if (split) {
3270                 display[1] = view;
3271                 current_view = 1;
3272         } else if (!nomaximize) {
3273                 /* Maximize the current view. */
3274                 memset(display, 0, sizeof(display));
3275                 current_view = 0;
3276                 display[current_view] = view;
3277         }
3279         /* No parent signals that this is the first loaded view. */
3280         if (prev && view != prev) {
3281                 view->parent = prev;
3282         }
3284         /* Resize the view when switching between split- and full-screen,
3285          * or when switching between two different full-screen views. */
3286         if (nviews != displayed_views() ||
3287             (nviews == 1 && base_view != display[0]))
3288                 resize_display();
3290         if (view->ops->open) {
3291                 if (view->pipe)
3292                         end_update(view, TRUE);
3293                 if (!view->ops->open(view)) {
3294                         report("Failed to load %s view", view->name);
3295                         return;
3296                 }
3297                 restore_view_position(view);
3299         } else if ((reload || strcmp(view->vid, view->id)) &&
3300                    !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
3301                 report("Failed to load %s view", view->name);
3302                 return;
3303         }
3305         if (split && prev->lineno - prev->offset >= prev->height) {
3306                 /* Take the title line into account. */
3307                 int lines = prev->lineno - prev->offset - prev->height + 1;
3309                 /* Scroll the view that was split if the current line is
3310                  * outside the new limited view. */
3311                 do_scroll_view(prev, lines);
3312         }
3314         if (prev && view != prev && split && view_is_displayed(prev)) {
3315                 /* "Blur" the previous view. */
3316                 update_view_title(prev);
3317         }
3319         if (view->pipe && view->lines == 0) {
3320                 /* Clear the old view and let the incremental updating refill
3321                  * the screen. */
3322                 werase(view->win);
3323                 view->p_restore = flags & (OPEN_RELOAD | OPEN_REFRESH);
3324                 report("");
3325         } else if (view_is_displayed(view)) {
3326                 redraw_view(view);
3327                 report("");
3328         }
3331 static void
3332 open_external_viewer(const char *argv[], const char *dir)
3334         def_prog_mode();           /* save current tty modes */
3335         endwin();                  /* restore original tty modes */
3336         run_io_fg(argv, dir);
3337         fprintf(stderr, "Press Enter to continue");
3338         getc(opt_tty);
3339         reset_prog_mode();
3340         redraw_display(TRUE);
3343 static void
3344 open_mergetool(const char *file)
3346         const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
3348         open_external_viewer(mergetool_argv, opt_cdup);
3351 static void
3352 open_editor(const char *file)
3354         const char *editor_argv[] = { "vi", file, NULL };
3355         const char *editor;
3357         editor = getenv("GIT_EDITOR");
3358         if (!editor && *opt_editor)
3359                 editor = opt_editor;
3360         if (!editor)
3361                 editor = getenv("VISUAL");
3362         if (!editor)
3363                 editor = getenv("EDITOR");
3364         if (!editor)
3365                 editor = "vi";
3367         editor_argv[0] = editor;
3368         open_external_viewer(editor_argv, opt_cdup);
3371 static void
3372 open_run_request(enum request request)
3374         struct run_request *req = get_run_request(request);
3375         const char *argv[ARRAY_SIZE(req->argv)] = { NULL };
3377         if (!req) {
3378                 report("Unknown run request");
3379                 return;
3380         }
3382         if (format_argv(argv, req->argv, FORMAT_ALL))
3383                 open_external_viewer(argv, NULL);
3384         free_argv(argv);
3387 /*
3388  * User request switch noodle
3389  */
3391 static int
3392 view_driver(struct view *view, enum request request)
3394         int i;
3396         if (request == REQ_NONE)
3397                 return TRUE;
3399         if (request > REQ_NONE) {
3400                 open_run_request(request);
3401                 /* FIXME: When all views can refresh always do this. */
3402                 if (view == VIEW(REQ_VIEW_STATUS) ||
3403                     view == VIEW(REQ_VIEW_MAIN) ||
3404                     view == VIEW(REQ_VIEW_LOG) ||
3405                     view == VIEW(REQ_VIEW_BRANCH) ||
3406                     view == VIEW(REQ_VIEW_STAGE))
3407                         request = REQ_REFRESH;
3408                 else
3409                         return TRUE;
3410         }
3412         if (view && view->lines) {
3413                 request = view->ops->request(view, request, &view->line[view->lineno]);
3414                 if (request == REQ_NONE)
3415                         return TRUE;
3416         }
3418         switch (request) {
3419         case REQ_MOVE_UP:
3420         case REQ_MOVE_DOWN:
3421         case REQ_MOVE_PAGE_UP:
3422         case REQ_MOVE_PAGE_DOWN:
3423         case REQ_MOVE_FIRST_LINE:
3424         case REQ_MOVE_LAST_LINE:
3425                 move_view(view, request);
3426                 break;
3428         case REQ_SCROLL_LEFT:
3429         case REQ_SCROLL_RIGHT:
3430         case REQ_SCROLL_LINE_DOWN:
3431         case REQ_SCROLL_LINE_UP:
3432         case REQ_SCROLL_PAGE_DOWN:
3433         case REQ_SCROLL_PAGE_UP:
3434                 scroll_view(view, request);
3435                 break;
3437         case REQ_VIEW_BLAME:
3438                 if (!opt_file[0]) {
3439                         report("No file chosen, press %s to open tree view",
3440                                get_key(view->keymap, REQ_VIEW_TREE));
3441                         break;
3442                 }
3443                 open_view(view, request, OPEN_DEFAULT);
3444                 break;
3446         case REQ_VIEW_BLOB:
3447                 if (!ref_blob[0]) {
3448                         report("No file chosen, press %s to open tree view",
3449                                get_key(view->keymap, REQ_VIEW_TREE));
3450                         break;
3451                 }
3452                 open_view(view, request, OPEN_DEFAULT);
3453                 break;
3455         case REQ_VIEW_PAGER:
3456                 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3457                         report("No pager content, press %s to run command from prompt",
3458                                get_key(view->keymap, REQ_PROMPT));
3459                         break;
3460                 }
3461                 open_view(view, request, OPEN_DEFAULT);
3462                 break;
3464         case REQ_VIEW_STAGE:
3465                 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3466                         report("No stage content, press %s to open the status view and choose file",
3467                                get_key(view->keymap, REQ_VIEW_STATUS));
3468                         break;
3469                 }
3470                 open_view(view, request, OPEN_DEFAULT);
3471                 break;
3473         case REQ_VIEW_STATUS:
3474                 if (opt_is_inside_work_tree == FALSE) {
3475                         report("The status view requires a working tree");
3476                         break;
3477                 }
3478                 open_view(view, request, OPEN_DEFAULT);
3479                 break;
3481         case REQ_VIEW_MAIN:
3482         case REQ_VIEW_DIFF:
3483         case REQ_VIEW_LOG:
3484         case REQ_VIEW_TREE:
3485         case REQ_VIEW_HELP:
3486         case REQ_VIEW_BRANCH:
3487                 open_view(view, request, OPEN_DEFAULT);
3488                 break;
3490         case REQ_NEXT:
3491         case REQ_PREVIOUS:
3492                 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3494                 if ((view == VIEW(REQ_VIEW_DIFF) &&
3495                      view->parent == VIEW(REQ_VIEW_MAIN)) ||
3496                    (view == VIEW(REQ_VIEW_DIFF) &&
3497                      view->parent == VIEW(REQ_VIEW_BLAME)) ||
3498                    (view == VIEW(REQ_VIEW_STAGE) &&
3499                      view->parent == VIEW(REQ_VIEW_STATUS)) ||
3500                    (view == VIEW(REQ_VIEW_BLOB) &&
3501                      view->parent == VIEW(REQ_VIEW_TREE)) ||
3502                    (view == VIEW(REQ_VIEW_MAIN) &&
3503                      view->parent == VIEW(REQ_VIEW_BRANCH))) {
3504                         int line;
3506                         view = view->parent;
3507                         line = view->lineno;
3508                         move_view(view, request);
3509                         if (view_is_displayed(view))
3510                                 update_view_title(view);
3511                         if (line != view->lineno)
3512                                 view->ops->request(view, REQ_ENTER,
3513                                                    &view->line[view->lineno]);
3515                 } else {
3516                         move_view(view, request);
3517                 }
3518                 break;
3520         case REQ_VIEW_NEXT:
3521         {
3522                 int nviews = displayed_views();
3523                 int next_view = (current_view + 1) % nviews;
3525                 if (next_view == current_view) {
3526                         report("Only one view is displayed");
3527                         break;
3528                 }
3530                 current_view = next_view;
3531                 /* Blur out the title of the previous view. */
3532                 update_view_title(view);
3533                 report("");
3534                 break;
3535         }
3536         case REQ_REFRESH:
3537                 report("Refreshing is not yet supported for the %s view", view->name);
3538                 break;
3540         case REQ_MAXIMIZE:
3541                 if (displayed_views() == 2)
3542                         maximize_view(view);
3543                 break;
3545         case REQ_OPTIONS:
3546                 open_option_menu();
3547                 break;
3549         case REQ_TOGGLE_LINENO:
3550                 toggle_view_option(&opt_line_number, "line numbers");
3551                 break;
3553         case REQ_TOGGLE_DATE:
3554                 toggle_date();
3555                 break;
3557         case REQ_TOGGLE_AUTHOR:
3558                 toggle_author();
3559                 break;
3561         case REQ_TOGGLE_REV_GRAPH:
3562                 toggle_view_option(&opt_rev_graph, "revision graph display");
3563                 break;
3565         case REQ_TOGGLE_REFS:
3566                 toggle_view_option(&opt_show_refs, "reference display");
3567                 break;
3569         case REQ_TOGGLE_SORT_FIELD:
3570         case REQ_TOGGLE_SORT_ORDER:
3571                 report("Sorting is not yet supported for the %s view", view->name);
3572                 break;
3574         case REQ_SEARCH:
3575         case REQ_SEARCH_BACK:
3576                 search_view(view, request);
3577                 break;
3579         case REQ_FIND_NEXT:
3580         case REQ_FIND_PREV:
3581                 find_next(view, request);
3582                 break;
3584         case REQ_STOP_LOADING:
3585                 for (i = 0; i < ARRAY_SIZE(views); i++) {
3586                         view = &views[i];
3587                         if (view->pipe)
3588                                 report("Stopped loading the %s view", view->name),
3589                         end_update(view, TRUE);
3590                 }
3591                 break;
3593         case REQ_SHOW_VERSION:
3594                 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3595                 return TRUE;
3597         case REQ_SCREEN_REDRAW:
3598                 redraw_display(TRUE);
3599                 break;
3601         case REQ_EDIT:
3602                 report("Nothing to edit");
3603                 break;
3605         case REQ_ENTER:
3606                 report("Nothing to enter");
3607                 break;
3609         case REQ_VIEW_CLOSE:
3610                 /* XXX: Mark closed views by letting view->parent point to the
3611                  * view itself. Parents to closed view should never be
3612                  * followed. */
3613                 if (view->parent &&
3614                     view->parent->parent != view->parent) {
3615                         maximize_view(view->parent);
3616                         view->parent = view;
3617                         break;
3618                 }
3619                 /* Fall-through */
3620         case REQ_QUIT:
3621                 return FALSE;
3623         default:
3624                 report("Unknown key, press %s for help",
3625                        get_key(view->keymap, REQ_VIEW_HELP));
3626                 return TRUE;
3627         }
3629         return TRUE;
3633 /*
3634  * View backend utilities
3635  */
3637 enum sort_field {
3638         ORDERBY_NAME,
3639         ORDERBY_DATE,
3640         ORDERBY_AUTHOR,
3641 };
3643 struct sort_state {
3644         const enum sort_field *fields;
3645         size_t size, current;
3646         bool reverse;
3647 };
3649 #define SORT_STATE(fields) { fields, ARRAY_SIZE(fields), 0 }
3650 #define get_sort_field(state) ((state).fields[(state).current])
3651 #define sort_order(state, result) ((state).reverse ? -(result) : (result))
3653 static void
3654 sort_view(struct view *view, enum request request, struct sort_state *state,
3655           int (*compare)(const void *, const void *))
3657         switch (request) {
3658         case REQ_TOGGLE_SORT_FIELD:
3659                 state->current = (state->current + 1) % state->size;
3660                 break;
3662         case REQ_TOGGLE_SORT_ORDER:
3663                 state->reverse = !state->reverse;
3664                 break;
3665         default:
3666                 die("Not a sort request");
3667         }
3669         qsort(view->line, view->lines, sizeof(*view->line), compare);
3670         redraw_view(view);
3673 DEFINE_ALLOCATOR(realloc_authors, const char *, 256)
3675 /* Small author cache to reduce memory consumption. It uses binary
3676  * search to lookup or find place to position new entries. No entries
3677  * are ever freed. */
3678 static const char *
3679 get_author(const char *name)
3681         static const char **authors;
3682         static size_t authors_size;
3683         int from = 0, to = authors_size - 1;
3685         while (from <= to) {
3686                 size_t pos = (to + from) / 2;
3687                 int cmp = strcmp(name, authors[pos]);
3689                 if (!cmp)
3690                         return authors[pos];
3692                 if (cmp < 0)
3693                         to = pos - 1;
3694                 else
3695                         from = pos + 1;
3696         }
3698         if (!realloc_authors(&authors, authors_size, 1))
3699                 return NULL;
3700         name = strdup(name);
3701         if (!name)
3702                 return NULL;
3704         memmove(authors + from + 1, authors + from, (authors_size - from) * sizeof(*authors));
3705         authors[from] = name;
3706         authors_size++;
3708         return name;
3711 static void
3712 parse_timesec(struct time *time, const char *sec)
3714         time->sec = (time_t) atol(sec);
3717 static void
3718 parse_timezone(struct time *time, const char *zone)
3720         long tz;
3722         tz  = ('0' - zone[1]) * 60 * 60 * 10;
3723         tz += ('0' - zone[2]) * 60 * 60;
3724         tz += ('0' - zone[3]) * 60;
3725         tz += ('0' - zone[4]);
3727         if (zone[0] == '-')
3728                 tz = -tz;
3730         time->tz = tz;
3731         time->sec -= tz;
3734 /* Parse author lines where the name may be empty:
3735  *      author  <email@address.tld> 1138474660 +0100
3736  */
3737 static void
3738 parse_author_line(char *ident, const char **author, struct time *time)
3740         char *nameend = strchr(ident, '<');
3741         char *emailend = strchr(ident, '>');
3743         if (nameend && emailend)
3744                 *nameend = *emailend = 0;
3745         ident = chomp_string(ident);
3746         if (!*ident) {
3747                 if (nameend)
3748                         ident = chomp_string(nameend + 1);
3749                 if (!*ident)
3750                         ident = "Unknown";
3751         }
3753         *author = get_author(ident);
3755         /* Parse epoch and timezone */
3756         if (emailend && emailend[1] == ' ') {
3757                 char *secs = emailend + 2;
3758                 char *zone = strchr(secs, ' ');
3760                 parse_timesec(time, secs);
3762                 if (zone && strlen(zone) == STRING_SIZE(" +0700"))
3763                         parse_timezone(time, zone + 1);
3764         }
3767 static bool
3768 open_commit_parent_menu(char buf[SIZEOF_STR], int *parents)
3770         char rev[SIZEOF_REV];
3771         const char *revlist_argv[] = {
3772                 "git", "log", "--no-color", "-1", "--pretty=format:%s", rev, NULL
3773         };
3774         struct menu_item *items;
3775         char text[SIZEOF_STR];
3776         bool ok = TRUE;
3777         int i;
3779         items = calloc(*parents + 1, sizeof(*items));
3780         if (!items)
3781                 return FALSE;
3783         for (i = 0; i < *parents; i++) {
3784                 string_copy_rev(rev, &buf[SIZEOF_REV * i]);
3785                 if (!run_io_buf(revlist_argv, text, sizeof(text)) ||
3786                     !(items[i].text = strdup(text))) {
3787                         ok = FALSE;
3788                         break;
3789                 }
3790         }
3792         if (ok) {
3793                 *parents = 0;
3794                 ok = prompt_menu("Select parent", items, parents);
3795         }
3796         for (i = 0; items[i].text; i++)
3797                 free((char *) items[i].text);
3798         free(items);
3799         return ok;
3802 static bool
3803 select_commit_parent(const char *id, char rev[SIZEOF_REV], const char *path)
3805         char buf[SIZEOF_STR * 4];
3806         const char *revlist_argv[] = {
3807                 "git", "log", "--no-color", "-1",
3808                         "--pretty=format:%P", id, "--", path, NULL
3809         };
3810         int parents;
3812         if (!run_io_buf(revlist_argv, buf, sizeof(buf)) ||
3813             (parents = strlen(buf) / 40) < 0) {
3814                 report("Failed to get parent information");
3815                 return FALSE;
3817         } else if (parents == 0) {
3818                 if (path)
3819                         report("Path '%s' does not exist in the parent", path);
3820                 else
3821                         report("The selected commit has no parents");
3822                 return FALSE;
3823         }
3825         if (parents > 1 && !open_commit_parent_menu(buf, &parents))
3826                 return FALSE;
3828         string_copy_rev(rev, &buf[41 * parents]);
3829         return TRUE;
3832 /*
3833  * Pager backend
3834  */
3836 static bool
3837 pager_draw(struct view *view, struct line *line, unsigned int lineno)
3839         char text[SIZEOF_STR];
3841         if (opt_line_number && draw_lineno(view, lineno))
3842                 return TRUE;
3844         string_expand(text, sizeof(text), line->data, opt_tab_size);
3845         draw_text(view, line->type, text, TRUE);
3846         return TRUE;
3849 static bool
3850 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
3852         const char *describe_argv[] = { "git", "describe", commit_id, NULL };
3853         char ref[SIZEOF_STR];
3855         if (!run_io_buf(describe_argv, ref, sizeof(ref)) || !*ref)
3856                 return TRUE;
3858         /* This is the only fatal call, since it can "corrupt" the buffer. */
3859         if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
3860                 return FALSE;
3862         return TRUE;
3865 static void
3866 add_pager_refs(struct view *view, struct line *line)
3868         char buf[SIZEOF_STR];
3869         char *commit_id = (char *)line->data + STRING_SIZE("commit ");
3870         struct ref_list *list;
3871         size_t bufpos = 0, i;
3872         const char *sep = "Refs: ";
3873         bool is_tag = FALSE;
3875         assert(line->type == LINE_COMMIT);
3877         list = get_ref_list(commit_id);
3878         if (!list) {
3879                 if (view == VIEW(REQ_VIEW_DIFF))
3880                         goto try_add_describe_ref;
3881                 return;
3882         }
3884         for (i = 0; i < list->size; i++) {
3885                 struct ref *ref = list->refs[i];
3886                 const char *fmt = ref->tag    ? "%s[%s]" :
3887                                   ref->remote ? "%s<%s>" : "%s%s";
3889                 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
3890                         return;
3891                 sep = ", ";
3892                 if (ref->tag)
3893                         is_tag = TRUE;
3894         }
3896         if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
3897 try_add_describe_ref:
3898                 /* Add <tag>-g<commit_id> "fake" reference. */
3899                 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
3900                         return;
3901         }
3903         if (bufpos == 0)
3904                 return;
3906         add_line_text(view, buf, LINE_PP_REFS);
3909 static bool
3910 pager_read(struct view *view, char *data)
3912         struct line *line;
3914         if (!data)
3915                 return TRUE;
3917         line = add_line_text(view, data, get_line_type(data));
3918         if (!line)
3919                 return FALSE;
3921         if (line->type == LINE_COMMIT &&
3922             (view == VIEW(REQ_VIEW_DIFF) ||
3923              view == VIEW(REQ_VIEW_LOG)))
3924                 add_pager_refs(view, line);
3926         return TRUE;
3929 static enum request
3930 pager_request(struct view *view, enum request request, struct line *line)
3932         int split = 0;
3934         if (request != REQ_ENTER)
3935                 return request;
3937         if (line->type == LINE_COMMIT &&
3938            (view == VIEW(REQ_VIEW_LOG) ||
3939             view == VIEW(REQ_VIEW_PAGER))) {
3940                 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
3941                 split = 1;
3942         }
3944         /* Always scroll the view even if it was split. That way
3945          * you can use Enter to scroll through the log view and
3946          * split open each commit diff. */
3947         scroll_view(view, REQ_SCROLL_LINE_DOWN);
3949         /* FIXME: A minor workaround. Scrolling the view will call report("")
3950          * but if we are scrolling a non-current view this won't properly
3951          * update the view title. */
3952         if (split)
3953                 update_view_title(view);
3955         return REQ_NONE;
3958 static bool
3959 pager_grep(struct view *view, struct line *line)
3961         const char *text[] = { line->data, NULL };
3963         return grep_text(view, text);
3966 static void
3967 pager_select(struct view *view, struct line *line)
3969         if (line->type == LINE_COMMIT) {
3970                 char *text = (char *)line->data + STRING_SIZE("commit ");
3972                 if (view != VIEW(REQ_VIEW_PAGER))
3973                         string_copy_rev(view->ref, text);
3974                 string_copy_rev(ref_commit, text);
3975         }
3978 static struct view_ops pager_ops = {
3979         "line",
3980         NULL,
3981         NULL,
3982         pager_read,
3983         pager_draw,
3984         pager_request,
3985         pager_grep,
3986         pager_select,
3987 };
3989 static const char *log_argv[SIZEOF_ARG] = {
3990         "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
3991 };
3993 static enum request
3994 log_request(struct view *view, enum request request, struct line *line)
3996         switch (request) {
3997         case REQ_REFRESH:
3998                 load_refs();
3999                 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
4000                 return REQ_NONE;
4001         default:
4002                 return pager_request(view, request, line);
4003         }
4006 static struct view_ops log_ops = {
4007         "line",
4008         log_argv,
4009         NULL,
4010         pager_read,
4011         pager_draw,
4012         log_request,
4013         pager_grep,
4014         pager_select,
4015 };
4017 static const char *diff_argv[SIZEOF_ARG] = {
4018         "git", "show", "--pretty=fuller", "--no-color", "--root",
4019                 "--patch-with-stat", "--find-copies-harder", "-C", "%(commit)", NULL
4020 };
4022 static struct view_ops diff_ops = {
4023         "line",
4024         diff_argv,
4025         NULL,
4026         pager_read,
4027         pager_draw,
4028         pager_request,
4029         pager_grep,
4030         pager_select,
4031 };
4033 /*
4034  * Help backend
4035  */
4037 static bool help_keymap_hidden[ARRAY_SIZE(keymap_table)];
4039 static bool
4040 help_open_keymap_title(struct view *view, enum keymap keymap)
4042         struct line *line;
4044         line = add_line_format(view, LINE_HELP_KEYMAP, "[%c] %s bindings",
4045                                help_keymap_hidden[keymap] ? '+' : '-',
4046                                enum_name(keymap_table[keymap]));
4047         if (line)
4048                 line->other = keymap;
4050         return help_keymap_hidden[keymap];
4053 static void
4054 help_open_keymap(struct view *view, enum keymap keymap)
4056         const char *group = NULL;
4057         char buf[SIZEOF_STR];
4058         size_t bufpos;
4059         bool add_title = TRUE;
4060         int i;
4062         for (i = 0; i < ARRAY_SIZE(req_info); i++) {
4063                 const char *key = NULL;
4065                 if (req_info[i].request == REQ_NONE)
4066                         continue;
4068                 if (!req_info[i].request) {
4069                         group = req_info[i].help;
4070                         continue;
4071                 }
4073                 key = get_keys(keymap, req_info[i].request, TRUE);
4074                 if (!key || !*key)
4075                         continue;
4077                 if (add_title && help_open_keymap_title(view, keymap))
4078                         return;
4079                 add_title = FALSE;
4081                 if (group) {
4082                         add_line_text(view, group, LINE_HELP_GROUP);
4083                         group = NULL;
4084                 }
4086                 add_line_format(view, LINE_DEFAULT, "    %-25s %-20s %s", key,
4087                                 enum_name(req_info[i]), req_info[i].help);
4088         }
4090         group = "External commands:";
4092         for (i = 0; i < run_requests; i++) {
4093                 struct run_request *req = get_run_request(REQ_NONE + i + 1);
4094                 const char *key;
4095                 int argc;
4097                 if (!req || req->keymap != keymap)
4098                         continue;
4100                 key = get_key_name(req->key);
4101                 if (!*key)
4102                         key = "(no key defined)";
4104                 if (add_title && help_open_keymap_title(view, keymap))
4105                         return;
4106                 if (group) {
4107                         add_line_text(view, group, LINE_HELP_GROUP);
4108                         group = NULL;
4109                 }
4111                 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
4112                         if (!string_format_from(buf, &bufpos, "%s%s",
4113                                                 argc ? " " : "", req->argv[argc]))
4114                                 return;
4116                 add_line_format(view, LINE_DEFAULT, "    %-25s `%s`", key, buf);
4117         }
4120 static bool
4121 help_open(struct view *view)
4123         enum keymap keymap;
4125         reset_view(view);
4126         add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
4127         add_line_text(view, "", LINE_DEFAULT);
4129         for (keymap = 0; keymap < ARRAY_SIZE(keymap_table); keymap++)
4130                 help_open_keymap(view, keymap);
4132         return TRUE;
4135 static enum request
4136 help_request(struct view *view, enum request request, struct line *line)
4138         switch (request) {
4139         case REQ_ENTER:
4140                 if (line->type == LINE_HELP_KEYMAP) {
4141                         help_keymap_hidden[line->other] =
4142                                 !help_keymap_hidden[line->other];
4143                         view->p_restore = TRUE;
4144                         open_view(view, REQ_VIEW_HELP, OPEN_REFRESH);
4145                 }
4147                 return REQ_NONE;
4148         default:
4149                 return pager_request(view, request, line);
4150         }
4153 static struct view_ops help_ops = {
4154         "line",
4155         NULL,
4156         help_open,
4157         NULL,
4158         pager_draw,
4159         help_request,
4160         pager_grep,
4161         pager_select,
4162 };
4165 /*
4166  * Tree backend
4167  */
4169 struct tree_stack_entry {
4170         struct tree_stack_entry *prev;  /* Entry below this in the stack */
4171         unsigned long lineno;           /* Line number to restore */
4172         char *name;                     /* Position of name in opt_path */
4173 };
4175 /* The top of the path stack. */
4176 static struct tree_stack_entry *tree_stack = NULL;
4177 unsigned long tree_lineno = 0;
4179 static void
4180 pop_tree_stack_entry(void)
4182         struct tree_stack_entry *entry = tree_stack;
4184         tree_lineno = entry->lineno;
4185         entry->name[0] = 0;
4186         tree_stack = entry->prev;
4187         free(entry);
4190 static void
4191 push_tree_stack_entry(const char *name, unsigned long lineno)
4193         struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
4194         size_t pathlen = strlen(opt_path);
4196         if (!entry)
4197                 return;
4199         entry->prev = tree_stack;
4200         entry->name = opt_path + pathlen;
4201         tree_stack = entry;
4203         if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
4204                 pop_tree_stack_entry();
4205                 return;
4206         }
4208         /* Move the current line to the first tree entry. */
4209         tree_lineno = 1;
4210         entry->lineno = lineno;
4213 /* Parse output from git-ls-tree(1):
4214  *
4215  * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
4216  */
4218 #define SIZEOF_TREE_ATTR \
4219         STRING_SIZE("100644 blob f931e1d229c3e185caad4449bf5b66ed72462657\t")
4221 #define SIZEOF_TREE_MODE \
4222         STRING_SIZE("100644 ")
4224 #define TREE_ID_OFFSET \
4225         STRING_SIZE("100644 blob ")
4227 struct tree_entry {
4228         char id[SIZEOF_REV];
4229         mode_t mode;
4230         struct time time;               /* Date from the author ident. */
4231         const char *author;             /* Author of the commit. */
4232         char name[1];
4233 };
4235 static const char *
4236 tree_path(const struct line *line)
4238         return ((struct tree_entry *) line->data)->name;
4241 static int
4242 tree_compare_entry(const struct line *line1, const struct line *line2)
4244         if (line1->type != line2->type)
4245                 return line1->type == LINE_TREE_DIR ? -1 : 1;
4246         return strcmp(tree_path(line1), tree_path(line2));
4249 static const enum sort_field tree_sort_fields[] = {
4250         ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
4251 };
4252 static struct sort_state tree_sort_state = SORT_STATE(tree_sort_fields);
4254 static int
4255 tree_compare(const void *l1, const void *l2)
4257         const struct line *line1 = (const struct line *) l1;
4258         const struct line *line2 = (const struct line *) l2;
4259         const struct tree_entry *entry1 = ((const struct line *) l1)->data;
4260         const struct tree_entry *entry2 = ((const struct line *) l2)->data;
4262         if (line1->type == LINE_TREE_HEAD)
4263                 return -1;
4264         if (line2->type == LINE_TREE_HEAD)
4265                 return 1;
4267         switch (get_sort_field(tree_sort_state)) {
4268         case ORDERBY_DATE:
4269                 return sort_order(tree_sort_state, timecmp(&entry1->time, &entry2->time));
4271         case ORDERBY_AUTHOR:
4272                 return sort_order(tree_sort_state, strcmp(entry1->author, entry2->author));
4274         case ORDERBY_NAME:
4275         default:
4276                 return sort_order(tree_sort_state, tree_compare_entry(line1, line2));
4277         }
4281 static struct line *
4282 tree_entry(struct view *view, enum line_type type, const char *path,
4283            const char *mode, const char *id)
4285         struct tree_entry *entry = calloc(1, sizeof(*entry) + strlen(path));
4286         struct line *line = entry ? add_line_data(view, entry, type) : NULL;
4288         if (!entry || !line) {
4289                 free(entry);
4290                 return NULL;
4291         }
4293         strncpy(entry->name, path, strlen(path));
4294         if (mode)
4295                 entry->mode = strtoul(mode, NULL, 8);
4296         if (id)
4297                 string_copy_rev(entry->id, id);
4299         return line;
4302 static bool
4303 tree_read_date(struct view *view, char *text, bool *read_date)
4305         static const char *author_name;
4306         static struct time author_time;
4308         if (!text && *read_date) {
4309                 *read_date = FALSE;
4310                 return TRUE;
4312         } else if (!text) {
4313                 char *path = *opt_path ? opt_path : ".";
4314                 /* Find next entry to process */
4315                 const char *log_file[] = {
4316                         "git", "log", "--no-color", "--pretty=raw",
4317                                 "--cc", "--raw", view->id, "--", path, NULL
4318                 };
4319                 struct io io = {};
4321                 if (!view->lines) {
4322                         tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL);
4323                         report("Tree is empty");
4324                         return TRUE;
4325                 }
4327                 if (!run_io_rd(&io, log_file, opt_cdup, FORMAT_NONE)) {
4328                         report("Failed to load tree data");
4329                         return TRUE;
4330                 }
4332                 done_io(view->pipe);
4333                 view->io = io;
4334                 *read_date = TRUE;
4335                 return FALSE;
4337         } else if (*text == 'a' && get_line_type(text) == LINE_AUTHOR) {
4338                 parse_author_line(text + STRING_SIZE("author "),
4339                                   &author_name, &author_time);
4341         } else if (*text == ':') {
4342                 char *pos;
4343                 size_t annotated = 1;
4344                 size_t i;
4346                 pos = strchr(text, '\t');
4347                 if (!pos)
4348                         return TRUE;
4349                 text = pos + 1;
4350                 if (*opt_path && !strncmp(text, opt_path, strlen(opt_path)))
4351                         text += strlen(opt_path);
4352                 pos = strchr(text, '/');
4353                 if (pos)
4354                         *pos = 0;
4356                 for (i = 1; i < view->lines; i++) {
4357                         struct line *line = &view->line[i];
4358                         struct tree_entry *entry = line->data;
4360                         annotated += !!entry->author;
4361                         if (entry->author || strcmp(entry->name, text))
4362                                 continue;
4364                         entry->author = author_name;
4365                         entry->time = author_time;
4366                         line->dirty = 1;
4367                         break;
4368                 }
4370                 if (annotated == view->lines)
4371                         kill_io(view->pipe);
4372         }
4373         return TRUE;
4376 static bool
4377 tree_read(struct view *view, char *text)
4379         static bool read_date = FALSE;
4380         struct tree_entry *data;
4381         struct line *entry, *line;
4382         enum line_type type;
4383         size_t textlen = text ? strlen(text) : 0;
4384         char *path = text + SIZEOF_TREE_ATTR;
4386         if (read_date || !text)
4387                 return tree_read_date(view, text, &read_date);
4389         if (textlen <= SIZEOF_TREE_ATTR)
4390                 return FALSE;
4391         if (view->lines == 0 &&
4392             !tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL))
4393                 return FALSE;
4395         /* Strip the path part ... */
4396         if (*opt_path) {
4397                 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
4398                 size_t striplen = strlen(opt_path);
4400                 if (pathlen > striplen)
4401                         memmove(path, path + striplen,
4402                                 pathlen - striplen + 1);
4404                 /* Insert "link" to parent directory. */
4405                 if (view->lines == 1 &&
4406                     !tree_entry(view, LINE_TREE_DIR, "..", "040000", view->ref))
4407                         return FALSE;
4408         }
4410         type = text[SIZEOF_TREE_MODE] == 't' ? LINE_TREE_DIR : LINE_TREE_FILE;
4411         entry = tree_entry(view, type, path, text, text + TREE_ID_OFFSET);
4412         if (!entry)
4413                 return FALSE;
4414         data = entry->data;
4416         /* Skip "Directory ..." and ".." line. */
4417         for (line = &view->line[1 + !!*opt_path]; line < entry; line++) {
4418                 if (tree_compare_entry(line, entry) <= 0)
4419                         continue;
4421                 memmove(line + 1, line, (entry - line) * sizeof(*entry));
4423                 line->data = data;
4424                 line->type = type;
4425                 for (; line <= entry; line++)
4426                         line->dirty = line->cleareol = 1;
4427                 return TRUE;
4428         }
4430         if (tree_lineno > view->lineno) {
4431                 view->lineno = tree_lineno;
4432                 tree_lineno = 0;
4433         }
4435         return TRUE;
4438 static bool
4439 tree_draw(struct view *view, struct line *line, unsigned int lineno)
4441         struct tree_entry *entry = line->data;
4443         if (line->type == LINE_TREE_HEAD) {
4444                 if (draw_text(view, line->type, "Directory path /", TRUE))
4445                         return TRUE;
4446         } else {
4447                 if (draw_mode(view, entry->mode))
4448                         return TRUE;
4450                 if (opt_author && draw_author(view, entry->author))
4451                         return TRUE;
4453                 if (opt_date && draw_date(view, &entry->time))
4454                         return TRUE;
4455         }
4456         if (draw_text(view, line->type, entry->name, TRUE))
4457                 return TRUE;
4458         return TRUE;
4461 static void
4462 open_blob_editor()
4464         char file[SIZEOF_STR] = "/tmp/tigblob.XXXXXX";
4465         int fd = mkstemp(file);
4467         if (fd == -1)
4468                 report("Failed to create temporary file");
4469         else if (!run_io_append(blob_ops.argv, FORMAT_ALL, fd))
4470                 report("Failed to save blob data to file");
4471         else
4472                 open_editor(file);
4473         if (fd != -1)
4474                 unlink(file);
4477 static enum request
4478 tree_request(struct view *view, enum request request, struct line *line)
4480         enum open_flags flags;
4482         switch (request) {
4483         case REQ_VIEW_BLAME:
4484                 if (line->type != LINE_TREE_FILE) {
4485                         report("Blame only supported for files");
4486                         return REQ_NONE;
4487                 }
4489                 string_copy(opt_ref, view->vid);
4490                 return request;
4492         case REQ_EDIT:
4493                 if (line->type != LINE_TREE_FILE) {
4494                         report("Edit only supported for files");
4495                 } else if (!is_head_commit(view->vid)) {
4496                         open_blob_editor();
4497                 } else {
4498                         open_editor(opt_file);
4499                 }
4500                 return REQ_NONE;
4502         case REQ_TOGGLE_SORT_FIELD:
4503         case REQ_TOGGLE_SORT_ORDER:
4504                 sort_view(view, request, &tree_sort_state, tree_compare);
4505                 return REQ_NONE;
4507         case REQ_PARENT:
4508                 if (!*opt_path) {
4509                         /* quit view if at top of tree */
4510                         return REQ_VIEW_CLOSE;
4511                 }
4512                 /* fake 'cd  ..' */
4513                 line = &view->line[1];
4514                 break;
4516         case REQ_ENTER:
4517                 break;
4519         default:
4520                 return request;
4521         }
4523         /* Cleanup the stack if the tree view is at a different tree. */
4524         while (!*opt_path && tree_stack)
4525                 pop_tree_stack_entry();
4527         switch (line->type) {
4528         case LINE_TREE_DIR:
4529                 /* Depending on whether it is a subdirectory or parent link
4530                  * mangle the path buffer. */
4531                 if (line == &view->line[1] && *opt_path) {
4532                         pop_tree_stack_entry();
4534                 } else {
4535                         const char *basename = tree_path(line);
4537                         push_tree_stack_entry(basename, view->lineno);
4538                 }
4540                 /* Trees and subtrees share the same ID, so they are not not
4541                  * unique like blobs. */
4542                 flags = OPEN_RELOAD;
4543                 request = REQ_VIEW_TREE;
4544                 break;
4546         case LINE_TREE_FILE:
4547                 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4548                 request = REQ_VIEW_BLOB;
4549                 break;
4551         default:
4552                 return REQ_NONE;
4553         }
4555         open_view(view, request, flags);
4556         if (request == REQ_VIEW_TREE)
4557                 view->lineno = tree_lineno;
4559         return REQ_NONE;
4562 static bool
4563 tree_grep(struct view *view, struct line *line)
4565         struct tree_entry *entry = line->data;
4566         const char *text[] = {
4567                 entry->name,
4568                 opt_author ? entry->author : "",
4569                 mkdate(&entry->time, opt_date),
4570                 NULL
4571         };
4573         return grep_text(view, text);
4576 static void
4577 tree_select(struct view *view, struct line *line)
4579         struct tree_entry *entry = line->data;
4581         if (line->type == LINE_TREE_FILE) {
4582                 string_copy_rev(ref_blob, entry->id);
4583                 string_format(opt_file, "%s%s", opt_path, tree_path(line));
4585         } else if (line->type != LINE_TREE_DIR) {
4586                 return;
4587         }
4589         string_copy_rev(view->ref, entry->id);
4592 static bool
4593 tree_prepare(struct view *view)
4595         if (view->lines == 0 && opt_prefix[0]) {
4596                 char *pos = opt_prefix;
4598                 while (pos && *pos) {
4599                         char *end = strchr(pos, '/');
4601                         if (end)
4602                                 *end = 0;
4603                         push_tree_stack_entry(pos, 0);
4604                         pos = end;
4605                         if (end) {
4606                                 *end = '/';
4607                                 pos++;
4608                         }
4609                 }
4611         } else if (strcmp(view->vid, view->id)) {
4612                 opt_path[0] = 0;
4613         }
4615         return init_io_rd(&view->io, view->ops->argv, opt_cdup, FORMAT_ALL);
4618 static const char *tree_argv[SIZEOF_ARG] = {
4619         "git", "ls-tree", "%(commit)", "%(directory)", NULL
4620 };
4622 static struct view_ops tree_ops = {
4623         "file",
4624         tree_argv,
4625         NULL,
4626         tree_read,
4627         tree_draw,
4628         tree_request,
4629         tree_grep,
4630         tree_select,
4631         tree_prepare,
4632 };
4634 static bool
4635 blob_read(struct view *view, char *line)
4637         if (!line)
4638                 return TRUE;
4639         return add_line_text(view, line, LINE_DEFAULT) != NULL;
4642 static enum request
4643 blob_request(struct view *view, enum request request, struct line *line)
4645         switch (request) {
4646         case REQ_EDIT:
4647                 open_blob_editor();
4648                 return REQ_NONE;
4649         default:
4650                 return pager_request(view, request, line);
4651         }
4654 static const char *blob_argv[SIZEOF_ARG] = {
4655         "git", "cat-file", "blob", "%(blob)", NULL
4656 };
4658 static struct view_ops blob_ops = {
4659         "line",
4660         blob_argv,
4661         NULL,
4662         blob_read,
4663         pager_draw,
4664         blob_request,
4665         pager_grep,
4666         pager_select,
4667 };
4669 /*
4670  * Blame backend
4671  *
4672  * Loading the blame view is a two phase job:
4673  *
4674  *  1. File content is read either using opt_file from the
4675  *     filesystem or using git-cat-file.
4676  *  2. Then blame information is incrementally added by
4677  *     reading output from git-blame.
4678  */
4680 static const char *blame_head_argv[] = {
4681         "git", "blame", "--incremental", "--", "%(file)", NULL
4682 };
4684 static const char *blame_ref_argv[] = {
4685         "git", "blame", "--incremental", "%(ref)", "--", "%(file)", NULL
4686 };
4688 static const char *blame_cat_file_argv[] = {
4689         "git", "cat-file", "blob", "%(ref):%(file)", NULL
4690 };
4692 struct blame_commit {
4693         char id[SIZEOF_REV];            /* SHA1 ID. */
4694         char title[128];                /* First line of the commit message. */
4695         const char *author;             /* Author of the commit. */
4696         struct time time;               /* Date from the author ident. */
4697         char filename[128];             /* Name of file. */
4698         bool has_previous;              /* Was a "previous" line detected. */
4699 };
4701 struct blame {
4702         struct blame_commit *commit;
4703         unsigned long lineno;
4704         char text[1];
4705 };
4707 static bool
4708 blame_open(struct view *view)
4710         char path[SIZEOF_STR];
4712         if (!view->parent && *opt_prefix) {
4713                 string_copy(path, opt_file);
4714                 if (!string_format(opt_file, "%s%s", opt_prefix, path))
4715                         return FALSE;
4716         }
4718         if (*opt_ref || !io_open(&view->io, "%s%s", opt_cdup, opt_file)) {
4719                 if (!run_io_rd(&view->io, blame_cat_file_argv, opt_cdup, FORMAT_ALL))
4720                         return FALSE;
4721         }
4723         setup_update(view, opt_file);
4724         string_format(view->ref, "%s ...", opt_file);
4726         return TRUE;
4729 static struct blame_commit *
4730 get_blame_commit(struct view *view, const char *id)
4732         size_t i;
4734         for (i = 0; i < view->lines; i++) {
4735                 struct blame *blame = view->line[i].data;
4737                 if (!blame->commit)
4738                         continue;
4740                 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
4741                         return blame->commit;
4742         }
4744         {
4745                 struct blame_commit *commit = calloc(1, sizeof(*commit));
4747                 if (commit)
4748                         string_ncopy(commit->id, id, SIZEOF_REV);
4749                 return commit;
4750         }
4753 static bool
4754 parse_number(const char **posref, size_t *number, size_t min, size_t max)
4756         const char *pos = *posref;
4758         *posref = NULL;
4759         pos = strchr(pos + 1, ' ');
4760         if (!pos || !isdigit(pos[1]))
4761                 return FALSE;
4762         *number = atoi(pos + 1);
4763         if (*number < min || *number > max)
4764                 return FALSE;
4766         *posref = pos;
4767         return TRUE;
4770 static struct blame_commit *
4771 parse_blame_commit(struct view *view, const char *text, int *blamed)
4773         struct blame_commit *commit;
4774         struct blame *blame;
4775         const char *pos = text + SIZEOF_REV - 2;
4776         size_t orig_lineno = 0;
4777         size_t lineno;
4778         size_t group;
4780         if (strlen(text) <= SIZEOF_REV || pos[1] != ' ')
4781                 return NULL;
4783         if (!parse_number(&pos, &orig_lineno, 1, 9999999) ||
4784             !parse_number(&pos, &lineno, 1, view->lines) ||
4785             !parse_number(&pos, &group, 1, view->lines - lineno + 1))
4786                 return NULL;
4788         commit = get_blame_commit(view, text);
4789         if (!commit)
4790                 return NULL;
4792         *blamed += group;
4793         while (group--) {
4794                 struct line *line = &view->line[lineno + group - 1];
4796                 blame = line->data;
4797                 blame->commit = commit;
4798                 blame->lineno = orig_lineno + group - 1;
4799                 line->dirty = 1;
4800         }
4802         return commit;
4805 static bool
4806 blame_read_file(struct view *view, const char *line, bool *read_file)
4808         if (!line) {
4809                 const char **argv = *opt_ref ? blame_ref_argv : blame_head_argv;
4810                 struct io io = {};
4812                 if (view->lines == 0 && !view->parent)
4813                         die("No blame exist for %s", view->vid);
4815                 if (view->lines == 0 || !run_io_rd(&io, argv, opt_cdup, FORMAT_ALL)) {
4816                         report("Failed to load blame data");
4817                         return TRUE;
4818                 }
4820                 done_io(view->pipe);
4821                 view->io = io;
4822                 *read_file = FALSE;
4823                 return FALSE;
4825         } else {
4826                 size_t linelen = strlen(line);
4827                 struct blame *blame = malloc(sizeof(*blame) + linelen);
4829                 if (!blame)
4830                         return FALSE;
4832                 blame->commit = NULL;
4833                 strncpy(blame->text, line, linelen);
4834                 blame->text[linelen] = 0;
4835                 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
4836         }
4839 static bool
4840 match_blame_header(const char *name, char **line)
4842         size_t namelen = strlen(name);
4843         bool matched = !strncmp(name, *line, namelen);
4845         if (matched)
4846                 *line += namelen;
4848         return matched;
4851 static bool
4852 blame_read(struct view *view, char *line)
4854         static struct blame_commit *commit = NULL;
4855         static int blamed = 0;
4856         static bool read_file = TRUE;
4858         if (read_file)
4859                 return blame_read_file(view, line, &read_file);
4861         if (!line) {
4862                 /* Reset all! */
4863                 commit = NULL;
4864                 blamed = 0;
4865                 read_file = TRUE;
4866                 string_format(view->ref, "%s", view->vid);
4867                 if (view_is_displayed(view)) {
4868                         update_view_title(view);
4869                         redraw_view_from(view, 0);
4870                 }
4871                 return TRUE;
4872         }
4874         if (!commit) {
4875                 commit = parse_blame_commit(view, line, &blamed);
4876                 string_format(view->ref, "%s %2d%%", view->vid,
4877                               view->lines ? blamed * 100 / view->lines : 0);
4879         } else if (match_blame_header("author ", &line)) {
4880                 commit->author = get_author(line);
4882         } else if (match_blame_header("author-time ", &line)) {
4883                 parse_timesec(&commit->time, line);
4885         } else if (match_blame_header("author-tz ", &line)) {
4886                 parse_timezone(&commit->time, line);
4888         } else if (match_blame_header("summary ", &line)) {
4889                 string_ncopy(commit->title, line, strlen(line));
4891         } else if (match_blame_header("previous ", &line)) {
4892                 commit->has_previous = TRUE;
4894         } else if (match_blame_header("filename ", &line)) {
4895                 string_ncopy(commit->filename, line, strlen(line));
4896                 commit = NULL;
4897         }
4899         return TRUE;
4902 static bool
4903 blame_draw(struct view *view, struct line *line, unsigned int lineno)
4905         struct blame *blame = line->data;
4906         struct time *time = NULL;
4907         const char *id = NULL, *author = NULL;
4908         char text[SIZEOF_STR];
4910         if (blame->commit && *blame->commit->filename) {
4911                 id = blame->commit->id;
4912                 author = blame->commit->author;
4913                 time = &blame->commit->time;
4914         }
4916         if (opt_date && draw_date(view, time))
4917                 return TRUE;
4919         if (opt_author && draw_author(view, author))
4920                 return TRUE;
4922         if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
4923                 return TRUE;
4925         if (draw_lineno(view, lineno))
4926                 return TRUE;
4928         string_expand(text, sizeof(text), blame->text, opt_tab_size);
4929         draw_text(view, LINE_DEFAULT, text, TRUE);
4930         return TRUE;
4933 static bool
4934 check_blame_commit(struct blame *blame, bool check_null_id)
4936         if (!blame->commit)
4937                 report("Commit data not loaded yet");
4938         else if (check_null_id && !strcmp(blame->commit->id, NULL_ID))
4939                 report("No commit exist for the selected line");
4940         else
4941                 return TRUE;
4942         return FALSE;
4945 static void
4946 setup_blame_parent_line(struct view *view, struct blame *blame)
4948         const char *diff_tree_argv[] = {
4949                 "git", "diff-tree", "-U0", blame->commit->id,
4950                         "--", blame->commit->filename, NULL
4951         };
4952         struct io io = {};
4953         int parent_lineno = -1;
4954         int blamed_lineno = -1;
4955         char *line;
4957         if (!run_io(&io, diff_tree_argv, NULL, IO_RD))
4958                 return;
4960         while ((line = io_get(&io, '\n', TRUE))) {
4961                 if (*line == '@') {
4962                         char *pos = strchr(line, '+');
4964                         parent_lineno = atoi(line + 4);
4965                         if (pos)
4966                                 blamed_lineno = atoi(pos + 1);
4968                 } else if (*line == '+' && parent_lineno != -1) {
4969                         if (blame->lineno == blamed_lineno - 1 &&
4970                             !strcmp(blame->text, line + 1)) {
4971                                 view->lineno = parent_lineno ? parent_lineno - 1 : 0;
4972                                 break;
4973                         }
4974                         blamed_lineno++;
4975                 }
4976         }
4978         done_io(&io);
4981 static enum request
4982 blame_request(struct view *view, enum request request, struct line *line)
4984         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4985         struct blame *blame = line->data;
4987         switch (request) {
4988         case REQ_VIEW_BLAME:
4989                 if (check_blame_commit(blame, TRUE)) {
4990                         string_copy(opt_ref, blame->commit->id);
4991                         string_copy(opt_file, blame->commit->filename);
4992                         if (blame->lineno)
4993                                 view->lineno = blame->lineno;
4994                         open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4995                 }
4996                 break;
4998         case REQ_PARENT:
4999                 if (check_blame_commit(blame, TRUE) &&
5000                     select_commit_parent(blame->commit->id, opt_ref,
5001                                          blame->commit->filename)) {
5002                         string_copy(opt_file, blame->commit->filename);
5003                         setup_blame_parent_line(view, blame);
5004                         open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
5005                 }
5006                 break;
5008         case REQ_ENTER:
5009                 if (!check_blame_commit(blame, FALSE))
5010                         break;
5012                 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
5013                     !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
5014                         break;
5016                 if (!strcmp(blame->commit->id, NULL_ID)) {
5017                         struct view *diff = VIEW(REQ_VIEW_DIFF);
5018                         const char *diff_index_argv[] = {
5019                                 "git", "diff-index", "--root", "--patch-with-stat",
5020                                         "-C", "-M", "HEAD", "--", view->vid, NULL
5021                         };
5023                         if (!blame->commit->has_previous) {
5024                                 diff_index_argv[1] = "diff";
5025                                 diff_index_argv[2] = "--no-color";
5026                                 diff_index_argv[6] = "--";
5027                                 diff_index_argv[7] = "/dev/null";
5028                         }
5030                         if (!prepare_update(diff, diff_index_argv, NULL, FORMAT_DASH)) {
5031                                 report("Failed to allocate diff command");
5032                                 break;
5033                         }
5034                         flags |= OPEN_PREPARED;
5035                 }
5037                 open_view(view, REQ_VIEW_DIFF, flags);
5038                 if (VIEW(REQ_VIEW_DIFF)->pipe && !strcmp(blame->commit->id, NULL_ID))
5039                         string_copy_rev(VIEW(REQ_VIEW_DIFF)->ref, NULL_ID);
5040                 break;
5042         default:
5043                 return request;
5044         }
5046         return REQ_NONE;
5049 static bool
5050 blame_grep(struct view *view, struct line *line)
5052         struct blame *blame = line->data;
5053         struct blame_commit *commit = blame->commit;
5054         const char *text[] = {
5055                 blame->text,
5056                 commit ? commit->title : "",
5057                 commit ? commit->id : "",
5058                 commit && opt_author ? commit->author : "",
5059                 commit ? mkdate(&commit->time, opt_date) : "",
5060                 NULL
5061         };
5063         return grep_text(view, text);
5066 static void
5067 blame_select(struct view *view, struct line *line)
5069         struct blame *blame = line->data;
5070         struct blame_commit *commit = blame->commit;
5072         if (!commit)
5073                 return;
5075         if (!strcmp(commit->id, NULL_ID))
5076                 string_ncopy(ref_commit, "HEAD", 4);
5077         else
5078                 string_copy_rev(ref_commit, commit->id);
5081 static struct view_ops blame_ops = {
5082         "line",
5083         NULL,
5084         blame_open,
5085         blame_read,
5086         blame_draw,
5087         blame_request,
5088         blame_grep,
5089         blame_select,
5090 };
5092 /*
5093  * Branch backend
5094  */
5096 struct branch {
5097         const char *author;             /* Author of the last commit. */
5098         struct time time;               /* Date of the last activity. */
5099         const struct ref *ref;          /* Name and commit ID information. */
5100 };
5102 static const struct ref branch_all;
5104 static const enum sort_field branch_sort_fields[] = {
5105         ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
5106 };
5107 static struct sort_state branch_sort_state = SORT_STATE(branch_sort_fields);
5109 static int
5110 branch_compare(const void *l1, const void *l2)
5112         const struct branch *branch1 = ((const struct line *) l1)->data;
5113         const struct branch *branch2 = ((const struct line *) l2)->data;
5115         switch (get_sort_field(branch_sort_state)) {
5116         case ORDERBY_DATE:
5117                 return sort_order(branch_sort_state, timecmp(&branch1->time, &branch2->time));
5119         case ORDERBY_AUTHOR:
5120                 return sort_order(branch_sort_state, strcmp(branch1->author, branch2->author));
5122         case ORDERBY_NAME:
5123         default:
5124                 return sort_order(branch_sort_state, strcmp(branch1->ref->name, branch2->ref->name));
5125         }
5128 static bool
5129 branch_draw(struct view *view, struct line *line, unsigned int lineno)
5131         struct branch *branch = line->data;
5132         enum line_type type = branch->ref->head ? LINE_MAIN_HEAD : LINE_DEFAULT;
5134         if (opt_date && draw_date(view, &branch->time))
5135                 return TRUE;
5137         if (opt_author && draw_author(view, branch->author))
5138                 return TRUE;
5140         draw_text(view, type, branch->ref == &branch_all ? "All branches" : branch->ref->name, TRUE);
5141         return TRUE;
5144 static enum request
5145 branch_request(struct view *view, enum request request, struct line *line)
5147         struct branch *branch = line->data;
5149         switch (request) {
5150         case REQ_REFRESH:
5151                 load_refs();
5152                 open_view(view, REQ_VIEW_BRANCH, OPEN_REFRESH);
5153                 return REQ_NONE;
5155         case REQ_TOGGLE_SORT_FIELD:
5156         case REQ_TOGGLE_SORT_ORDER:
5157                 sort_view(view, request, &branch_sort_state, branch_compare);
5158                 return REQ_NONE;
5160         case REQ_ENTER:
5161                 if (branch->ref == &branch_all) {
5162                         const char *all_branches_argv[] = {
5163                                 "git", "log", "--no-color", "--pretty=raw", "--parents",
5164                                       "--topo-order", "--all", NULL
5165                         };
5166                         struct view *main_view = VIEW(REQ_VIEW_MAIN);
5168                         if (!prepare_update(main_view, all_branches_argv, NULL, FORMAT_NONE)) {
5169                                 report("Failed to load view of all branches");
5170                                 return REQ_NONE;
5171                         }
5172                         open_view(view, REQ_VIEW_MAIN, OPEN_PREPARED | OPEN_SPLIT);
5173                 } else {
5174                         open_view(view, REQ_VIEW_MAIN, OPEN_SPLIT);
5175                 }
5176                 return REQ_NONE;
5178         default:
5179                 return request;
5180         }
5183 static bool
5184 branch_read(struct view *view, char *line)
5186         static char id[SIZEOF_REV];
5187         struct branch *reference;
5188         size_t i;
5190         if (!line)
5191                 return TRUE;
5193         switch (get_line_type(line)) {
5194         case LINE_COMMIT:
5195                 string_copy_rev(id, line + STRING_SIZE("commit "));
5196                 return TRUE;
5198         case LINE_AUTHOR:
5199                 for (i = 0, reference = NULL; i < view->lines; i++) {
5200                         struct branch *branch = view->line[i].data;
5202                         if (strcmp(branch->ref->id, id))
5203                                 continue;
5205                         view->line[i].dirty = TRUE;
5206                         if (reference) {
5207                                 branch->author = reference->author;
5208                                 branch->time = reference->time;
5209                                 continue;
5210                         }
5212                         parse_author_line(line + STRING_SIZE("author "),
5213                                           &branch->author, &branch->time);
5214                         reference = branch;
5215                 }
5216                 return TRUE;
5218         default:
5219                 return TRUE;
5220         }
5224 static bool
5225 branch_open_visitor(void *data, const struct ref *ref)
5227         struct view *view = data;
5228         struct branch *branch;
5230         if (ref->tag || ref->ltag || ref->remote)
5231                 return TRUE;
5233         branch = calloc(1, sizeof(*branch));
5234         if (!branch)
5235                 return FALSE;
5237         branch->ref = ref;
5238         return !!add_line_data(view, branch, LINE_DEFAULT);
5241 static bool
5242 branch_open(struct view *view)
5244         const char *branch_log[] = {
5245                 "git", "log", "--no-color", "--pretty=raw",
5246                         "--simplify-by-decoration", "--all", NULL
5247         };
5249         if (!run_io_rd(&view->io, branch_log, NULL, FORMAT_NONE)) {
5250                 report("Failed to load branch data");
5251                 return TRUE;
5252         }
5254         setup_update(view, view->id);
5255         branch_open_visitor(view, &branch_all);
5256         foreach_ref(branch_open_visitor, view);
5257         view->p_restore = TRUE;
5259         return TRUE;
5262 static bool
5263 branch_grep(struct view *view, struct line *line)
5265         struct branch *branch = line->data;
5266         const char *text[] = {
5267                 branch->ref->name,
5268                 branch->author,
5269                 NULL
5270         };
5272         return grep_text(view, text);
5275 static void
5276 branch_select(struct view *view, struct line *line)
5278         struct branch *branch = line->data;
5280         string_copy_rev(view->ref, branch->ref->id);
5281         string_copy_rev(ref_commit, branch->ref->id);
5282         string_copy_rev(ref_head, branch->ref->id);
5285 static struct view_ops branch_ops = {
5286         "branch",
5287         NULL,
5288         branch_open,
5289         branch_read,
5290         branch_draw,
5291         branch_request,
5292         branch_grep,
5293         branch_select,
5294 };
5296 /*
5297  * Status backend
5298  */
5300 struct status {
5301         char status;
5302         struct {
5303                 mode_t mode;
5304                 char rev[SIZEOF_REV];
5305                 char name[SIZEOF_STR];
5306         } old;
5307         struct {
5308                 mode_t mode;
5309                 char rev[SIZEOF_REV];
5310                 char name[SIZEOF_STR];
5311         } new;
5312 };
5314 static char status_onbranch[SIZEOF_STR];
5315 static struct status stage_status;
5316 static enum line_type stage_line_type;
5317 static size_t stage_chunks;
5318 static int *stage_chunk;
5320 DEFINE_ALLOCATOR(realloc_ints, int, 32)
5322 /* This should work even for the "On branch" line. */
5323 static inline bool
5324 status_has_none(struct view *view, struct line *line)
5326         return line < view->line + view->lines && !line[1].data;
5329 /* Get fields from the diff line:
5330  * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
5331  */
5332 static inline bool
5333 status_get_diff(struct status *file, const char *buf, size_t bufsize)
5335         const char *old_mode = buf +  1;
5336         const char *new_mode = buf +  8;
5337         const char *old_rev  = buf + 15;
5338         const char *new_rev  = buf + 56;
5339         const char *status   = buf + 97;
5341         if (bufsize < 98 ||
5342             old_mode[-1] != ':' ||
5343             new_mode[-1] != ' ' ||
5344             old_rev[-1]  != ' ' ||
5345             new_rev[-1]  != ' ' ||
5346             status[-1]   != ' ')
5347                 return FALSE;
5349         file->status = *status;
5351         string_copy_rev(file->old.rev, old_rev);
5352         string_copy_rev(file->new.rev, new_rev);
5354         file->old.mode = strtoul(old_mode, NULL, 8);
5355         file->new.mode = strtoul(new_mode, NULL, 8);
5357         file->old.name[0] = file->new.name[0] = 0;
5359         return TRUE;
5362 static bool
5363 status_run(struct view *view, const char *argv[], char status, enum line_type type)
5365         struct status *unmerged = NULL;
5366         char *buf;
5367         struct io io = {};
5369         if (!run_io(&io, argv, opt_cdup, IO_RD))
5370                 return FALSE;
5372         add_line_data(view, NULL, type);
5374         while ((buf = io_get(&io, 0, TRUE))) {
5375                 struct status *file = unmerged;
5377                 if (!file) {
5378                         file = calloc(1, sizeof(*file));
5379                         if (!file || !add_line_data(view, file, type))
5380                                 goto error_out;
5381                 }
5383                 /* Parse diff info part. */
5384                 if (status) {
5385                         file->status = status;
5386                         if (status == 'A')
5387                                 string_copy(file->old.rev, NULL_ID);
5389                 } else if (!file->status || file == unmerged) {
5390                         if (!status_get_diff(file, buf, strlen(buf)))
5391                                 goto error_out;
5393                         buf = io_get(&io, 0, TRUE);
5394                         if (!buf)
5395                                 break;
5397                         /* Collapse all modified entries that follow an
5398                          * associated unmerged entry. */
5399                         if (unmerged == file) {
5400                                 unmerged->status = 'U';
5401                                 unmerged = NULL;
5402                         } else if (file->status == 'U') {
5403                                 unmerged = file;
5404                         }
5405                 }
5407                 /* Grab the old name for rename/copy. */
5408                 if (!*file->old.name &&
5409                     (file->status == 'R' || file->status == 'C')) {
5410                         string_ncopy(file->old.name, buf, strlen(buf));
5412                         buf = io_get(&io, 0, TRUE);
5413                         if (!buf)
5414                                 break;
5415                 }
5417                 /* git-ls-files just delivers a NUL separated list of
5418                  * file names similar to the second half of the
5419                  * git-diff-* output. */
5420                 string_ncopy(file->new.name, buf, strlen(buf));
5421                 if (!*file->old.name)
5422                         string_copy(file->old.name, file->new.name);
5423                 file = NULL;
5424         }
5426         if (io_error(&io)) {
5427 error_out:
5428                 done_io(&io);
5429                 return FALSE;
5430         }
5432         if (!view->line[view->lines - 1].data)
5433                 add_line_data(view, NULL, LINE_STAT_NONE);
5435         done_io(&io);
5436         return TRUE;
5439 /* Don't show unmerged entries in the staged section. */
5440 static const char *status_diff_index_argv[] = {
5441         "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
5442                              "--cached", "-M", "HEAD", NULL
5443 };
5445 static const char *status_diff_files_argv[] = {
5446         "git", "diff-files", "-z", NULL
5447 };
5449 static const char *status_list_other_argv[] = {
5450         "git", "ls-files", "-z", "--others", "--exclude-standard", opt_prefix, NULL
5451 };
5453 static const char *status_list_no_head_argv[] = {
5454         "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
5455 };
5457 static const char *update_index_argv[] = {
5458         "git", "update-index", "-q", "--unmerged", "--refresh", NULL
5459 };
5461 /* Restore the previous line number to stay in the context or select a
5462  * line with something that can be updated. */
5463 static void
5464 status_restore(struct view *view)
5466         if (view->p_lineno >= view->lines)
5467                 view->p_lineno = view->lines - 1;
5468         while (view->p_lineno < view->lines && !view->line[view->p_lineno].data)
5469                 view->p_lineno++;
5470         while (view->p_lineno > 0 && !view->line[view->p_lineno].data)
5471                 view->p_lineno--;
5473         /* If the above fails, always skip the "On branch" line. */
5474         if (view->p_lineno < view->lines)
5475                 view->lineno = view->p_lineno;
5476         else
5477                 view->lineno = 1;
5479         if (view->lineno < view->offset)
5480                 view->offset = view->lineno;
5481         else if (view->offset + view->height <= view->lineno)
5482                 view->offset = view->lineno - view->height + 1;
5484         view->p_restore = FALSE;
5487 static void
5488 status_update_onbranch(void)
5490         static const char *paths[][2] = {
5491                 { "rebase-apply/rebasing",      "Rebasing" },
5492                 { "rebase-apply/applying",      "Applying mailbox" },
5493                 { "rebase-apply/",              "Rebasing mailbox" },
5494                 { "rebase-merge/interactive",   "Interactive rebase" },
5495                 { "rebase-merge/",              "Rebase merge" },
5496                 { "MERGE_HEAD",                 "Merging" },
5497                 { "BISECT_LOG",                 "Bisecting" },
5498                 { "HEAD",                       "On branch" },
5499         };
5500         char buf[SIZEOF_STR];
5501         struct stat stat;
5502         int i;
5504         if (is_initial_commit()) {
5505                 string_copy(status_onbranch, "Initial commit");
5506                 return;
5507         }
5509         for (i = 0; i < ARRAY_SIZE(paths); i++) {
5510                 char *head = opt_head;
5512                 if (!string_format(buf, "%s/%s", opt_git_dir, paths[i][0]) ||
5513                     lstat(buf, &stat) < 0)
5514                         continue;
5516                 if (!*opt_head) {
5517                         struct io io = {};
5519                         if (io_open(&io, "%s/rebase-merge/head-name", opt_git_dir) &&
5520                             io_read_buf(&io, buf, sizeof(buf))) {
5521                                 head = buf;
5522                                 if (!prefixcmp(head, "refs/heads/"))
5523                                         head += STRING_SIZE("refs/heads/");
5524                         }
5525                 }
5527                 if (!string_format(status_onbranch, "%s %s", paths[i][1], head))
5528                         string_copy(status_onbranch, opt_head);
5529                 return;
5530         }
5532         string_copy(status_onbranch, "Not currently on any branch");
5535 /* First parse staged info using git-diff-index(1), then parse unstaged
5536  * info using git-diff-files(1), and finally untracked files using
5537  * git-ls-files(1). */
5538 static bool
5539 status_open(struct view *view)
5541         reset_view(view);
5543         add_line_data(view, NULL, LINE_STAT_HEAD);
5544         status_update_onbranch();
5546         run_io_bg(update_index_argv);
5548         if (is_initial_commit()) {
5549                 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
5550                         return FALSE;
5551         } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
5552                 return FALSE;
5553         }
5555         if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
5556             !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
5557                 return FALSE;
5559         /* Restore the exact position or use the specialized restore
5560          * mode? */
5561         if (!view->p_restore)
5562                 status_restore(view);
5563         return TRUE;
5566 static bool
5567 status_draw(struct view *view, struct line *line, unsigned int lineno)
5569         struct status *status = line->data;
5570         enum line_type type;
5571         const char *text;
5573         if (!status) {
5574                 switch (line->type) {
5575                 case LINE_STAT_STAGED:
5576                         type = LINE_STAT_SECTION;
5577                         text = "Changes to be committed:";
5578                         break;
5580                 case LINE_STAT_UNSTAGED:
5581                         type = LINE_STAT_SECTION;
5582                         text = "Changed but not updated:";
5583                         break;
5585                 case LINE_STAT_UNTRACKED:
5586                         type = LINE_STAT_SECTION;
5587                         text = "Untracked files:";
5588                         break;
5590                 case LINE_STAT_NONE:
5591                         type = LINE_DEFAULT;
5592                         text = "  (no files)";
5593                         break;
5595                 case LINE_STAT_HEAD:
5596                         type = LINE_STAT_HEAD;
5597                         text = status_onbranch;
5598                         break;
5600                 default:
5601                         return FALSE;
5602                 }
5603         } else {
5604                 static char buf[] = { '?', ' ', ' ', ' ', 0 };
5606                 buf[0] = status->status;
5607                 if (draw_text(view, line->type, buf, TRUE))
5608                         return TRUE;
5609                 type = LINE_DEFAULT;
5610                 text = status->new.name;
5611         }
5613         draw_text(view, type, text, TRUE);
5614         return TRUE;
5617 static enum request
5618 status_load_error(struct view *view, struct view *stage, const char *path)
5620         if (displayed_views() == 2 || display[current_view] != view)
5621                 maximize_view(view);
5622         report("Failed to load '%s': %s", path, io_strerror(&stage->io));
5623         return REQ_NONE;
5626 static enum request
5627 status_enter(struct view *view, struct line *line)
5629         struct status *status = line->data;
5630         const char *oldpath = status ? status->old.name : NULL;
5631         /* Diffs for unmerged entries are empty when passing the new
5632          * path, so leave it empty. */
5633         const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
5634         const char *info;
5635         enum open_flags split;
5636         struct view *stage = VIEW(REQ_VIEW_STAGE);
5638         if (line->type == LINE_STAT_NONE ||
5639             (!status && line[1].type == LINE_STAT_NONE)) {
5640                 report("No file to diff");
5641                 return REQ_NONE;
5642         }
5644         switch (line->type) {
5645         case LINE_STAT_STAGED:
5646                 if (is_initial_commit()) {
5647                         const char *no_head_diff_argv[] = {
5648                                 "git", "diff", "--no-color", "--patch-with-stat",
5649                                         "--", "/dev/null", newpath, NULL
5650                         };
5652                         if (!prepare_update(stage, no_head_diff_argv, opt_cdup, FORMAT_DASH))
5653                                 return status_load_error(view, stage, newpath);
5654                 } else {
5655                         const char *index_show_argv[] = {
5656                                 "git", "diff-index", "--root", "--patch-with-stat",
5657                                         "-C", "-M", "--cached", "HEAD", "--",
5658                                         oldpath, newpath, NULL
5659                         };
5661                         if (!prepare_update(stage, index_show_argv, opt_cdup, FORMAT_DASH))
5662                                 return status_load_error(view, stage, newpath);
5663                 }
5665                 if (status)
5666                         info = "Staged changes to %s";
5667                 else
5668                         info = "Staged changes";
5669                 break;
5671         case LINE_STAT_UNSTAGED:
5672         {
5673                 const char *files_show_argv[] = {
5674                         "git", "diff-files", "--root", "--patch-with-stat",
5675                                 "-C", "-M", "--", oldpath, newpath, NULL
5676                 };
5678                 if (!prepare_update(stage, files_show_argv, opt_cdup, FORMAT_DASH))
5679                         return status_load_error(view, stage, newpath);
5680                 if (status)
5681                         info = "Unstaged changes to %s";
5682                 else
5683                         info = "Unstaged changes";
5684                 break;
5685         }
5686         case LINE_STAT_UNTRACKED:
5687                 if (!newpath) {
5688                         report("No file to show");
5689                         return REQ_NONE;
5690                 }
5692                 if (!suffixcmp(status->new.name, -1, "/")) {
5693                         report("Cannot display a directory");
5694                         return REQ_NONE;
5695                 }
5697                 if (!prepare_update_file(stage, newpath))
5698                         return status_load_error(view, stage, newpath);
5699                 info = "Untracked file %s";
5700                 break;
5702         case LINE_STAT_HEAD:
5703                 return REQ_NONE;
5705         default:
5706                 die("line type %d not handled in switch", line->type);
5707         }
5709         split = view_is_displayed(view) ? OPEN_SPLIT : 0;
5710         open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split);
5711         if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
5712                 if (status) {
5713                         stage_status = *status;
5714                 } else {
5715                         memset(&stage_status, 0, sizeof(stage_status));
5716                 }
5718                 stage_line_type = line->type;
5719                 stage_chunks = 0;
5720                 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
5721         }
5723         return REQ_NONE;
5726 static bool
5727 status_exists(struct status *status, enum line_type type)
5729         struct view *view = VIEW(REQ_VIEW_STATUS);
5730         unsigned long lineno;
5732         for (lineno = 0; lineno < view->lines; lineno++) {
5733                 struct line *line = &view->line[lineno];
5734                 struct status *pos = line->data;
5736                 if (line->type != type)
5737                         continue;
5738                 if (!pos && (!status || !status->status) && line[1].data) {
5739                         select_view_line(view, lineno);
5740                         return TRUE;
5741                 }
5742                 if (pos && !strcmp(status->new.name, pos->new.name)) {
5743                         select_view_line(view, lineno);
5744                         return TRUE;
5745                 }
5746         }
5748         return FALSE;
5752 static bool
5753 status_update_prepare(struct io *io, enum line_type type)
5755         const char *staged_argv[] = {
5756                 "git", "update-index", "-z", "--index-info", NULL
5757         };
5758         const char *others_argv[] = {
5759                 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
5760         };
5762         switch (type) {
5763         case LINE_STAT_STAGED:
5764                 return run_io(io, staged_argv, opt_cdup, IO_WR);
5766         case LINE_STAT_UNSTAGED:
5767         case LINE_STAT_UNTRACKED:
5768                 return run_io(io, others_argv, opt_cdup, IO_WR);
5770         default:
5771                 die("line type %d not handled in switch", type);
5772                 return FALSE;
5773         }
5776 static bool
5777 status_update_write(struct io *io, struct status *status, enum line_type type)
5779         char buf[SIZEOF_STR];
5780         size_t bufsize = 0;
5782         switch (type) {
5783         case LINE_STAT_STAGED:
5784                 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
5785                                         status->old.mode,
5786                                         status->old.rev,
5787                                         status->old.name, 0))
5788                         return FALSE;
5789                 break;
5791         case LINE_STAT_UNSTAGED:
5792         case LINE_STAT_UNTRACKED:
5793                 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
5794                         return FALSE;
5795                 break;
5797         default:
5798                 die("line type %d not handled in switch", type);
5799         }
5801         return io_write(io, buf, bufsize);
5804 static bool
5805 status_update_file(struct status *status, enum line_type type)
5807         struct io io = {};
5808         bool result;
5810         if (!status_update_prepare(&io, type))
5811                 return FALSE;
5813         result = status_update_write(&io, status, type);
5814         return done_io(&io) && result;
5817 static bool
5818 status_update_files(struct view *view, struct line *line)
5820         char buf[sizeof(view->ref)];
5821         struct io io = {};
5822         bool result = TRUE;
5823         struct line *pos = view->line + view->lines;
5824         int files = 0;
5825         int file, done;
5826         int cursor_y = -1, cursor_x = -1;
5828         if (!status_update_prepare(&io, line->type))
5829                 return FALSE;
5831         for (pos = line; pos < view->line + view->lines && pos->data; pos++)
5832                 files++;
5834         string_copy(buf, view->ref);
5835         getsyx(cursor_y, cursor_x);
5836         for (file = 0, done = 5; result && file < files; line++, file++) {
5837                 int almost_done = file * 100 / files;
5839                 if (almost_done > done) {
5840                         done = almost_done;
5841                         string_format(view->ref, "updating file %u of %u (%d%% done)",
5842                                       file, files, done);
5843                         update_view_title(view);
5844                         setsyx(cursor_y, cursor_x);
5845                         doupdate();
5846                 }
5847                 result = status_update_write(&io, line->data, line->type);
5848         }
5849         string_copy(view->ref, buf);
5851         return done_io(&io) && result;
5854 static bool
5855 status_update(struct view *view)
5857         struct line *line = &view->line[view->lineno];
5859         assert(view->lines);
5861         if (!line->data) {
5862                 /* This should work even for the "On branch" line. */
5863                 if (line < view->line + view->lines && !line[1].data) {
5864                         report("Nothing to update");
5865                         return FALSE;
5866                 }
5868                 if (!status_update_files(view, line + 1)) {
5869                         report("Failed to update file status");
5870                         return FALSE;
5871                 }
5873         } else if (!status_update_file(line->data, line->type)) {
5874                 report("Failed to update file status");
5875                 return FALSE;
5876         }
5878         return TRUE;
5881 static bool
5882 status_revert(struct status *status, enum line_type type, bool has_none)
5884         if (!status || type != LINE_STAT_UNSTAGED) {
5885                 if (type == LINE_STAT_STAGED) {
5886                         report("Cannot revert changes to staged files");
5887                 } else if (type == LINE_STAT_UNTRACKED) {
5888                         report("Cannot revert changes to untracked files");
5889                 } else if (has_none) {
5890                         report("Nothing to revert");
5891                 } else {
5892                         report("Cannot revert changes to multiple files");
5893                 }
5895         } else if (prompt_yesno("Are you sure you want to revert changes?")) {
5896                 char mode[10] = "100644";
5897                 const char *reset_argv[] = {
5898                         "git", "update-index", "--cacheinfo", mode,
5899                                 status->old.rev, status->old.name, NULL
5900                 };
5901                 const char *checkout_argv[] = {
5902                         "git", "checkout", "--", status->old.name, NULL
5903                 };
5905                 if (status->status == 'U') {
5906                         string_format(mode, "%5o", status->old.mode);
5908                         if (status->old.mode == 0 && status->new.mode == 0) {
5909                                 reset_argv[2] = "--force-remove";
5910                                 reset_argv[3] = status->old.name;
5911                                 reset_argv[4] = NULL;
5912                         }
5914                         if (!run_io_fg(reset_argv, opt_cdup))
5915                                 return FALSE;
5916                         if (status->old.mode == 0 && status->new.mode == 0)
5917                                 return TRUE;
5918                 }
5920                 return run_io_fg(checkout_argv, opt_cdup);
5921         }
5923         return FALSE;
5926 static enum request
5927 status_request(struct view *view, enum request request, struct line *line)
5929         struct status *status = line->data;
5931         switch (request) {
5932         case REQ_STATUS_UPDATE:
5933                 if (!status_update(view))
5934                         return REQ_NONE;
5935                 break;
5937         case REQ_STATUS_REVERT:
5938                 if (!status_revert(status, line->type, status_has_none(view, line)))
5939                         return REQ_NONE;
5940                 break;
5942         case REQ_STATUS_MERGE:
5943                 if (!status || status->status != 'U') {
5944                         report("Merging only possible for files with unmerged status ('U').");
5945                         return REQ_NONE;
5946                 }
5947                 open_mergetool(status->new.name);
5948                 break;
5950         case REQ_EDIT:
5951                 if (!status)
5952                         return request;
5953                 if (status->status == 'D') {
5954                         report("File has been deleted.");
5955                         return REQ_NONE;
5956                 }
5958                 open_editor(status->new.name);
5959                 break;
5961         case REQ_VIEW_BLAME:
5962                 if (status)
5963                         opt_ref[0] = 0;
5964                 return request;
5966         case REQ_ENTER:
5967                 /* After returning the status view has been split to
5968                  * show the stage view. No further reloading is
5969                  * necessary. */
5970                 return status_enter(view, line);
5972         case REQ_REFRESH:
5973                 /* Simply reload the view. */
5974                 break;
5976         default:
5977                 return request;
5978         }
5980         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
5982         return REQ_NONE;
5985 static void
5986 status_select(struct view *view, struct line *line)
5988         struct status *status = line->data;
5989         char file[SIZEOF_STR] = "all files";
5990         const char *text;
5991         const char *key;
5993         if (status && !string_format(file, "'%s'", status->new.name))
5994                 return;
5996         if (!status && line[1].type == LINE_STAT_NONE)
5997                 line++;
5999         switch (line->type) {
6000         case LINE_STAT_STAGED:
6001                 text = "Press %s to unstage %s for commit";
6002                 break;
6004         case LINE_STAT_UNSTAGED:
6005                 text = "Press %s to stage %s for commit";
6006                 break;
6008         case LINE_STAT_UNTRACKED:
6009                 text = "Press %s to stage %s for addition";
6010                 break;
6012         case LINE_STAT_HEAD:
6013         case LINE_STAT_NONE:
6014                 text = "Nothing to update";
6015                 break;
6017         default:
6018                 die("line type %d not handled in switch", line->type);
6019         }
6021         if (status && status->status == 'U') {
6022                 text = "Press %s to resolve conflict in %s";
6023                 key = get_key(KEYMAP_STATUS, REQ_STATUS_MERGE);
6025         } else {
6026                 key = get_key(KEYMAP_STATUS, REQ_STATUS_UPDATE);
6027         }
6029         string_format(view->ref, text, key, file);
6030         if (status)
6031                 string_copy(opt_file, status->new.name);
6034 static bool
6035 status_grep(struct view *view, struct line *line)
6037         struct status *status = line->data;
6039         if (status) {
6040                 const char buf[2] = { status->status, 0 };
6041                 const char *text[] = { status->new.name, buf, NULL };
6043                 return grep_text(view, text);
6044         }
6046         return FALSE;
6049 static struct view_ops status_ops = {
6050         "file",
6051         NULL,
6052         status_open,
6053         NULL,
6054         status_draw,
6055         status_request,
6056         status_grep,
6057         status_select,
6058 };
6061 static bool
6062 stage_diff_write(struct io *io, struct line *line, struct line *end)
6064         while (line < end) {
6065                 if (!io_write(io, line->data, strlen(line->data)) ||
6066                     !io_write(io, "\n", 1))
6067                         return FALSE;
6068                 line++;
6069                 if (line->type == LINE_DIFF_CHUNK ||
6070                     line->type == LINE_DIFF_HEADER)
6071                         break;
6072         }
6074         return TRUE;
6077 static struct line *
6078 stage_diff_find(struct view *view, struct line *line, enum line_type type)
6080         for (; view->line < line; line--)
6081                 if (line->type == type)
6082                         return line;
6084         return NULL;
6087 static bool
6088 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
6090         const char *apply_argv[SIZEOF_ARG] = {
6091                 "git", "apply", "--whitespace=nowarn", NULL
6092         };
6093         struct line *diff_hdr;
6094         struct io io = {};
6095         int argc = 3;
6097         diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
6098         if (!diff_hdr)
6099                 return FALSE;
6101         if (!revert)
6102                 apply_argv[argc++] = "--cached";
6103         if (revert || stage_line_type == LINE_STAT_STAGED)
6104                 apply_argv[argc++] = "-R";
6105         apply_argv[argc++] = "-";
6106         apply_argv[argc++] = NULL;
6107         if (!run_io(&io, apply_argv, opt_cdup, IO_WR))
6108                 return FALSE;
6110         if (!stage_diff_write(&io, diff_hdr, chunk) ||
6111             !stage_diff_write(&io, chunk, view->line + view->lines))
6112                 chunk = NULL;
6114         done_io(&io);
6115         run_io_bg(update_index_argv);
6117         return chunk ? TRUE : FALSE;
6120 static bool
6121 stage_update(struct view *view, struct line *line)
6123         struct line *chunk = NULL;
6125         if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
6126                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6128         if (chunk) {
6129                 if (!stage_apply_chunk(view, chunk, FALSE)) {
6130                         report("Failed to apply chunk");
6131                         return FALSE;
6132                 }
6134         } else if (!stage_status.status) {
6135                 view = VIEW(REQ_VIEW_STATUS);
6137                 for (line = view->line; line < view->line + view->lines; line++)
6138                         if (line->type == stage_line_type)
6139                                 break;
6141                 if (!status_update_files(view, line + 1)) {
6142                         report("Failed to update files");
6143                         return FALSE;
6144                 }
6146         } else if (!status_update_file(&stage_status, stage_line_type)) {
6147                 report("Failed to update file");
6148                 return FALSE;
6149         }
6151         return TRUE;
6154 static bool
6155 stage_revert(struct view *view, struct line *line)
6157         struct line *chunk = NULL;
6159         if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
6160                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6162         if (chunk) {
6163                 if (!prompt_yesno("Are you sure you want to revert changes?"))
6164                         return FALSE;
6166                 if (!stage_apply_chunk(view, chunk, TRUE)) {
6167                         report("Failed to revert chunk");
6168                         return FALSE;
6169                 }
6170                 return TRUE;
6172         } else {
6173                 return status_revert(stage_status.status ? &stage_status : NULL,
6174                                      stage_line_type, FALSE);
6175         }
6179 static void
6180 stage_next(struct view *view, struct line *line)
6182         int i;
6184         if (!stage_chunks) {
6185                 for (line = view->line; line < view->line + view->lines; line++) {
6186                         if (line->type != LINE_DIFF_CHUNK)
6187                                 continue;
6189                         if (!realloc_ints(&stage_chunk, stage_chunks, 1)) {
6190                                 report("Allocation failure");
6191                                 return;
6192                         }
6194                         stage_chunk[stage_chunks++] = line - view->line;
6195                 }
6196         }
6198         for (i = 0; i < stage_chunks; i++) {
6199                 if (stage_chunk[i] > view->lineno) {
6200                         do_scroll_view(view, stage_chunk[i] - view->lineno);
6201                         report("Chunk %d of %d", i + 1, stage_chunks);
6202                         return;
6203                 }
6204         }
6206         report("No next chunk found");
6209 static enum request
6210 stage_request(struct view *view, enum request request, struct line *line)
6212         switch (request) {
6213         case REQ_STATUS_UPDATE:
6214                 if (!stage_update(view, line))
6215                         return REQ_NONE;
6216                 break;
6218         case REQ_STATUS_REVERT:
6219                 if (!stage_revert(view, line))
6220                         return REQ_NONE;
6221                 break;
6223         case REQ_STAGE_NEXT:
6224                 if (stage_line_type == LINE_STAT_UNTRACKED) {
6225                         report("File is untracked; press %s to add",
6226                                get_key(KEYMAP_STAGE, REQ_STATUS_UPDATE));
6227                         return REQ_NONE;
6228                 }
6229                 stage_next(view, line);
6230                 return REQ_NONE;
6232         case REQ_EDIT:
6233                 if (!stage_status.new.name[0])
6234                         return request;
6235                 if (stage_status.status == 'D') {
6236                         report("File has been deleted.");
6237                         return REQ_NONE;
6238                 }
6240                 open_editor(stage_status.new.name);
6241                 break;
6243         case REQ_REFRESH:
6244                 /* Reload everything ... */
6245                 break;
6247         case REQ_VIEW_BLAME:
6248                 if (stage_status.new.name[0]) {
6249                         string_copy(opt_file, stage_status.new.name);
6250                         opt_ref[0] = 0;
6251                 }
6252                 return request;
6254         case REQ_ENTER:
6255                 return pager_request(view, request, line);
6257         default:
6258                 return request;
6259         }
6261         VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
6262         open_view(view, REQ_VIEW_STATUS, OPEN_REFRESH);
6264         /* Check whether the staged entry still exists, and close the
6265          * stage view if it doesn't. */
6266         if (!status_exists(&stage_status, stage_line_type)) {
6267                 status_restore(VIEW(REQ_VIEW_STATUS));
6268                 return REQ_VIEW_CLOSE;
6269         }
6271         if (stage_line_type == LINE_STAT_UNTRACKED) {
6272                 if (!suffixcmp(stage_status.new.name, -1, "/")) {
6273                         report("Cannot display a directory");
6274                         return REQ_NONE;
6275                 }
6277                 if (!prepare_update_file(view, stage_status.new.name)) {
6278                         report("Failed to open file: %s", strerror(errno));
6279                         return REQ_NONE;
6280                 }
6281         }
6282         open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
6284         return REQ_NONE;
6287 static struct view_ops stage_ops = {
6288         "line",
6289         NULL,
6290         NULL,
6291         pager_read,
6292         pager_draw,
6293         stage_request,
6294         pager_grep,
6295         pager_select,
6296 };
6299 /*
6300  * Revision graph
6301  */
6303 struct commit {
6304         char id[SIZEOF_REV];            /* SHA1 ID. */
6305         char title[128];                /* First line of the commit message. */
6306         const char *author;             /* Author of the commit. */
6307         struct time time;               /* Date from the author ident. */
6308         struct ref_list *refs;          /* Repository references. */
6309         chtype graph[SIZEOF_REVGRAPH];  /* Ancestry chain graphics. */
6310         size_t graph_size;              /* The width of the graph array. */
6311         bool has_parents;               /* Rewritten --parents seen. */
6312 };
6314 /* Size of rev graph with no  "padding" columns */
6315 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
6317 struct rev_graph {
6318         struct rev_graph *prev, *next, *parents;
6319         char rev[SIZEOF_REVITEMS][SIZEOF_REV];
6320         size_t size;
6321         struct commit *commit;
6322         size_t pos;
6323         unsigned int boundary:1;
6324 };
6326 /* Parents of the commit being visualized. */
6327 static struct rev_graph graph_parents[4];
6329 /* The current stack of revisions on the graph. */
6330 static struct rev_graph graph_stacks[4] = {
6331         { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
6332         { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
6333         { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
6334         { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
6335 };
6337 static inline bool
6338 graph_parent_is_merge(struct rev_graph *graph)
6340         return graph->parents->size > 1;
6343 static inline void
6344 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
6346         struct commit *commit = graph->commit;
6348         if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
6349                 commit->graph[commit->graph_size++] = symbol;
6352 static void
6353 clear_rev_graph(struct rev_graph *graph)
6355         graph->boundary = 0;
6356         graph->size = graph->pos = 0;
6357         graph->commit = NULL;
6358         memset(graph->parents, 0, sizeof(*graph->parents));
6361 static void
6362 done_rev_graph(struct rev_graph *graph)
6364         if (graph_parent_is_merge(graph) &&
6365             graph->pos < graph->size - 1 &&
6366             graph->next->size == graph->size + graph->parents->size - 1) {
6367                 size_t i = graph->pos + graph->parents->size - 1;
6369                 graph->commit->graph_size = i * 2;
6370                 while (i < graph->next->size - 1) {
6371                         append_to_rev_graph(graph, ' ');
6372                         append_to_rev_graph(graph, '\\');
6373                         i++;
6374                 }
6375         }
6377         clear_rev_graph(graph);
6380 static void
6381 push_rev_graph(struct rev_graph *graph, const char *parent)
6383         int i;
6385         /* "Collapse" duplicate parents lines.
6386          *
6387          * FIXME: This needs to also update update the drawn graph but
6388          * for now it just serves as a method for pruning graph lines. */
6389         for (i = 0; i < graph->size; i++)
6390                 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
6391                         return;
6393         if (graph->size < SIZEOF_REVITEMS) {
6394                 string_copy_rev(graph->rev[graph->size++], parent);
6395         }
6398 static chtype
6399 get_rev_graph_symbol(struct rev_graph *graph)
6401         chtype symbol;
6403         if (graph->boundary)
6404                 symbol = REVGRAPH_BOUND;
6405         else if (graph->parents->size == 0)
6406                 symbol = REVGRAPH_INIT;
6407         else if (graph_parent_is_merge(graph))
6408                 symbol = REVGRAPH_MERGE;
6409         else if (graph->pos >= graph->size)
6410                 symbol = REVGRAPH_BRANCH;
6411         else
6412                 symbol = REVGRAPH_COMMIT;
6414         return symbol;
6417 static void
6418 draw_rev_graph(struct rev_graph *graph)
6420         struct rev_filler {
6421                 chtype separator, line;
6422         };
6423         enum { DEFAULT, RSHARP, RDIAG, LDIAG };
6424         static struct rev_filler fillers[] = {
6425                 { ' ',  '|' },
6426                 { '`',  '.' },
6427                 { '\'', ' ' },
6428                 { '/',  ' ' },
6429         };
6430         chtype symbol = get_rev_graph_symbol(graph);
6431         struct rev_filler *filler;
6432         size_t i;
6434         fillers[DEFAULT].line = opt_line_graphics ? ACS_VLINE : '|';
6435         filler = &fillers[DEFAULT];
6437         for (i = 0; i < graph->pos; i++) {
6438                 append_to_rev_graph(graph, filler->line);
6439                 if (graph_parent_is_merge(graph->prev) &&
6440                     graph->prev->pos == i)
6441                         filler = &fillers[RSHARP];
6443                 append_to_rev_graph(graph, filler->separator);
6444         }
6446         /* Place the symbol for this revision. */
6447         append_to_rev_graph(graph, symbol);
6449         if (graph->prev->size > graph->size)
6450                 filler = &fillers[RDIAG];
6451         else
6452                 filler = &fillers[DEFAULT];
6454         i++;
6456         for (; i < graph->size; i++) {
6457                 append_to_rev_graph(graph, filler->separator);
6458                 append_to_rev_graph(graph, filler->line);
6459                 if (graph_parent_is_merge(graph->prev) &&
6460                     i < graph->prev->pos + graph->parents->size)
6461                         filler = &fillers[RSHARP];
6462                 if (graph->prev->size > graph->size)
6463                         filler = &fillers[LDIAG];
6464         }
6466         if (graph->prev->size > graph->size) {
6467                 append_to_rev_graph(graph, filler->separator);
6468                 if (filler->line != ' ')
6469                         append_to_rev_graph(graph, filler->line);
6470         }
6473 /* Prepare the next rev graph */
6474 static void
6475 prepare_rev_graph(struct rev_graph *graph)
6477         size_t i;
6479         /* First, traverse all lines of revisions up to the active one. */
6480         for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
6481                 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
6482                         break;
6484                 push_rev_graph(graph->next, graph->rev[graph->pos]);
6485         }
6487         /* Interleave the new revision parent(s). */
6488         for (i = 0; !graph->boundary && i < graph->parents->size; i++)
6489                 push_rev_graph(graph->next, graph->parents->rev[i]);
6491         /* Lastly, put any remaining revisions. */
6492         for (i = graph->pos + 1; i < graph->size; i++)
6493                 push_rev_graph(graph->next, graph->rev[i]);
6496 static void
6497 update_rev_graph(struct view *view, struct rev_graph *graph)
6499         /* If this is the finalizing update ... */
6500         if (graph->commit)
6501                 prepare_rev_graph(graph);
6503         /* Graph visualization needs a one rev look-ahead,
6504          * so the first update doesn't visualize anything. */
6505         if (!graph->prev->commit)
6506                 return;
6508         if (view->lines > 2)
6509                 view->line[view->lines - 3].dirty = 1;
6510         if (view->lines > 1)
6511                 view->line[view->lines - 2].dirty = 1;
6512         draw_rev_graph(graph->prev);
6513         done_rev_graph(graph->prev->prev);
6517 /*
6518  * Main view backend
6519  */
6521 static const char *main_argv[SIZEOF_ARG] = {
6522         "git", "log", "--no-color", "--pretty=raw", "--parents",
6523                       "--topo-order", "%(head)", NULL
6524 };
6526 static bool
6527 main_draw(struct view *view, struct line *line, unsigned int lineno)
6529         struct commit *commit = line->data;
6531         if (!commit->author)
6532                 return FALSE;
6534         if (opt_date && draw_date(view, &commit->time))
6535                 return TRUE;
6537         if (opt_author && draw_author(view, commit->author))
6538                 return TRUE;
6540         if (opt_rev_graph && commit->graph_size &&
6541             draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
6542                 return TRUE;
6544         if (opt_show_refs && commit->refs) {
6545                 size_t i;
6547                 for (i = 0; i < commit->refs->size; i++) {
6548                         struct ref *ref = commit->refs->refs[i];
6549                         enum line_type type;
6551                         if (ref->head)
6552                                 type = LINE_MAIN_HEAD;
6553                         else if (ref->ltag)
6554                                 type = LINE_MAIN_LOCAL_TAG;
6555                         else if (ref->tag)
6556                                 type = LINE_MAIN_TAG;
6557                         else if (ref->tracked)
6558                                 type = LINE_MAIN_TRACKED;
6559                         else if (ref->remote)
6560                                 type = LINE_MAIN_REMOTE;
6561                         else
6562                                 type = LINE_MAIN_REF;
6564                         if (draw_text(view, type, "[", TRUE) ||
6565                             draw_text(view, type, ref->name, TRUE) ||
6566                             draw_text(view, type, "]", TRUE))
6567                                 return TRUE;
6569                         if (draw_text(view, LINE_DEFAULT, " ", TRUE))
6570                                 return TRUE;
6571                 }
6572         }
6574         draw_text(view, LINE_DEFAULT, commit->title, TRUE);
6575         return TRUE;
6578 /* Reads git log --pretty=raw output and parses it into the commit struct. */
6579 static bool
6580 main_read(struct view *view, char *line)
6582         static struct rev_graph *graph = graph_stacks;
6583         enum line_type type;
6584         struct commit *commit;
6586         if (!line) {
6587                 int i;
6589                 if (!view->lines && !view->parent)
6590                         die("No revisions match the given arguments.");
6591                 if (view->lines > 0) {
6592                         commit = view->line[view->lines - 1].data;
6593                         view->line[view->lines - 1].dirty = 1;
6594                         if (!commit->author) {
6595                                 view->lines--;
6596                                 free(commit);
6597                                 graph->commit = NULL;
6598                         }
6599                 }
6600                 update_rev_graph(view, graph);
6602                 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
6603                         clear_rev_graph(&graph_stacks[i]);
6604                 return TRUE;
6605         }
6607         type = get_line_type(line);
6608         if (type == LINE_COMMIT) {
6609                 commit = calloc(1, sizeof(struct commit));
6610                 if (!commit)
6611                         return FALSE;
6613                 line += STRING_SIZE("commit ");
6614                 if (*line == '-') {
6615                         graph->boundary = 1;
6616                         line++;
6617                 }
6619                 string_copy_rev(commit->id, line);
6620                 commit->refs = get_ref_list(commit->id);
6621                 graph->commit = commit;
6622                 add_line_data(view, commit, LINE_MAIN_COMMIT);
6624                 while ((line = strchr(line, ' '))) {
6625                         line++;
6626                         push_rev_graph(graph->parents, line);
6627                         commit->has_parents = TRUE;
6628                 }
6629                 return TRUE;
6630         }
6632         if (!view->lines)
6633                 return TRUE;
6634         commit = view->line[view->lines - 1].data;
6636         switch (type) {
6637         case LINE_PARENT:
6638                 if (commit->has_parents)
6639                         break;
6640                 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
6641                 break;
6643         case LINE_AUTHOR:
6644                 parse_author_line(line + STRING_SIZE("author "),
6645                                   &commit->author, &commit->time);
6646                 update_rev_graph(view, graph);
6647                 graph = graph->next;
6648                 break;
6650         default:
6651                 /* Fill in the commit title if it has not already been set. */
6652                 if (commit->title[0])
6653                         break;
6655                 /* Require titles to start with a non-space character at the
6656                  * offset used by git log. */
6657                 if (strncmp(line, "    ", 4))
6658                         break;
6659                 line += 4;
6660                 /* Well, if the title starts with a whitespace character,
6661                  * try to be forgiving.  Otherwise we end up with no title. */
6662                 while (isspace(*line))
6663                         line++;
6664                 if (*line == '\0')
6665                         break;
6666                 /* FIXME: More graceful handling of titles; append "..." to
6667                  * shortened titles, etc. */
6669                 string_expand(commit->title, sizeof(commit->title), line, 1);
6670                 view->line[view->lines - 1].dirty = 1;
6671         }
6673         return TRUE;
6676 static enum request
6677 main_request(struct view *view, enum request request, struct line *line)
6679         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
6681         switch (request) {
6682         case REQ_ENTER:
6683                 open_view(view, REQ_VIEW_DIFF, flags);
6684                 break;
6685         case REQ_REFRESH:
6686                 load_refs();
6687                 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
6688                 break;
6689         default:
6690                 return request;
6691         }
6693         return REQ_NONE;
6696 static bool
6697 grep_refs(struct ref_list *list, regex_t *regex)
6699         regmatch_t pmatch;
6700         size_t i;
6702         if (!opt_show_refs || !list)
6703                 return FALSE;
6705         for (i = 0; i < list->size; i++) {
6706                 if (regexec(regex, list->refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
6707                         return TRUE;
6708         }
6710         return FALSE;
6713 static bool
6714 main_grep(struct view *view, struct line *line)
6716         struct commit *commit = line->data;
6717         const char *text[] = {
6718                 commit->title,
6719                 opt_author ? commit->author : "",
6720                 mkdate(&commit->time, opt_date),
6721                 NULL
6722         };
6724         return grep_text(view, text) || grep_refs(commit->refs, view->regex);
6727 static void
6728 main_select(struct view *view, struct line *line)
6730         struct commit *commit = line->data;
6732         string_copy_rev(view->ref, commit->id);
6733         string_copy_rev(ref_commit, view->ref);
6736 static struct view_ops main_ops = {
6737         "commit",
6738         main_argv,
6739         NULL,
6740         main_read,
6741         main_draw,
6742         main_request,
6743         main_grep,
6744         main_select,
6745 };
6748 /*
6749  * Unicode / UTF-8 handling
6750  *
6751  * NOTE: Much of the following code for dealing with Unicode is derived from
6752  * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
6753  * src/intl/charset.c from the UTF-8 branch commit elinks-0.11.0-g31f2c28.
6754  */
6756 static inline int
6757 unicode_width(unsigned long c, int tab_size)
6759         if (c >= 0x1100 &&
6760            (c <= 0x115f                         /* Hangul Jamo */
6761             || c == 0x2329
6762             || c == 0x232a
6763             || (c >= 0x2e80  && c <= 0xa4cf && c != 0x303f)
6764                                                 /* CJK ... Yi */
6765             || (c >= 0xac00  && c <= 0xd7a3)    /* Hangul Syllables */
6766             || (c >= 0xf900  && c <= 0xfaff)    /* CJK Compatibility Ideographs */
6767             || (c >= 0xfe30  && c <= 0xfe6f)    /* CJK Compatibility Forms */
6768             || (c >= 0xff00  && c <= 0xff60)    /* Fullwidth Forms */
6769             || (c >= 0xffe0  && c <= 0xffe6)
6770             || (c >= 0x20000 && c <= 0x2fffd)
6771             || (c >= 0x30000 && c <= 0x3fffd)))
6772                 return 2;
6774         if (c == '\t')
6775                 return tab_size;
6777         return 1;
6780 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
6781  * Illegal bytes are set one. */
6782 static const unsigned char utf8_bytes[256] = {
6783         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,
6784         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,
6785         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,
6786         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,
6787         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,
6788         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,
6789         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,
6790         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,
6791 };
6793 static inline unsigned char
6794 utf8_char_length(const char *string, const char *end)
6796         int c = *(unsigned char *) string;
6798         return utf8_bytes[c];
6801 /* Decode UTF-8 multi-byte representation into a Unicode character. */
6802 static inline unsigned long
6803 utf8_to_unicode(const char *string, size_t length)
6805         unsigned long unicode;
6807         switch (length) {
6808         case 1:
6809                 unicode  =   string[0];
6810                 break;
6811         case 2:
6812                 unicode  =  (string[0] & 0x1f) << 6;
6813                 unicode +=  (string[1] & 0x3f);
6814                 break;
6815         case 3:
6816                 unicode  =  (string[0] & 0x0f) << 12;
6817                 unicode += ((string[1] & 0x3f) << 6);
6818                 unicode +=  (string[2] & 0x3f);
6819                 break;
6820         case 4:
6821                 unicode  =  (string[0] & 0x0f) << 18;
6822                 unicode += ((string[1] & 0x3f) << 12);
6823                 unicode += ((string[2] & 0x3f) << 6);
6824                 unicode +=  (string[3] & 0x3f);
6825                 break;
6826         case 5:
6827                 unicode  =  (string[0] & 0x0f) << 24;
6828                 unicode += ((string[1] & 0x3f) << 18);
6829                 unicode += ((string[2] & 0x3f) << 12);
6830                 unicode += ((string[3] & 0x3f) << 6);
6831                 unicode +=  (string[4] & 0x3f);
6832                 break;
6833         case 6:
6834                 unicode  =  (string[0] & 0x01) << 30;
6835                 unicode += ((string[1] & 0x3f) << 24);
6836                 unicode += ((string[2] & 0x3f) << 18);
6837                 unicode += ((string[3] & 0x3f) << 12);
6838                 unicode += ((string[4] & 0x3f) << 6);
6839                 unicode +=  (string[5] & 0x3f);
6840                 break;
6841         default:
6842                 die("Invalid Unicode length");
6843         }
6845         /* Invalid characters could return the special 0xfffd value but NUL
6846          * should be just as good. */
6847         return unicode > 0xffff ? 0 : unicode;
6850 /* Calculates how much of string can be shown within the given maximum width
6851  * and sets trimmed parameter to non-zero value if all of string could not be
6852  * shown. If the reserve flag is TRUE, it will reserve at least one
6853  * trailing character, which can be useful when drawing a delimiter.
6854  *
6855  * Returns the number of bytes to output from string to satisfy max_width. */
6856 static size_t
6857 utf8_length(const char **start, size_t skip, int *width, size_t max_width, int *trimmed, bool reserve, int tab_size)
6859         const char *string = *start;
6860         const char *end = strchr(string, '\0');
6861         unsigned char last_bytes = 0;
6862         size_t last_ucwidth = 0;
6864         *width = 0;
6865         *trimmed = 0;
6867         while (string < end) {
6868                 unsigned char bytes = utf8_char_length(string, end);
6869                 size_t ucwidth;
6870                 unsigned long unicode;
6872                 if (string + bytes > end)
6873                         break;
6875                 /* Change representation to figure out whether
6876                  * it is a single- or double-width character. */
6878                 unicode = utf8_to_unicode(string, bytes);
6879                 /* FIXME: Graceful handling of invalid Unicode character. */
6880                 if (!unicode)
6881                         break;
6883                 ucwidth = unicode_width(unicode, tab_size);
6884                 if (skip > 0) {
6885                         skip -= ucwidth <= skip ? ucwidth : skip;
6886                         *start += bytes;
6887                 }
6888                 *width  += ucwidth;
6889                 if (*width > max_width) {
6890                         *trimmed = 1;
6891                         *width -= ucwidth;
6892                         if (reserve && *width == max_width) {
6893                                 string -= last_bytes;
6894                                 *width -= last_ucwidth;
6895                         }
6896                         break;
6897                 }
6899                 string  += bytes;
6900                 last_bytes = ucwidth ? bytes : 0;
6901                 last_ucwidth = ucwidth;
6902         }
6904         return string - *start;
6908 /*
6909  * Status management
6910  */
6912 /* Whether or not the curses interface has been initialized. */
6913 static bool cursed = FALSE;
6915 /* Terminal hacks and workarounds. */
6916 static bool use_scroll_redrawwin;
6917 static bool use_scroll_status_wclear;
6919 /* The status window is used for polling keystrokes. */
6920 static WINDOW *status_win;
6922 /* Reading from the prompt? */
6923 static bool input_mode = FALSE;
6925 static bool status_empty = FALSE;
6927 /* Update status and title window. */
6928 static void
6929 report(const char *msg, ...)
6931         struct view *view = display[current_view];
6933         if (input_mode)
6934                 return;
6936         if (!view) {
6937                 char buf[SIZEOF_STR];
6938                 va_list args;
6940                 va_start(args, msg);
6941                 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
6942                         buf[sizeof(buf) - 1] = 0;
6943                         buf[sizeof(buf) - 2] = '.';
6944                         buf[sizeof(buf) - 3] = '.';
6945                         buf[sizeof(buf) - 4] = '.';
6946                 }
6947                 va_end(args);
6948                 die("%s", buf);
6949         }
6951         if (!status_empty || *msg) {
6952                 va_list args;
6954                 va_start(args, msg);
6956                 wmove(status_win, 0, 0);
6957                 if (view->has_scrolled && use_scroll_status_wclear)
6958                         wclear(status_win);
6959                 if (*msg) {
6960                         vwprintw(status_win, msg, args);
6961                         status_empty = FALSE;
6962                 } else {
6963                         status_empty = TRUE;
6964                 }
6965                 wclrtoeol(status_win);
6966                 wnoutrefresh(status_win);
6968                 va_end(args);
6969         }
6971         update_view_title(view);
6974 static void
6975 init_display(void)
6977         const char *term;
6978         int x, y;
6980         /* Initialize the curses library */
6981         if (isatty(STDIN_FILENO)) {
6982                 cursed = !!initscr();
6983                 opt_tty = stdin;
6984         } else {
6985                 /* Leave stdin and stdout alone when acting as a pager. */
6986                 opt_tty = fopen("/dev/tty", "r+");
6987                 if (!opt_tty)
6988                         die("Failed to open /dev/tty");
6989                 cursed = !!newterm(NULL, opt_tty, opt_tty);
6990         }
6992         if (!cursed)
6993                 die("Failed to initialize curses");
6995         nonl();         /* Disable conversion and detect newlines from input. */
6996         cbreak();       /* Take input chars one at a time, no wait for \n */
6997         noecho();       /* Don't echo input */
6998         leaveok(stdscr, FALSE);
7000         if (has_colors())
7001                 init_colors();
7003         getmaxyx(stdscr, y, x);
7004         status_win = newwin(1, 0, y - 1, 0);
7005         if (!status_win)
7006                 die("Failed to create status window");
7008         /* Enable keyboard mapping */
7009         keypad(status_win, TRUE);
7010         wbkgdset(status_win, get_line_attr(LINE_STATUS));
7012         TABSIZE = opt_tab_size;
7014         term = getenv("XTERM_VERSION") ? NULL : getenv("COLORTERM");
7015         if (term && !strcmp(term, "gnome-terminal")) {
7016                 /* In the gnome-terminal-emulator, the message from
7017                  * scrolling up one line when impossible followed by
7018                  * scrolling down one line causes corruption of the
7019                  * status line. This is fixed by calling wclear. */
7020                 use_scroll_status_wclear = TRUE;
7021                 use_scroll_redrawwin = FALSE;
7023         } else if (term && !strcmp(term, "xrvt-xpm")) {
7024                 /* No problems with full optimizations in xrvt-(unicode)
7025                  * and aterm. */
7026                 use_scroll_status_wclear = use_scroll_redrawwin = FALSE;
7028         } else {
7029                 /* When scrolling in (u)xterm the last line in the
7030                  * scrolling direction will update slowly. */
7031                 use_scroll_redrawwin = TRUE;
7032                 use_scroll_status_wclear = FALSE;
7033         }
7036 static int
7037 get_input(int prompt_position)
7039         struct view *view;
7040         int i, key, cursor_y, cursor_x;
7041         bool loading = FALSE;
7043         if (prompt_position)
7044                 input_mode = TRUE;
7046         while (TRUE) {
7047                 foreach_view (view, i) {
7048                         update_view(view);
7049                         if (view_is_displayed(view) && view->has_scrolled &&
7050                             use_scroll_redrawwin)
7051                                 redrawwin(view->win);
7052                         view->has_scrolled = FALSE;
7053                         if (view->pipe)
7054                                 loading = TRUE;
7055                 }
7057                 /* Update the cursor position. */
7058                 if (prompt_position) {
7059                         getbegyx(status_win, cursor_y, cursor_x);
7060                         cursor_x = prompt_position;
7061                 } else {
7062                         view = display[current_view];
7063                         getbegyx(view->win, cursor_y, cursor_x);
7064                         cursor_x = view->width - 1;
7065                         cursor_y += view->lineno - view->offset;
7066                 }
7067                 setsyx(cursor_y, cursor_x);
7069                 /* Refresh, accept single keystroke of input */
7070                 doupdate();
7071                 nodelay(status_win, loading);
7072                 key = wgetch(status_win);
7074                 /* wgetch() with nodelay() enabled returns ERR when
7075                  * there's no input. */
7076                 if (key == ERR) {
7078                 } else if (key == KEY_RESIZE) {
7079                         int height, width;
7081                         getmaxyx(stdscr, height, width);
7083                         wresize(status_win, 1, width);
7084                         mvwin(status_win, height - 1, 0);
7085                         wnoutrefresh(status_win);
7086                         resize_display();
7087                         redraw_display(TRUE);
7089                 } else {
7090                         input_mode = FALSE;
7091                         return key;
7092                 }
7093         }
7096 static char *
7097 prompt_input(const char *prompt, input_handler handler, void *data)
7099         enum input_status status = INPUT_OK;
7100         static char buf[SIZEOF_STR];
7101         size_t pos = 0;
7103         buf[pos] = 0;
7105         while (status == INPUT_OK || status == INPUT_SKIP) {
7106                 int key;
7108                 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
7109                 wclrtoeol(status_win);
7111                 key = get_input(pos + 1);
7112                 switch (key) {
7113                 case KEY_RETURN:
7114                 case KEY_ENTER:
7115                 case '\n':
7116                         status = pos ? INPUT_STOP : INPUT_CANCEL;
7117                         break;
7119                 case KEY_BACKSPACE:
7120                         if (pos > 0)
7121                                 buf[--pos] = 0;
7122                         else
7123                                 status = INPUT_CANCEL;
7124                         break;
7126                 case KEY_ESC:
7127                         status = INPUT_CANCEL;
7128                         break;
7130                 default:
7131                         if (pos >= sizeof(buf)) {
7132                                 report("Input string too long");
7133                                 return NULL;
7134                         }
7136                         status = handler(data, buf, key);
7137                         if (status == INPUT_OK)
7138                                 buf[pos++] = (char) key;
7139                 }
7140         }
7142         /* Clear the status window */
7143         status_empty = FALSE;
7144         report("");
7146         if (status == INPUT_CANCEL)
7147                 return NULL;
7149         buf[pos++] = 0;
7151         return buf;
7154 static enum input_status
7155 prompt_yesno_handler(void *data, char *buf, int c)
7157         if (c == 'y' || c == 'Y')
7158                 return INPUT_STOP;
7159         if (c == 'n' || c == 'N')
7160                 return INPUT_CANCEL;
7161         return INPUT_SKIP;
7164 static bool
7165 prompt_yesno(const char *prompt)
7167         char prompt2[SIZEOF_STR];
7169         if (!string_format(prompt2, "%s [Yy/Nn]", prompt))
7170                 return FALSE;
7172         return !!prompt_input(prompt2, prompt_yesno_handler, NULL);
7175 static enum input_status
7176 read_prompt_handler(void *data, char *buf, int c)
7178         return isprint(c) ? INPUT_OK : INPUT_SKIP;
7181 static char *
7182 read_prompt(const char *prompt)
7184         return prompt_input(prompt, read_prompt_handler, NULL);
7187 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected)
7189         enum input_status status = INPUT_OK;
7190         int size = 0;
7192         while (items[size].text)
7193                 size++;
7195         while (status == INPUT_OK) {
7196                 const struct menu_item *item = &items[*selected];
7197                 int key;
7198                 int i;
7200                 mvwprintw(status_win, 0, 0, "%s (%d of %d) ",
7201                           prompt, *selected + 1, size);
7202                 if (item->hotkey)
7203                         wprintw(status_win, "[%c] ", (char) item->hotkey);
7204                 wprintw(status_win, "%s", item->text);
7205                 wclrtoeol(status_win);
7207                 key = get_input(COLS - 1);
7208                 switch (key) {
7209                 case KEY_RETURN:
7210                 case KEY_ENTER:
7211                 case '\n':
7212                         status = INPUT_STOP;
7213                         break;
7215                 case KEY_LEFT:
7216                 case KEY_UP:
7217                         *selected = *selected - 1;
7218                         if (*selected < 0)
7219                                 *selected = size - 1;
7220                         break;
7222                 case KEY_RIGHT:
7223                 case KEY_DOWN:
7224                         *selected = (*selected + 1) % size;
7225                         break;
7227                 case KEY_ESC:
7228                         status = INPUT_CANCEL;
7229                         break;
7231                 default:
7232                         for (i = 0; items[i].text; i++)
7233                                 if (items[i].hotkey == key) {
7234                                         *selected = i;
7235                                         status = INPUT_STOP;
7236                                         break;
7237                                 }
7238                 }
7239         }
7241         /* Clear the status window */
7242         status_empty = FALSE;
7243         report("");
7245         return status != INPUT_CANCEL;
7248 /*
7249  * Repository properties
7250  */
7252 static struct ref **refs = NULL;
7253 static size_t refs_size = 0;
7254 static struct ref *refs_head = NULL;
7256 static struct ref_list **ref_lists = NULL;
7257 static size_t ref_lists_size = 0;
7259 DEFINE_ALLOCATOR(realloc_refs, struct ref *, 256)
7260 DEFINE_ALLOCATOR(realloc_refs_list, struct ref *, 8)
7261 DEFINE_ALLOCATOR(realloc_ref_lists, struct ref_list *, 8)
7263 static int
7264 compare_refs(const void *ref1_, const void *ref2_)
7266         const struct ref *ref1 = *(const struct ref **)ref1_;
7267         const struct ref *ref2 = *(const struct ref **)ref2_;
7269         if (ref1->tag != ref2->tag)
7270                 return ref2->tag - ref1->tag;
7271         if (ref1->ltag != ref2->ltag)
7272                 return ref2->ltag - ref2->ltag;
7273         if (ref1->head != ref2->head)
7274                 return ref2->head - ref1->head;
7275         if (ref1->tracked != ref2->tracked)
7276                 return ref2->tracked - ref1->tracked;
7277         if (ref1->remote != ref2->remote)
7278                 return ref2->remote - ref1->remote;
7279         return strcmp(ref1->name, ref2->name);
7282 static void
7283 foreach_ref(bool (*visitor)(void *data, const struct ref *ref), void *data)
7285         size_t i;
7287         for (i = 0; i < refs_size; i++)
7288                 if (!visitor(data, refs[i]))
7289                         break;
7292 static struct ref *
7293 get_ref_head()
7295         return refs_head;
7298 static struct ref_list *
7299 get_ref_list(const char *id)
7301         struct ref_list *list;
7302         size_t i;
7304         for (i = 0; i < ref_lists_size; i++)
7305                 if (!strcmp(id, ref_lists[i]->id))
7306                         return ref_lists[i];
7308         if (!realloc_ref_lists(&ref_lists, ref_lists_size, 1))
7309                 return NULL;
7310         list = calloc(1, sizeof(*list));
7311         if (!list)
7312                 return NULL;
7314         for (i = 0; i < refs_size; i++) {
7315                 if (!strcmp(id, refs[i]->id) &&
7316                     realloc_refs_list(&list->refs, list->size, 1))
7317                         list->refs[list->size++] = refs[i];
7318         }
7320         if (!list->refs) {
7321                 free(list);
7322                 return NULL;
7323         }
7325         qsort(list->refs, list->size, sizeof(*list->refs), compare_refs);
7326         ref_lists[ref_lists_size++] = list;
7327         return list;
7330 static int
7331 read_ref(char *id, size_t idlen, char *name, size_t namelen)
7333         struct ref *ref = NULL;
7334         bool tag = FALSE;
7335         bool ltag = FALSE;
7336         bool remote = FALSE;
7337         bool tracked = FALSE;
7338         bool head = FALSE;
7339         int from = 0, to = refs_size - 1;
7341         if (!prefixcmp(name, "refs/tags/")) {
7342                 if (!suffixcmp(name, namelen, "^{}")) {
7343                         namelen -= 3;
7344                         name[namelen] = 0;
7345                 } else {
7346                         ltag = TRUE;
7347                 }
7349                 tag = TRUE;
7350                 namelen -= STRING_SIZE("refs/tags/");
7351                 name    += STRING_SIZE("refs/tags/");
7353         } else if (!prefixcmp(name, "refs/remotes/")) {
7354                 remote = TRUE;
7355                 namelen -= STRING_SIZE("refs/remotes/");
7356                 name    += STRING_SIZE("refs/remotes/");
7357                 tracked  = !strcmp(opt_remote, name);
7359         } else if (!prefixcmp(name, "refs/heads/")) {
7360                 namelen -= STRING_SIZE("refs/heads/");
7361                 name    += STRING_SIZE("refs/heads/");
7362                 if (!strncmp(opt_head, name, namelen))
7363                         return OK;
7365         } else if (!strcmp(name, "HEAD")) {
7366                 head     = TRUE;
7367                 if (*opt_head) {
7368                         namelen  = strlen(opt_head);
7369                         name     = opt_head;
7370                 }
7371         }
7373         /* If we are reloading or it's an annotated tag, replace the
7374          * previous SHA1 with the resolved commit id; relies on the fact
7375          * git-ls-remote lists the commit id of an annotated tag right
7376          * before the commit id it points to. */
7377         while (from <= to) {
7378                 size_t pos = (to + from) / 2;
7379                 int cmp = strcmp(name, refs[pos]->name);
7381                 if (!cmp) {
7382                         ref = refs[pos];
7383                         break;
7384                 }
7386                 if (cmp < 0)
7387                         to = pos - 1;
7388                 else
7389                         from = pos + 1;
7390         }
7392         if (!ref) {
7393                 if (!realloc_refs(&refs, refs_size, 1))
7394                         return ERR;
7395                 ref = calloc(1, sizeof(*ref) + namelen);
7396                 if (!ref)
7397                         return ERR;
7398                 memmove(refs + from + 1, refs + from,
7399                         (refs_size - from) * sizeof(*refs));
7400                 refs[from] = ref;
7401                 strncpy(ref->name, name, namelen);
7402                 refs_size++;
7403         }
7405         ref->head = head;
7406         ref->tag = tag;
7407         ref->ltag = ltag;
7408         ref->remote = remote;
7409         ref->tracked = tracked;
7410         string_copy_rev(ref->id, id);
7412         if (head)
7413                 refs_head = ref;
7414         return OK;
7417 static int
7418 load_refs(void)
7420         const char *head_argv[] = {
7421                 "git", "symbolic-ref", "HEAD", NULL
7422         };
7423         static const char *ls_remote_argv[SIZEOF_ARG] = {
7424                 "git", "ls-remote", opt_git_dir, NULL
7425         };
7426         static bool init = FALSE;
7427         size_t i;
7429         if (!init) {
7430                 argv_from_env(ls_remote_argv, "TIG_LS_REMOTE");
7431                 init = TRUE;
7432         }
7434         if (!*opt_git_dir)
7435                 return OK;
7437         if (run_io_buf(head_argv, opt_head, sizeof(opt_head)) &&
7438             !prefixcmp(opt_head, "refs/heads/")) {
7439                 char *offset = opt_head + STRING_SIZE("refs/heads/");
7441                 memmove(opt_head, offset, strlen(offset) + 1);
7442         }
7444         refs_head = NULL;
7445         for (i = 0; i < refs_size; i++)
7446                 refs[i]->id[0] = 0;
7448         if (run_io_load(ls_remote_argv, "\t", read_ref) == ERR)
7449                 return ERR;
7451         /* Update the ref lists to reflect changes. */
7452         for (i = 0; i < ref_lists_size; i++) {
7453                 struct ref_list *list = ref_lists[i];
7454                 size_t old, new;
7456                 for (old = new = 0; old < list->size; old++)
7457                         if (!strcmp(list->id, list->refs[old]->id))
7458                                 list->refs[new++] = list->refs[old];
7459                 list->size = new;
7460         }
7462         return OK;
7465 static void
7466 set_remote_branch(const char *name, const char *value, size_t valuelen)
7468         if (!strcmp(name, ".remote")) {
7469                 string_ncopy(opt_remote, value, valuelen);
7471         } else if (*opt_remote && !strcmp(name, ".merge")) {
7472                 size_t from = strlen(opt_remote);
7474                 if (!prefixcmp(value, "refs/heads/"))
7475                         value += STRING_SIZE("refs/heads/");
7477                 if (!string_format_from(opt_remote, &from, "/%s", value))
7478                         opt_remote[0] = 0;
7479         }
7482 static void
7483 set_repo_config_option(char *name, char *value, int (*cmd)(int, const char **))
7485         const char *argv[SIZEOF_ARG] = { name, "=" };
7486         int argc = 1 + (cmd == option_set_command);
7487         int error = ERR;
7489         if (!argv_from_string(argv, &argc, value))
7490                 config_msg = "Too many option arguments";
7491         else
7492                 error = cmd(argc, argv);
7494         if (error == ERR)
7495                 warn("Option 'tig.%s': %s", name, config_msg);
7498 static bool
7499 set_environment_variable(const char *name, const char *value)
7501         size_t len = strlen(name) + 1 + strlen(value) + 1;
7502         char *env = malloc(len);
7504         if (env &&
7505             string_nformat(env, len, NULL, "%s=%s", name, value) &&
7506             putenv(env) == 0)
7507                 return TRUE;
7508         free(env);
7509         return FALSE;
7512 static void
7513 set_work_tree(const char *value)
7515         char cwd[SIZEOF_STR];
7517         if (!getcwd(cwd, sizeof(cwd)))
7518                 die("Failed to get cwd path: %s", strerror(errno));
7519         if (chdir(opt_git_dir) < 0)
7520                 die("Failed to chdir(%s): %s", strerror(errno));
7521         if (!getcwd(opt_git_dir, sizeof(opt_git_dir)))
7522                 die("Failed to get git path: %s", strerror(errno));
7523         if (chdir(cwd) < 0)
7524                 die("Failed to chdir(%s): %s", cwd, strerror(errno));
7525         if (chdir(value) < 0)
7526                 die("Failed to chdir(%s): %s", value, strerror(errno));
7527         if (!getcwd(cwd, sizeof(cwd)))
7528                 die("Failed to get cwd path: %s", strerror(errno));
7529         if (!set_environment_variable("GIT_WORK_TREE", cwd))
7530                 die("Failed to set GIT_WORK_TREE to '%s'", cwd);
7531         if (!set_environment_variable("GIT_DIR", opt_git_dir))
7532                 die("Failed to set GIT_DIR to '%s'", opt_git_dir);
7533         opt_is_inside_work_tree = TRUE;
7536 static int
7537 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
7539         if (!strcmp(name, "i18n.commitencoding"))
7540                 string_ncopy(opt_encoding, value, valuelen);
7542         else if (!strcmp(name, "core.editor"))
7543                 string_ncopy(opt_editor, value, valuelen);
7545         else if (!strcmp(name, "core.worktree"))
7546                 set_work_tree(value);
7548         else if (!prefixcmp(name, "tig.color."))
7549                 set_repo_config_option(name + 10, value, option_color_command);
7551         else if (!prefixcmp(name, "tig.bind."))
7552                 set_repo_config_option(name + 9, value, option_bind_command);
7554         else if (!prefixcmp(name, "tig."))
7555                 set_repo_config_option(name + 4, value, option_set_command);
7557         else if (*opt_head && !prefixcmp(name, "branch.") &&
7558                  !strncmp(name + 7, opt_head, strlen(opt_head)))
7559                 set_remote_branch(name + 7 + strlen(opt_head), value, valuelen);
7561         return OK;
7564 static int
7565 load_git_config(void)
7567         const char *config_list_argv[] = { "git", "config", "--list", NULL };
7569         return run_io_load(config_list_argv, "=", read_repo_config_option);
7572 static int
7573 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
7575         if (!opt_git_dir[0]) {
7576                 string_ncopy(opt_git_dir, name, namelen);
7578         } else if (opt_is_inside_work_tree == -1) {
7579                 /* This can be 3 different values depending on the
7580                  * version of git being used. If git-rev-parse does not
7581                  * understand --is-inside-work-tree it will simply echo
7582                  * the option else either "true" or "false" is printed.
7583                  * Default to true for the unknown case. */
7584                 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
7586         } else if (*name == '.') {
7587                 string_ncopy(opt_cdup, name, namelen);
7589         } else {
7590                 string_ncopy(opt_prefix, name, namelen);
7591         }
7593         return OK;
7596 static int
7597 load_repo_info(void)
7599         const char *rev_parse_argv[] = {
7600                 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
7601                         "--show-cdup", "--show-prefix", NULL
7602         };
7604         return run_io_load(rev_parse_argv, "=", read_repo_info);
7608 /*
7609  * Main
7610  */
7612 static const char usage[] =
7613 "tig " TIG_VERSION " (" __DATE__ ")\n"
7614 "\n"
7615 "Usage: tig        [options] [revs] [--] [paths]\n"
7616 "   or: tig show   [options] [revs] [--] [paths]\n"
7617 "   or: tig blame  [rev] path\n"
7618 "   or: tig status\n"
7619 "   or: tig <      [git command output]\n"
7620 "\n"
7621 "Options:\n"
7622 "  -v, --version   Show version and exit\n"
7623 "  -h, --help      Show help message and exit";
7625 static void __NORETURN
7626 quit(int sig)
7628         /* XXX: Restore tty modes and let the OS cleanup the rest! */
7629         if (cursed)
7630                 endwin();
7631         exit(0);
7634 static void __NORETURN
7635 die(const char *err, ...)
7637         va_list args;
7639         endwin();
7641         va_start(args, err);
7642         fputs("tig: ", stderr);
7643         vfprintf(stderr, err, args);
7644         fputs("\n", stderr);
7645         va_end(args);
7647         exit(1);
7650 static void
7651 warn(const char *msg, ...)
7653         va_list args;
7655         va_start(args, msg);
7656         fputs("tig warning: ", stderr);
7657         vfprintf(stderr, msg, args);
7658         fputs("\n", stderr);
7659         va_end(args);
7662 static enum request
7663 parse_options(int argc, const char *argv[])
7665         enum request request = REQ_VIEW_MAIN;
7666         const char *subcommand;
7667         bool seen_dashdash = FALSE;
7668         /* XXX: This is vulnerable to the user overriding options
7669          * required for the main view parser. */
7670         const char *custom_argv[SIZEOF_ARG] = {
7671                 "git", "log", "--no-color", "--pretty=raw", "--parents",
7672                         "--topo-order", NULL
7673         };
7674         int i, j = 6;
7676         if (!isatty(STDIN_FILENO)) {
7677                 io_open(&VIEW(REQ_VIEW_PAGER)->io, "");
7678                 return REQ_VIEW_PAGER;
7679         }
7681         if (argc <= 1)
7682                 return REQ_NONE;
7684         subcommand = argv[1];
7685         if (!strcmp(subcommand, "status")) {
7686                 if (argc > 2)
7687                         warn("ignoring arguments after `%s'", subcommand);
7688                 return REQ_VIEW_STATUS;
7690         } else if (!strcmp(subcommand, "blame")) {
7691                 if (argc <= 2 || argc > 4)
7692                         die("invalid number of options to blame\n\n%s", usage);
7694                 i = 2;
7695                 if (argc == 4) {
7696                         string_ncopy(opt_ref, argv[i], strlen(argv[i]));
7697                         i++;
7698                 }
7700                 string_ncopy(opt_file, argv[i], strlen(argv[i]));
7701                 return REQ_VIEW_BLAME;
7703         } else if (!strcmp(subcommand, "show")) {
7704                 request = REQ_VIEW_DIFF;
7706         } else {
7707                 subcommand = NULL;
7708         }
7710         if (subcommand) {
7711                 custom_argv[1] = subcommand;
7712                 j = 2;
7713         }
7715         for (i = 1 + !!subcommand; i < argc; i++) {
7716                 const char *opt = argv[i];
7718                 if (seen_dashdash || !strcmp(opt, "--")) {
7719                         seen_dashdash = TRUE;
7721                 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
7722                         printf("tig version %s\n", TIG_VERSION);
7723                         quit(0);
7725                 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
7726                         printf("%s\n", usage);
7727                         quit(0);
7728                 }
7730                 custom_argv[j++] = opt;
7731                 if (j >= ARRAY_SIZE(custom_argv))
7732                         die("command too long");
7733         }
7735         if (!prepare_update(VIEW(request), custom_argv, NULL, FORMAT_NONE))
7736                 die("Failed to format arguments");
7738         return request;
7741 int
7742 main(int argc, const char *argv[])
7744         const char *codeset = "UTF-8";
7745         enum request request = parse_options(argc, argv);
7746         struct view *view;
7747         size_t i;
7749         signal(SIGINT, quit);
7750         signal(SIGPIPE, SIG_IGN);
7752         if (setlocale(LC_ALL, "")) {
7753                 codeset = nl_langinfo(CODESET);
7754         }
7756         if (load_repo_info() == ERR)
7757                 die("Failed to load repo info.");
7759         if (load_options() == ERR)
7760                 die("Failed to load user config.");
7762         if (load_git_config() == ERR)
7763                 die("Failed to load repo config.");
7765         /* Require a git repository unless when running in pager mode. */
7766         if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
7767                 die("Not a git repository");
7769         if (*opt_encoding && strcmp(codeset, "UTF-8")) {
7770                 opt_iconv_in = iconv_open("UTF-8", opt_encoding);
7771                 if (opt_iconv_in == ICONV_NONE)
7772                         die("Failed to initialize character set conversion");
7773         }
7775         if (codeset && strcmp(codeset, "UTF-8")) {
7776                 opt_iconv_out = iconv_open(codeset, "UTF-8");
7777                 if (opt_iconv_out == ICONV_NONE)
7778                         die("Failed to initialize character set conversion");
7779         }
7781         if (load_refs() == ERR)
7782                 die("Failed to load refs.");
7784         foreach_view (view, i)
7785                 argv_from_env(view->ops->argv, view->cmd_env);
7787         init_display();
7789         if (request != REQ_NONE)
7790                 open_view(NULL, request, OPEN_PREPARED);
7791         request = request == REQ_NONE ? REQ_VIEW_MAIN : REQ_NONE;
7793         while (view_driver(display[current_view], request)) {
7794                 int key = get_input(0);
7796                 view = display[current_view];
7797                 request = get_keybinding(view->keymap, key);
7799                 /* Some low-level request handling. This keeps access to
7800                  * status_win restricted. */
7801                 switch (request) {
7802                 case REQ_PROMPT:
7803                 {
7804                         char *cmd = read_prompt(":");
7806                         if (cmd && isdigit(*cmd)) {
7807                                 int lineno = view->lineno + 1;
7809                                 if (parse_int(&lineno, cmd, 1, view->lines + 1) == OK) {
7810                                         select_view_line(view, lineno - 1);
7811                                         report("");
7812                                 } else {
7813                                         report("Unable to parse '%s' as a line number", cmd);
7814                                 }
7816                         } else if (cmd) {
7817                                 struct view *next = VIEW(REQ_VIEW_PAGER);
7818                                 const char *argv[SIZEOF_ARG] = { "git" };
7819                                 int argc = 1;
7821                                 /* When running random commands, initially show the
7822                                  * command in the title. However, it maybe later be
7823                                  * overwritten if a commit line is selected. */
7824                                 string_ncopy(next->ref, cmd, strlen(cmd));
7826                                 if (!argv_from_string(argv, &argc, cmd)) {
7827                                         report("Too many arguments");
7828                                 } else if (!prepare_update(next, argv, NULL, FORMAT_DASH)) {
7829                                         report("Failed to format command");
7830                                 } else {
7831                                         open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
7832                                 }
7833                         }
7835                         request = REQ_NONE;
7836                         break;
7837                 }
7838                 case REQ_SEARCH:
7839                 case REQ_SEARCH_BACK:
7840                 {
7841                         const char *prompt = request == REQ_SEARCH ? "/" : "?";
7842                         char *search = read_prompt(prompt);
7844                         if (search)
7845                                 string_ncopy(opt_search, search, strlen(search));
7846                         else if (*opt_search)
7847                                 request = request == REQ_SEARCH ?
7848                                         REQ_FIND_NEXT :
7849                                         REQ_FIND_PREV;
7850                         else
7851                                 request = REQ_NONE;
7852                         break;
7853                 }
7854                 default:
7855                         break;
7856                 }
7857         }
7859         quit(0);
7861         return 0;