Code

Refactor format variable lookup and expansion
[tig.git] / tig.c
1 /* Copyright (c) 2006-2009 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 void set_nonblocking_input(bool loading);
72 static size_t utf8_length(const char **string, size_t col, int *width, size_t max_width, int *trimmed, bool reserve);
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
111 #define MIN_VIEW_HEIGHT 4
113 #define NULL_ID         "0000000000000000000000000000000000000000"
115 #define S_ISGITLINK(mode) (((mode) & S_IFMT) == 0160000)
117 /* Some ASCII-shorthands fitted into the ncurses namespace. */
118 #define KEY_TAB         '\t'
119 #define KEY_RETURN      '\r'
120 #define KEY_ESC         27
123 struct ref {
124         char id[SIZEOF_REV];    /* Commit SHA1 ID */
125         unsigned int head:1;    /* Is it the current HEAD? */
126         unsigned int tag:1;     /* Is it a tag? */
127         unsigned int ltag:1;    /* If so, is the tag local? */
128         unsigned int remote:1;  /* Is it a remote ref? */
129         unsigned int tracked:1; /* Is it the remote for the current HEAD? */
130         char name[1];           /* Ref name; tag or head names are shortened. */
131 };
133 struct ref_list {
134         char id[SIZEOF_REV];    /* Commit SHA1 ID */
135         size_t size;            /* Number of refs. */
136         struct ref **refs;      /* References for this ID. */
137 };
139 static struct ref_list *get_ref_list(const char *id);
140 static void foreach_ref(bool (*visitor)(void *data, const struct ref *ref), void *data);
141 static int load_refs(void);
143 enum format_flags {
144         FORMAT_ALL,             /* Perform replacement in all arguments. */
145         FORMAT_DASH,            /* Perform replacement up until "--". */
146         FORMAT_NONE             /* No replacement should be performed. */
147 };
149 static bool format_argv(const char *dst[], const char *src[], enum format_flags flags);
151 enum input_status {
152         INPUT_OK,
153         INPUT_SKIP,
154         INPUT_STOP,
155         INPUT_CANCEL
156 };
158 typedef enum input_status (*input_handler)(void *data, char *buf, int c);
160 static char *prompt_input(const char *prompt, input_handler handler, void *data);
161 static bool prompt_yesno(const char *prompt);
163 struct menu_item {
164         int hotkey;
165         const char *text;
166         void *data;
167 };
169 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected);
171 /*
172  * Allocation helpers ... Entering macro hell to never be seen again.
173  */
175 #define DEFINE_ALLOCATOR(name, type, chunk_size)                                \
176 static type *                                                                   \
177 name(type **mem, size_t size, size_t increase)                                  \
178 {                                                                               \
179         size_t num_chunks = (size + chunk_size - 1) / chunk_size;               \
180         size_t num_chunks_new = (size + increase + chunk_size - 1) / chunk_size;\
181         type *tmp = *mem;                                                       \
182                                                                                 \
183         if (mem == NULL || num_chunks != num_chunks_new) {                      \
184                 tmp = realloc(tmp, num_chunks_new * chunk_size * sizeof(type)); \
185                 if (tmp)                                                        \
186                         *mem = tmp;                                             \
187         }                                                                       \
188                                                                                 \
189         return tmp;                                                             \
192 /*
193  * String helpers
194  */
196 static inline void
197 string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t srclen)
199         if (srclen > dstlen - 1)
200                 srclen = dstlen - 1;
202         strncpy(dst, src, srclen);
203         dst[srclen] = 0;
206 /* Shorthands for safely copying into a fixed buffer. */
208 #define string_copy(dst, src) \
209         string_ncopy_do(dst, sizeof(dst), src, sizeof(src))
211 #define string_ncopy(dst, src, srclen) \
212         string_ncopy_do(dst, sizeof(dst), src, srclen)
214 #define string_copy_rev(dst, src) \
215         string_ncopy_do(dst, SIZEOF_REV, src, SIZEOF_REV - 1)
217 #define string_add(dst, from, src) \
218         string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src))
220 static void
221 string_expand(char *dst, size_t dstlen, const char *src, int tabsize)
223         size_t size, pos;
225         for (size = pos = 0; size < dstlen - 1 && src[pos]; pos++) {
226                 if (src[pos] == '\t') {
227                         size_t expanded = tabsize - (size % tabsize);
229                         if (expanded + size >= dstlen - 1)
230                                 expanded = dstlen - size - 1;
231                         memcpy(dst + size, "        ", expanded);
232                         size += expanded;
233                 } else {
234                         dst[size++] = src[pos];
235                 }
236         }
238         dst[size] = 0;
241 static char *
242 chomp_string(char *name)
244         int namelen;
246         while (isspace(*name))
247                 name++;
249         namelen = strlen(name) - 1;
250         while (namelen > 0 && isspace(name[namelen]))
251                 name[namelen--] = 0;
253         return name;
256 static bool
257 string_nformat(char *buf, size_t bufsize, size_t *bufpos, const char *fmt, ...)
259         va_list args;
260         size_t pos = bufpos ? *bufpos : 0;
262         va_start(args, fmt);
263         pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
264         va_end(args);
266         if (bufpos)
267                 *bufpos = pos;
269         return pos >= bufsize ? FALSE : TRUE;
272 #define string_format(buf, fmt, args...) \
273         string_nformat(buf, sizeof(buf), NULL, fmt, args)
275 #define string_format_from(buf, from, fmt, args...) \
276         string_nformat(buf, sizeof(buf), from, fmt, args)
278 static int
279 string_enum_compare(const char *str1, const char *str2, int len)
281         size_t i;
283 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
285         /* Diff-Header == DIFF_HEADER */
286         for (i = 0; i < len; i++) {
287                 if (toupper(str1[i]) == toupper(str2[i]))
288                         continue;
290                 if (string_enum_sep(str1[i]) &&
291                     string_enum_sep(str2[i]))
292                         continue;
294                 return str1[i] - str2[i];
295         }
297         return 0;
300 #define enum_equals(entry, str, len) \
301         ((entry).namelen == (len) && !string_enum_compare((entry).name, str, len))
303 struct enum_map {
304         const char *name;
305         int namelen;
306         int value;
307 };
309 #define ENUM_MAP(name, value) { name, STRING_SIZE(name), value }
311 static char *
312 enum_map_name(const char *name, size_t namelen)
314         static char buf[SIZEOF_STR];
315         int bufpos;
317         for (bufpos = 0; bufpos <= namelen; bufpos++) {
318                 buf[bufpos] = tolower(name[bufpos]);
319                 if (buf[bufpos] == '_')
320                         buf[bufpos] = '-';
321         }
323         buf[bufpos] = 0;
324         return buf;
327 #define enum_name(entry) enum_map_name((entry).name, (entry).namelen)
329 static bool
330 map_enum_do(const struct enum_map *map, size_t map_size, int *value, const char *name)
332         size_t namelen = strlen(name);
333         int i;
335         for (i = 0; i < map_size; i++)
336                 if (enum_equals(map[i], name, namelen)) {
337                         *value = map[i].value;
338                         return TRUE;
339                 }
341         return FALSE;
344 #define map_enum(attr, map, name) \
345         map_enum_do(map, ARRAY_SIZE(map), attr, name)
347 #define prefixcmp(str1, str2) \
348         strncmp(str1, str2, STRING_SIZE(str2))
350 static inline int
351 suffixcmp(const char *str, int slen, const char *suffix)
353         size_t len = slen >= 0 ? slen : strlen(str);
354         size_t suffixlen = strlen(suffix);
356         return suffixlen < len ? strcmp(str + len - suffixlen, suffix) : -1;
360 /*
361  * What value of "tz" was in effect back then at "time" in the
362  * local timezone?
363  */
364 static int local_tzoffset(time_t time)
366         time_t t, t_local;
367         struct tm tm;
368         int offset, eastwest; 
370         t = time;
371         localtime_r(&t, &tm);
372         t_local = mktime(&tm);
374         if (t_local < t) {
375                 eastwest = -1;
376                 offset = t - t_local;
377         } else {
378                 eastwest = 1;
379                 offset = t_local - t;
380         }
381         offset /= 60; /* in minutes */
382         offset = (offset % 60) + ((offset / 60) * 100);
383         return offset * eastwest;
386 #define DATE_INFO \
387         DATE_(NO), \
388         DATE_(DEFAULT), \
389         DATE_(RELATIVE), \
390         DATE_(SHORT)
392 enum date {
393 #define DATE_(name) DATE_##name
394         DATE_INFO
395 #undef  DATE_
396 };
398 static const struct enum_map date_map[] = {
399 #define DATE_(name) ENUM_MAP(#name, DATE_##name)
400         DATE_INFO
401 #undef  DATE_
402 };
404 static const char *
405 string_date(const time_t *time, enum date date)
407         static char buf[DATE_COLS + 1];
408         static const struct enum_map reldate[] = {
409                 { "second", 1,                  60 * 2 },
410                 { "minute", 60,                 60 * 60 * 2 },
411                 { "hour",   60 * 60,            60 * 60 * 24 * 2 },
412                 { "day",    60 * 60 * 24,       60 * 60 * 24 * 7 * 2 },
413                 { "week",   60 * 60 * 24 * 7,   60 * 60 * 24 * 7 * 5 },
414                 { "month",  60 * 60 * 24 * 30,  60 * 60 * 24 * 30 * 12 },
415         };
416         struct tm tm;
418         if (date == DATE_RELATIVE) {
419                 struct timeval now;
420                 time_t date = *time + local_tzoffset(*time);
421                 time_t seconds;
422                 int i;
424                 gettimeofday(&now, NULL);
425                 seconds = now.tv_sec < date ? date - now.tv_sec : now.tv_sec - date;
426                 for (i = 0; i < ARRAY_SIZE(reldate); i++) {
427                         if (seconds >= reldate[i].value)
428                                 continue;
430                         seconds /= reldate[i].namelen;
431                         if (!string_format(buf, "%ld %s%s %s",
432                                            seconds, reldate[i].name,
433                                            seconds > 1 ? "s" : "",
434                                            now.tv_sec >= date ? "ago" : "ahead"))
435                                 break;
436                         return buf;
437                 }
438         }
440         gmtime_r(time, &tm);
441         return strftime(buf, sizeof(buf), DATE_FORMAT, &tm) ? buf : NULL;
445 static bool
446 argv_from_string(const char *argv[SIZEOF_ARG], int *argc, char *cmd)
448         int valuelen;
450         while (*cmd && *argc < SIZEOF_ARG && (valuelen = strcspn(cmd, " \t"))) {
451                 bool advance = cmd[valuelen] != 0;
453                 cmd[valuelen] = 0;
454                 argv[(*argc)++] = chomp_string(cmd);
455                 cmd = chomp_string(cmd + valuelen + advance);
456         }
458         if (*argc < SIZEOF_ARG)
459                 argv[*argc] = NULL;
460         return *argc < SIZEOF_ARG;
463 static void
464 argv_from_env(const char **argv, const char *name)
466         char *env = argv ? getenv(name) : NULL;
467         int argc = 0;
469         if (env && *env)
470                 env = strdup(env);
471         if (env && !argv_from_string(argv, &argc, env))
472                 die("Too many arguments in the `%s` environment variable", name);
476 /*
477  * Executing external commands.
478  */
480 enum io_type {
481         IO_FD,                  /* File descriptor based IO. */
482         IO_BG,                  /* Execute command in the background. */
483         IO_FG,                  /* Execute command with same std{in,out,err}. */
484         IO_RD,                  /* Read only fork+exec IO. */
485         IO_WR,                  /* Write only fork+exec IO. */
486         IO_AP,                  /* Append fork+exec output to file. */
487 };
489 struct io {
490         enum io_type type;      /* The requested type of pipe. */
491         const char *dir;        /* Directory from which to execute. */
492         pid_t pid;              /* Pipe for reading or writing. */
493         int pipe;               /* Pipe end for reading or writing. */
494         int error;              /* Error status. */
495         const char *argv[SIZEOF_ARG];   /* Shell command arguments. */
496         char *buf;              /* Read buffer. */
497         size_t bufalloc;        /* Allocated buffer size. */
498         size_t bufsize;         /* Buffer content size. */
499         char *bufpos;           /* Current buffer position. */
500         unsigned int eof:1;     /* Has end of file been reached. */
501 };
503 static void
504 reset_io(struct io *io)
506         io->pipe = -1;
507         io->pid = 0;
508         io->buf = io->bufpos = NULL;
509         io->bufalloc = io->bufsize = 0;
510         io->error = 0;
511         io->eof = 0;
514 static void
515 init_io(struct io *io, const char *dir, enum io_type type)
517         reset_io(io);
518         io->type = type;
519         io->dir = dir;
522 static bool
523 init_io_rd(struct io *io, const char *argv[], const char *dir,
524                 enum format_flags flags)
526         init_io(io, dir, IO_RD);
527         return format_argv(io->argv, argv, flags);
530 static bool
531 io_open(struct io *io, const char *fmt, ...)
533         char name[SIZEOF_STR] = "";
534         bool fits;
535         va_list args;
537         init_io(io, NULL, IO_FD);
539         va_start(args, fmt);
540         fits = vsnprintf(name, sizeof(name), fmt, args) < sizeof(name);
541         va_end(args);
543         if (!fits) {
544                 io->error = ENAMETOOLONG;
545                 return FALSE;
546         }
547         io->pipe = *name ? open(name, O_RDONLY) : STDIN_FILENO;
548         if (io->pipe == -1)
549                 io->error = errno;
550         return io->pipe != -1;
553 static bool
554 kill_io(struct io *io)
556         return io->pid == 0 || kill(io->pid, SIGKILL) != -1;
559 static bool
560 done_io(struct io *io)
562         pid_t pid = io->pid;
564         if (io->pipe != -1)
565                 close(io->pipe);
566         free(io->buf);
567         reset_io(io);
569         while (pid > 0) {
570                 int status;
571                 pid_t waiting = waitpid(pid, &status, 0);
573                 if (waiting < 0) {
574                         if (errno == EINTR)
575                                 continue;
576                         report("waitpid failed (%s)", strerror(errno));
577                         return FALSE;
578                 }
580                 return waiting == pid &&
581                        !WIFSIGNALED(status) &&
582                        WIFEXITED(status) &&
583                        !WEXITSTATUS(status);
584         }
586         return TRUE;
589 static bool
590 start_io(struct io *io)
592         int pipefds[2] = { -1, -1 };
594         if (io->type == IO_FD)
595                 return TRUE;
597         if ((io->type == IO_RD || io->type == IO_WR) &&
598             pipe(pipefds) < 0)
599                 return FALSE;
600         else if (io->type == IO_AP)
601                 pipefds[1] = io->pipe;
603         if ((io->pid = fork())) {
604                 if (pipefds[!(io->type == IO_WR)] != -1)
605                         close(pipefds[!(io->type == IO_WR)]);
606                 if (io->pid != -1) {
607                         io->pipe = pipefds[!!(io->type == IO_WR)];
608                         return TRUE;
609                 }
611         } else {
612                 if (io->type != IO_FG) {
613                         int devnull = open("/dev/null", O_RDWR);
614                         int readfd  = io->type == IO_WR ? pipefds[0] : devnull;
615                         int writefd = (io->type == IO_RD || io->type == IO_AP)
616                                                         ? pipefds[1] : devnull;
618                         dup2(readfd,  STDIN_FILENO);
619                         dup2(writefd, STDOUT_FILENO);
620                         dup2(devnull, STDERR_FILENO);
622                         close(devnull);
623                         if (pipefds[0] != -1)
624                                 close(pipefds[0]);
625                         if (pipefds[1] != -1)
626                                 close(pipefds[1]);
627                 }
629                 if (io->dir && *io->dir && chdir(io->dir) == -1)
630                         die("Failed to change directory: %s", strerror(errno));
632                 execvp(io->argv[0], (char *const*) io->argv);
633                 die("Failed to execute program: %s", strerror(errno));
634         }
636         if (pipefds[!!(io->type == IO_WR)] != -1)
637                 close(pipefds[!!(io->type == IO_WR)]);
638         return FALSE;
641 static bool
642 run_io(struct io *io, const char **argv, const char *dir, enum io_type type)
644         init_io(io, dir, type);
645         if (!format_argv(io->argv, argv, FORMAT_NONE))
646                 return FALSE;
647         return start_io(io);
650 static int
651 run_io_do(struct io *io)
653         return start_io(io) && done_io(io);
656 static int
657 run_io_bg(const char **argv)
659         struct io io = {};
661         init_io(&io, NULL, IO_BG);
662         if (!format_argv(io.argv, argv, FORMAT_NONE))
663                 return FALSE;
664         return run_io_do(&io);
667 static bool
668 run_io_fg(const char **argv, const char *dir)
670         struct io io = {};
672         init_io(&io, dir, IO_FG);
673         if (!format_argv(io.argv, argv, FORMAT_NONE))
674                 return FALSE;
675         return run_io_do(&io);
678 static bool
679 run_io_append(const char **argv, enum format_flags flags, int fd)
681         struct io io = {};
683         init_io(&io, NULL, IO_AP);
684         io.pipe = fd;
685         if (format_argv(io.argv, argv, flags))
686                 return run_io_do(&io);
687         close(fd);
688         return FALSE;
691 static bool
692 run_io_rd(struct io *io, const char **argv, const char *dir, enum format_flags flags)
694         return init_io_rd(io, argv, dir, flags) && start_io(io);
697 static bool
698 io_eof(struct io *io)
700         return io->eof;
703 static int
704 io_error(struct io *io)
706         return io->error;
709 static char *
710 io_strerror(struct io *io)
712         return strerror(io->error);
715 static bool
716 io_can_read(struct io *io)
718         struct timeval tv = { 0, 500 };
719         fd_set fds;
721         FD_ZERO(&fds);
722         FD_SET(io->pipe, &fds);
724         return select(io->pipe + 1, &fds, NULL, NULL, &tv) > 0;
727 static ssize_t
728 io_read(struct io *io, void *buf, size_t bufsize)
730         do {
731                 ssize_t readsize = read(io->pipe, buf, bufsize);
733                 if (readsize < 0 && (errno == EAGAIN || errno == EINTR))
734                         continue;
735                 else if (readsize == -1)
736                         io->error = errno;
737                 else if (readsize == 0)
738                         io->eof = 1;
739                 return readsize;
740         } while (1);
743 DEFINE_ALLOCATOR(realloc_io_buf, char, BUFSIZ)
745 static char *
746 io_get(struct io *io, int c, bool can_read)
748         char *eol;
749         ssize_t readsize;
751         while (TRUE) {
752                 if (io->bufsize > 0) {
753                         eol = memchr(io->bufpos, c, io->bufsize);
754                         if (eol) {
755                                 char *line = io->bufpos;
757                                 *eol = 0;
758                                 io->bufpos = eol + 1;
759                                 io->bufsize -= io->bufpos - line;
760                                 return line;
761                         }
762                 }
764                 if (io_eof(io)) {
765                         if (io->bufsize) {
766                                 io->bufpos[io->bufsize] = 0;
767                                 io->bufsize = 0;
768                                 return io->bufpos;
769                         }
770                         return NULL;
771                 }
773                 if (!can_read)
774                         return NULL;
776                 if (io->bufsize > 0 && io->bufpos > io->buf)
777                         memmove(io->buf, io->bufpos, io->bufsize);
779                 if (io->bufalloc == io->bufsize) {
780                         if (!realloc_io_buf(&io->buf, io->bufalloc, BUFSIZ))
781                                 return NULL;
782                         io->bufalloc += BUFSIZ;
783                 }
785                 io->bufpos = io->buf;
786                 readsize = io_read(io, io->buf + io->bufsize, io->bufalloc - io->bufsize);
787                 if (io_error(io))
788                         return NULL;
789                 io->bufsize += readsize;
790         }
793 static bool
794 io_write(struct io *io, const void *buf, size_t bufsize)
796         size_t written = 0;
798         while (!io_error(io) && written < bufsize) {
799                 ssize_t size;
801                 size = write(io->pipe, buf + written, bufsize - written);
802                 if (size < 0 && (errno == EAGAIN || errno == EINTR))
803                         continue;
804                 else if (size == -1)
805                         io->error = errno;
806                 else
807                         written += size;
808         }
810         return written == bufsize;
813 static bool
814 io_read_buf(struct io *io, char buf[], size_t bufsize)
816         char *result = io_get(io, '\n', TRUE);
818         if (result) {
819                 result = chomp_string(result);
820                 string_ncopy_do(buf, bufsize, result, strlen(result));
821         }
823         return done_io(io) && result;
826 static bool
827 run_io_buf(const char **argv, char buf[], size_t bufsize)
829         struct io io = {};
831         return run_io_rd(&io, argv, NULL, FORMAT_NONE)
832             && io_read_buf(&io, buf, bufsize);
835 static int
836 io_load(struct io *io, const char *separators,
837         int (*read_property)(char *, size_t, char *, size_t))
839         char *name;
840         int state = OK;
842         if (!start_io(io))
843                 return ERR;
845         while (state == OK && (name = io_get(io, '\n', TRUE))) {
846                 char *value;
847                 size_t namelen;
848                 size_t valuelen;
850                 name = chomp_string(name);
851                 namelen = strcspn(name, separators);
853                 if (name[namelen]) {
854                         name[namelen] = 0;
855                         value = chomp_string(name + namelen + 1);
856                         valuelen = strlen(value);
858                 } else {
859                         value = "";
860                         valuelen = 0;
861                 }
863                 state = read_property(name, namelen, value, valuelen);
864         }
866         if (state != ERR && io_error(io))
867                 state = ERR;
868         done_io(io);
870         return state;
873 static int
874 run_io_load(const char **argv, const char *separators,
875             int (*read_property)(char *, size_t, char *, size_t))
877         struct io io = {};
879         return init_io_rd(&io, argv, NULL, FORMAT_NONE)
880                 ? io_load(&io, separators, read_property) : ERR;
884 /*
885  * User requests
886  */
888 #define REQ_INFO \
889         /* XXX: Keep the view request first and in sync with views[]. */ \
890         REQ_GROUP("View switching") \
891         REQ_(VIEW_MAIN,         "Show main view"), \
892         REQ_(VIEW_DIFF,         "Show diff view"), \
893         REQ_(VIEW_LOG,          "Show log view"), \
894         REQ_(VIEW_TREE,         "Show tree view"), \
895         REQ_(VIEW_BLOB,         "Show blob view"), \
896         REQ_(VIEW_BLAME,        "Show blame view"), \
897         REQ_(VIEW_BRANCH,       "Show branch view"), \
898         REQ_(VIEW_HELP,         "Show help page"), \
899         REQ_(VIEW_PAGER,        "Show pager view"), \
900         REQ_(VIEW_STATUS,       "Show status view"), \
901         REQ_(VIEW_STAGE,        "Show stage view"), \
902         \
903         REQ_GROUP("View manipulation") \
904         REQ_(ENTER,             "Enter current line and scroll"), \
905         REQ_(NEXT,              "Move to next"), \
906         REQ_(PREVIOUS,          "Move to previous"), \
907         REQ_(PARENT,            "Move to parent"), \
908         REQ_(VIEW_NEXT,         "Move focus to next view"), \
909         REQ_(REFRESH,           "Reload and refresh"), \
910         REQ_(MAXIMIZE,          "Maximize the current view"), \
911         REQ_(VIEW_CLOSE,        "Close the current view"), \
912         REQ_(QUIT,              "Close all views and quit"), \
913         \
914         REQ_GROUP("View specific requests") \
915         REQ_(STATUS_UPDATE,     "Update file status"), \
916         REQ_(STATUS_REVERT,     "Revert file changes"), \
917         REQ_(STATUS_MERGE,      "Merge file using external tool"), \
918         REQ_(STAGE_NEXT,        "Find next chunk to stage"), \
919         \
920         REQ_GROUP("Cursor navigation") \
921         REQ_(MOVE_UP,           "Move cursor one line up"), \
922         REQ_(MOVE_DOWN,         "Move cursor one line down"), \
923         REQ_(MOVE_PAGE_DOWN,    "Move cursor one page down"), \
924         REQ_(MOVE_PAGE_UP,      "Move cursor one page up"), \
925         REQ_(MOVE_FIRST_LINE,   "Move cursor to first line"), \
926         REQ_(MOVE_LAST_LINE,    "Move cursor to last line"), \
927         \
928         REQ_GROUP("Scrolling") \
929         REQ_(SCROLL_LEFT,       "Scroll two columns left"), \
930         REQ_(SCROLL_RIGHT,      "Scroll two columns right"), \
931         REQ_(SCROLL_LINE_UP,    "Scroll one line up"), \
932         REQ_(SCROLL_LINE_DOWN,  "Scroll one line down"), \
933         REQ_(SCROLL_PAGE_UP,    "Scroll one page up"), \
934         REQ_(SCROLL_PAGE_DOWN,  "Scroll one page down"), \
935         \
936         REQ_GROUP("Searching") \
937         REQ_(SEARCH,            "Search the view"), \
938         REQ_(SEARCH_BACK,       "Search backwards in the view"), \
939         REQ_(FIND_NEXT,         "Find next search match"), \
940         REQ_(FIND_PREV,         "Find previous search match"), \
941         \
942         REQ_GROUP("Option manipulation") \
943         REQ_(OPTIONS,           "Open option menu"), \
944         REQ_(TOGGLE_LINENO,     "Toggle line numbers"), \
945         REQ_(TOGGLE_DATE,       "Toggle date display"), \
946         REQ_(TOGGLE_DATE_SHORT, "Toggle short (date-only) dates"), \
947         REQ_(TOGGLE_AUTHOR,     "Toggle author display"), \
948         REQ_(TOGGLE_REV_GRAPH,  "Toggle revision graph visualization"), \
949         REQ_(TOGGLE_REFS,       "Toggle reference display (tags/branches)"), \
950         REQ_(TOGGLE_SORT_ORDER, "Toggle ascending/descending sort order"), \
951         REQ_(TOGGLE_SORT_FIELD, "Toggle field to sort by"), \
952         \
953         REQ_GROUP("Misc") \
954         REQ_(PROMPT,            "Bring up the prompt"), \
955         REQ_(SCREEN_REDRAW,     "Redraw the screen"), \
956         REQ_(SHOW_VERSION,      "Show version information"), \
957         REQ_(STOP_LOADING,      "Stop all loading views"), \
958         REQ_(EDIT,              "Open in editor"), \
959         REQ_(NONE,              "Do nothing")
962 /* User action requests. */
963 enum request {
964 #define REQ_GROUP(help)
965 #define REQ_(req, help) REQ_##req
967         /* Offset all requests to avoid conflicts with ncurses getch values. */
968         REQ_OFFSET = KEY_MAX + 1,
969         REQ_INFO
971 #undef  REQ_GROUP
972 #undef  REQ_
973 };
975 struct request_info {
976         enum request request;
977         const char *name;
978         int namelen;
979         const char *help;
980 };
982 static const struct request_info req_info[] = {
983 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
984 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
985         REQ_INFO
986 #undef  REQ_GROUP
987 #undef  REQ_
988 };
990 static enum request
991 get_request(const char *name)
993         int namelen = strlen(name);
994         int i;
996         for (i = 0; i < ARRAY_SIZE(req_info); i++)
997                 if (enum_equals(req_info[i], name, namelen))
998                         return req_info[i].request;
1000         return REQ_NONE;
1004 /*
1005  * Options
1006  */
1008 /* Option and state variables. */
1009 static enum date opt_date               = DATE_DEFAULT;
1010 static bool opt_author                  = TRUE;
1011 static bool opt_line_number             = FALSE;
1012 static bool opt_line_graphics           = TRUE;
1013 static bool opt_rev_graph               = FALSE;
1014 static bool opt_show_refs               = TRUE;
1015 static int opt_num_interval             = 5;
1016 static double opt_hscroll               = 0.50;
1017 static double opt_scale_split_view      = 2.0 / 3.0;
1018 static int opt_tab_size                 = 8;
1019 static int opt_author_cols              = 19;
1020 static char opt_path[SIZEOF_STR]        = "";
1021 static char opt_file[SIZEOF_STR]        = "";
1022 static char opt_ref[SIZEOF_REF]         = "";
1023 static char opt_head[SIZEOF_REF]        = "";
1024 static char opt_head_rev[SIZEOF_REV]    = "";
1025 static char opt_remote[SIZEOF_REF]      = "";
1026 static char opt_encoding[20]            = "UTF-8";
1027 static char opt_codeset[20]             = "UTF-8";
1028 static iconv_t opt_iconv_in             = ICONV_NONE;
1029 static iconv_t opt_iconv_out            = ICONV_NONE;
1030 static char opt_search[SIZEOF_STR]      = "";
1031 static char opt_cdup[SIZEOF_STR]        = "";
1032 static char opt_prefix[SIZEOF_STR]      = "";
1033 static char opt_git_dir[SIZEOF_STR]     = "";
1034 static signed char opt_is_inside_work_tree      = -1; /* set to TRUE or FALSE */
1035 static char opt_editor[SIZEOF_STR]      = "";
1036 static FILE *opt_tty                    = NULL;
1038 #define is_initial_commit()     (!*opt_head_rev)
1039 #define is_head_commit(rev)     (!strcmp((rev), "HEAD") || !strcmp(opt_head_rev, (rev)))
1040 #define mkdate(time)            string_date(time, opt_date)
1043 /*
1044  * Line-oriented content detection.
1045  */
1047 #define LINE_INFO \
1048 LINE(DIFF_HEADER,  "diff --git ",       COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1049 LINE(DIFF_CHUNK,   "@@",                COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1050 LINE(DIFF_ADD,     "+",                 COLOR_GREEN,    COLOR_DEFAULT,  0), \
1051 LINE(DIFF_DEL,     "-",                 COLOR_RED,      COLOR_DEFAULT,  0), \
1052 LINE(DIFF_INDEX,        "index ",         COLOR_BLUE,   COLOR_DEFAULT,  0), \
1053 LINE(DIFF_OLDMODE,      "old file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
1054 LINE(DIFF_NEWMODE,      "new file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
1055 LINE(DIFF_COPY_FROM,    "copy from",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
1056 LINE(DIFF_COPY_TO,      "copy to",        COLOR_YELLOW, COLOR_DEFAULT,  0), \
1057 LINE(DIFF_RENAME_FROM,  "rename from",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
1058 LINE(DIFF_RENAME_TO,    "rename to",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
1059 LINE(DIFF_SIMILARITY,   "similarity ",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
1060 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
1061 LINE(DIFF_TREE,         "diff-tree ",     COLOR_BLUE,   COLOR_DEFAULT,  0), \
1062 LINE(PP_AUTHOR,    "Author: ",          COLOR_CYAN,     COLOR_DEFAULT,  0), \
1063 LINE(PP_COMMIT,    "Commit: ",          COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1064 LINE(PP_MERGE,     "Merge: ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
1065 LINE(PP_DATE,      "Date:   ",          COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1066 LINE(PP_ADATE,     "AuthorDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1067 LINE(PP_CDATE,     "CommitDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1068 LINE(PP_REFS,      "Refs: ",            COLOR_RED,      COLOR_DEFAULT,  0), \
1069 LINE(COMMIT,       "commit ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
1070 LINE(PARENT,       "parent ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
1071 LINE(TREE,         "tree ",             COLOR_BLUE,     COLOR_DEFAULT,  0), \
1072 LINE(AUTHOR,       "author ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
1073 LINE(COMMITTER,    "committer ",        COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1074 LINE(SIGNOFF,      "    Signed-off-by", COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1075 LINE(ACKED,        "    Acked-by",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1076 LINE(DEFAULT,      "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
1077 LINE(CURSOR,       "",                  COLOR_WHITE,    COLOR_GREEN,    A_BOLD), \
1078 LINE(STATUS,       "",                  COLOR_GREEN,    COLOR_DEFAULT,  0), \
1079 LINE(DELIMITER,    "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1080 LINE(DATE,         "",                  COLOR_BLUE,     COLOR_DEFAULT,  0), \
1081 LINE(MODE,         "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1082 LINE(LINE_NUMBER,  "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1083 LINE(TITLE_BLUR,   "",                  COLOR_WHITE,    COLOR_BLUE,     0), \
1084 LINE(TITLE_FOCUS,  "",                  COLOR_WHITE,    COLOR_BLUE,     A_BOLD), \
1085 LINE(MAIN_COMMIT,  "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
1086 LINE(MAIN_TAG,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  A_BOLD), \
1087 LINE(MAIN_LOCAL_TAG,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1088 LINE(MAIN_REMOTE,  "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1089 LINE(MAIN_TRACKED, "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_BOLD), \
1090 LINE(MAIN_REF,     "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1091 LINE(MAIN_HEAD,    "",                  COLOR_CYAN,     COLOR_DEFAULT,  A_BOLD), \
1092 LINE(MAIN_REVGRAPH,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1093 LINE(TREE_HEAD,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_BOLD), \
1094 LINE(TREE_DIR,     "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_NORMAL), \
1095 LINE(TREE_FILE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
1096 LINE(STAT_HEAD,    "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1097 LINE(STAT_SECTION, "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1098 LINE(STAT_NONE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
1099 LINE(STAT_STAGED,  "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1100 LINE(STAT_UNSTAGED,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1101 LINE(STAT_UNTRACKED,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1102 LINE(HELP_KEYMAP,  "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1103 LINE(HELP_GROUP,   "",                  COLOR_BLUE,     COLOR_DEFAULT,  0), \
1104 LINE(BLAME_ID,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0)
1106 enum line_type {
1107 #define LINE(type, line, fg, bg, attr) \
1108         LINE_##type
1109         LINE_INFO,
1110         LINE_NONE
1111 #undef  LINE
1112 };
1114 struct line_info {
1115         const char *name;       /* Option name. */
1116         int namelen;            /* Size of option name. */
1117         const char *line;       /* The start of line to match. */
1118         int linelen;            /* Size of string to match. */
1119         int fg, bg, attr;       /* Color and text attributes for the lines. */
1120 };
1122 static struct line_info line_info[] = {
1123 #define LINE(type, line, fg, bg, attr) \
1124         { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
1125         LINE_INFO
1126 #undef  LINE
1127 };
1129 static enum line_type
1130 get_line_type(const char *line)
1132         int linelen = strlen(line);
1133         enum line_type type;
1135         for (type = 0; type < ARRAY_SIZE(line_info); type++)
1136                 /* Case insensitive search matches Signed-off-by lines better. */
1137                 if (linelen >= line_info[type].linelen &&
1138                     !strncasecmp(line_info[type].line, line, line_info[type].linelen))
1139                         return type;
1141         return LINE_DEFAULT;
1144 static inline int
1145 get_line_attr(enum line_type type)
1147         assert(type < ARRAY_SIZE(line_info));
1148         return COLOR_PAIR(type) | line_info[type].attr;
1151 static struct line_info *
1152 get_line_info(const char *name)
1154         size_t namelen = strlen(name);
1155         enum line_type type;
1157         for (type = 0; type < ARRAY_SIZE(line_info); type++)
1158                 if (enum_equals(line_info[type], name, namelen))
1159                         return &line_info[type];
1161         return NULL;
1164 static void
1165 init_colors(void)
1167         int default_bg = line_info[LINE_DEFAULT].bg;
1168         int default_fg = line_info[LINE_DEFAULT].fg;
1169         enum line_type type;
1171         start_color();
1173         if (assume_default_colors(default_fg, default_bg) == ERR) {
1174                 default_bg = COLOR_BLACK;
1175                 default_fg = COLOR_WHITE;
1176         }
1178         for (type = 0; type < ARRAY_SIZE(line_info); type++) {
1179                 struct line_info *info = &line_info[type];
1180                 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
1181                 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
1183                 init_pair(type, fg, bg);
1184         }
1187 struct line {
1188         enum line_type type;
1190         /* State flags */
1191         unsigned int selected:1;
1192         unsigned int dirty:1;
1193         unsigned int cleareol:1;
1194         unsigned int other:16;
1196         void *data;             /* User data */
1197 };
1200 /*
1201  * Keys
1202  */
1204 struct keybinding {
1205         int alias;
1206         enum request request;
1207 };
1209 static const struct keybinding default_keybindings[] = {
1210         /* View switching */
1211         { 'm',          REQ_VIEW_MAIN },
1212         { 'd',          REQ_VIEW_DIFF },
1213         { 'l',          REQ_VIEW_LOG },
1214         { 't',          REQ_VIEW_TREE },
1215         { 'f',          REQ_VIEW_BLOB },
1216         { 'B',          REQ_VIEW_BLAME },
1217         { 'H',          REQ_VIEW_BRANCH },
1218         { 'p',          REQ_VIEW_PAGER },
1219         { 'h',          REQ_VIEW_HELP },
1220         { 'S',          REQ_VIEW_STATUS },
1221         { 'c',          REQ_VIEW_STAGE },
1223         /* View manipulation */
1224         { 'q',          REQ_VIEW_CLOSE },
1225         { KEY_TAB,      REQ_VIEW_NEXT },
1226         { KEY_RETURN,   REQ_ENTER },
1227         { KEY_UP,       REQ_PREVIOUS },
1228         { KEY_DOWN,     REQ_NEXT },
1229         { 'R',          REQ_REFRESH },
1230         { KEY_F(5),     REQ_REFRESH },
1231         { 'O',          REQ_MAXIMIZE },
1233         /* Cursor navigation */
1234         { 'k',          REQ_MOVE_UP },
1235         { 'j',          REQ_MOVE_DOWN },
1236         { KEY_HOME,     REQ_MOVE_FIRST_LINE },
1237         { KEY_END,      REQ_MOVE_LAST_LINE },
1238         { KEY_NPAGE,    REQ_MOVE_PAGE_DOWN },
1239         { ' ',          REQ_MOVE_PAGE_DOWN },
1240         { KEY_PPAGE,    REQ_MOVE_PAGE_UP },
1241         { 'b',          REQ_MOVE_PAGE_UP },
1242         { '-',          REQ_MOVE_PAGE_UP },
1244         /* Scrolling */
1245         { KEY_LEFT,     REQ_SCROLL_LEFT },
1246         { KEY_RIGHT,    REQ_SCROLL_RIGHT },
1247         { KEY_IC,       REQ_SCROLL_LINE_UP },
1248         { KEY_DC,       REQ_SCROLL_LINE_DOWN },
1249         { 'w',          REQ_SCROLL_PAGE_UP },
1250         { 's',          REQ_SCROLL_PAGE_DOWN },
1252         /* Searching */
1253         { '/',          REQ_SEARCH },
1254         { '?',          REQ_SEARCH_BACK },
1255         { 'n',          REQ_FIND_NEXT },
1256         { 'N',          REQ_FIND_PREV },
1258         /* Misc */
1259         { 'Q',          REQ_QUIT },
1260         { 'z',          REQ_STOP_LOADING },
1261         { 'v',          REQ_SHOW_VERSION },
1262         { 'r',          REQ_SCREEN_REDRAW },
1263         { 'o',          REQ_OPTIONS },
1264         { '.',          REQ_TOGGLE_LINENO },
1265         { 'D',          REQ_TOGGLE_DATE },
1266         { 'A',          REQ_TOGGLE_AUTHOR },
1267         { 'g',          REQ_TOGGLE_REV_GRAPH },
1268         { 'F',          REQ_TOGGLE_REFS },
1269         { 'I',          REQ_TOGGLE_SORT_ORDER },
1270         { 'i',          REQ_TOGGLE_SORT_FIELD },
1271         { ':',          REQ_PROMPT },
1272         { 'u',          REQ_STATUS_UPDATE },
1273         { '!',          REQ_STATUS_REVERT },
1274         { 'M',          REQ_STATUS_MERGE },
1275         { '@',          REQ_STAGE_NEXT },
1276         { ',',          REQ_PARENT },
1277         { 'e',          REQ_EDIT },
1278 };
1280 #define KEYMAP_INFO \
1281         KEYMAP_(GENERIC), \
1282         KEYMAP_(MAIN), \
1283         KEYMAP_(DIFF), \
1284         KEYMAP_(LOG), \
1285         KEYMAP_(TREE), \
1286         KEYMAP_(BLOB), \
1287         KEYMAP_(BLAME), \
1288         KEYMAP_(BRANCH), \
1289         KEYMAP_(PAGER), \
1290         KEYMAP_(HELP), \
1291         KEYMAP_(STATUS), \
1292         KEYMAP_(STAGE)
1294 enum keymap {
1295 #define KEYMAP_(name) KEYMAP_##name
1296         KEYMAP_INFO
1297 #undef  KEYMAP_
1298 };
1300 static const struct enum_map keymap_table[] = {
1301 #define KEYMAP_(name) ENUM_MAP(#name, KEYMAP_##name)
1302         KEYMAP_INFO
1303 #undef  KEYMAP_
1304 };
1306 #define set_keymap(map, name) map_enum(map, keymap_table, name)
1308 struct keybinding_table {
1309         struct keybinding *data;
1310         size_t size;
1311 };
1313 static struct keybinding_table keybindings[ARRAY_SIZE(keymap_table)];
1315 static void
1316 add_keybinding(enum keymap keymap, enum request request, int key)
1318         struct keybinding_table *table = &keybindings[keymap];
1320         table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data));
1321         if (!table->data)
1322                 die("Failed to allocate keybinding");
1323         table->data[table->size].alias = key;
1324         table->data[table->size++].request = request;
1327 /* Looks for a key binding first in the given map, then in the generic map, and
1328  * lastly in the default keybindings. */
1329 static enum request
1330 get_keybinding(enum keymap keymap, int key)
1332         size_t i;
1334         for (i = 0; i < keybindings[keymap].size; i++)
1335                 if (keybindings[keymap].data[i].alias == key)
1336                         return keybindings[keymap].data[i].request;
1338         for (i = 0; i < keybindings[KEYMAP_GENERIC].size; i++)
1339                 if (keybindings[KEYMAP_GENERIC].data[i].alias == key)
1340                         return keybindings[KEYMAP_GENERIC].data[i].request;
1342         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1343                 if (default_keybindings[i].alias == key)
1344                         return default_keybindings[i].request;
1346         return (enum request) key;
1350 struct key {
1351         const char *name;
1352         int value;
1353 };
1355 static const struct key key_table[] = {
1356         { "Enter",      KEY_RETURN },
1357         { "Space",      ' ' },
1358         { "Backspace",  KEY_BACKSPACE },
1359         { "Tab",        KEY_TAB },
1360         { "Escape",     KEY_ESC },
1361         { "Left",       KEY_LEFT },
1362         { "Right",      KEY_RIGHT },
1363         { "Up",         KEY_UP },
1364         { "Down",       KEY_DOWN },
1365         { "Insert",     KEY_IC },
1366         { "Delete",     KEY_DC },
1367         { "Hash",       '#' },
1368         { "Home",       KEY_HOME },
1369         { "End",        KEY_END },
1370         { "PageUp",     KEY_PPAGE },
1371         { "PageDown",   KEY_NPAGE },
1372         { "F1",         KEY_F(1) },
1373         { "F2",         KEY_F(2) },
1374         { "F3",         KEY_F(3) },
1375         { "F4",         KEY_F(4) },
1376         { "F5",         KEY_F(5) },
1377         { "F6",         KEY_F(6) },
1378         { "F7",         KEY_F(7) },
1379         { "F8",         KEY_F(8) },
1380         { "F9",         KEY_F(9) },
1381         { "F10",        KEY_F(10) },
1382         { "F11",        KEY_F(11) },
1383         { "F12",        KEY_F(12) },
1384 };
1386 static int
1387 get_key_value(const char *name)
1389         int i;
1391         for (i = 0; i < ARRAY_SIZE(key_table); i++)
1392                 if (!strcasecmp(key_table[i].name, name))
1393                         return key_table[i].value;
1395         if (strlen(name) == 1 && isprint(*name))
1396                 return (int) *name;
1398         return ERR;
1401 static const char *
1402 get_key_name(int key_value)
1404         static char key_char[] = "'X'";
1405         const char *seq = NULL;
1406         int key;
1408         for (key = 0; key < ARRAY_SIZE(key_table); key++)
1409                 if (key_table[key].value == key_value)
1410                         seq = key_table[key].name;
1412         if (seq == NULL &&
1413             key_value < 127 &&
1414             isprint(key_value)) {
1415                 key_char[1] = (char) key_value;
1416                 seq = key_char;
1417         }
1419         return seq ? seq : "(no key)";
1422 static bool
1423 append_key(char *buf, size_t *pos, const struct keybinding *keybinding)
1425         const char *sep = *pos > 0 ? ", " : "";
1426         const char *keyname = get_key_name(keybinding->alias);
1428         return string_nformat(buf, BUFSIZ, pos, "%s%s", sep, keyname);
1431 static bool
1432 append_keymap_request_keys(char *buf, size_t *pos, enum request request,
1433                            enum keymap keymap, bool all)
1435         int i;
1437         for (i = 0; i < keybindings[keymap].size; i++) {
1438                 if (keybindings[keymap].data[i].request == request) {
1439                         if (!append_key(buf, pos, &keybindings[keymap].data[i]))
1440                                 return FALSE;
1441                         if (!all)
1442                                 break;
1443                 }
1444         }
1446         return TRUE;
1449 #define get_key(keymap, request) get_keys(keymap, request, FALSE)
1451 static const char *
1452 get_keys(enum keymap keymap, enum request request, bool all)
1454         static char buf[BUFSIZ];
1455         size_t pos = 0;
1456         int i;
1458         buf[pos] = 0;
1460         if (!append_keymap_request_keys(buf, &pos, request, keymap, all))
1461                 return "Too many keybindings!";
1462         if (pos > 0 && !all)
1463                 return buf;
1465         if (keymap != KEYMAP_GENERIC) {
1466                 /* Only the generic keymap includes the default keybindings when
1467                  * listing all keys. */
1468                 if (all)
1469                         return buf;
1471                 if (!append_keymap_request_keys(buf, &pos, request, KEYMAP_GENERIC, all))
1472                         return "Too many keybindings!";
1473                 if (pos)
1474                         return buf;
1475         }
1477         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1478                 if (default_keybindings[i].request == request) {
1479                         if (!append_key(buf, &pos, &default_keybindings[i]))
1480                                 return "Too many keybindings!";
1481                         if (!all)
1482                                 return buf;
1483                 }
1484         }
1486         return buf;
1489 struct run_request {
1490         enum keymap keymap;
1491         int key;
1492         const char *argv[SIZEOF_ARG];
1493 };
1495 static struct run_request *run_request;
1496 static size_t run_requests;
1498 DEFINE_ALLOCATOR(realloc_run_requests, struct run_request, 8)
1500 static enum request
1501 add_run_request(enum keymap keymap, int key, int argc, const char **argv)
1503         struct run_request *req;
1505         if (argc >= ARRAY_SIZE(req->argv) - 1)
1506                 return REQ_NONE;
1508         if (!realloc_run_requests(&run_request, run_requests, 1))
1509                 return REQ_NONE;
1511         req = &run_request[run_requests];
1512         req->keymap = keymap;
1513         req->key = key;
1514         req->argv[0] = NULL;
1516         if (!format_argv(req->argv, argv, FORMAT_NONE))
1517                 return REQ_NONE;
1519         return REQ_NONE + ++run_requests;
1522 static struct run_request *
1523 get_run_request(enum request request)
1525         if (request <= REQ_NONE)
1526                 return NULL;
1527         return &run_request[request - REQ_NONE - 1];
1530 static void
1531 add_builtin_run_requests(void)
1533         const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1534         const char *commit[] = { "git", "commit", NULL };
1535         const char *gc[] = { "git", "gc", NULL };
1536         struct {
1537                 enum keymap keymap;
1538                 int key;
1539                 int argc;
1540                 const char **argv;
1541         } reqs[] = {
1542                 { KEYMAP_MAIN,    'C', ARRAY_SIZE(cherry_pick) - 1, cherry_pick },
1543                 { KEYMAP_STATUS,  'C', ARRAY_SIZE(commit) - 1, commit },
1544                 { KEYMAP_GENERIC, 'G', ARRAY_SIZE(gc) - 1, gc },
1545         };
1546         int i;
1548         for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1549                 enum request req;
1551                 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argc, reqs[i].argv);
1552                 if (req != REQ_NONE)
1553                         add_keybinding(reqs[i].keymap, req, reqs[i].key);
1554         }
1557 /*
1558  * User config file handling.
1559  */
1561 static int   config_lineno;
1562 static bool  config_errors;
1563 static const char *config_msg;
1565 static const struct enum_map color_map[] = {
1566 #define COLOR_MAP(name) ENUM_MAP(#name, COLOR_##name)
1567         COLOR_MAP(DEFAULT),
1568         COLOR_MAP(BLACK),
1569         COLOR_MAP(BLUE),
1570         COLOR_MAP(CYAN),
1571         COLOR_MAP(GREEN),
1572         COLOR_MAP(MAGENTA),
1573         COLOR_MAP(RED),
1574         COLOR_MAP(WHITE),
1575         COLOR_MAP(YELLOW),
1576 };
1578 static const struct enum_map attr_map[] = {
1579 #define ATTR_MAP(name) ENUM_MAP(#name, A_##name)
1580         ATTR_MAP(NORMAL),
1581         ATTR_MAP(BLINK),
1582         ATTR_MAP(BOLD),
1583         ATTR_MAP(DIM),
1584         ATTR_MAP(REVERSE),
1585         ATTR_MAP(STANDOUT),
1586         ATTR_MAP(UNDERLINE),
1587 };
1589 #define set_attribute(attr, name)       map_enum(attr, attr_map, name)
1591 static int parse_step(double *opt, const char *arg)
1593         *opt = atoi(arg);
1594         if (!strchr(arg, '%'))
1595                 return OK;
1597         /* "Shift down" so 100% and 1 does not conflict. */
1598         *opt = (*opt - 1) / 100;
1599         if (*opt >= 1.0) {
1600                 *opt = 0.99;
1601                 config_msg = "Step value larger than 100%";
1602                 return ERR;
1603         }
1604         if (*opt < 0.0) {
1605                 *opt = 1;
1606                 config_msg = "Invalid step value";
1607                 return ERR;
1608         }
1609         return OK;
1612 static int
1613 parse_int(int *opt, const char *arg, int min, int max)
1615         int value = atoi(arg);
1617         if (min <= value && value <= max) {
1618                 *opt = value;
1619                 return OK;
1620         }
1622         config_msg = "Integer value out of bound";
1623         return ERR;
1626 static bool
1627 set_color(int *color, const char *name)
1629         if (map_enum(color, color_map, name))
1630                 return TRUE;
1631         if (!prefixcmp(name, "color"))
1632                 return parse_int(color, name + 5, 0, 255) == OK;
1633         return FALSE;
1636 /* Wants: object fgcolor bgcolor [attribute] */
1637 static int
1638 option_color_command(int argc, const char *argv[])
1640         struct line_info *info;
1642         if (argc < 3) {
1643                 config_msg = "Wrong number of arguments given to color command";
1644                 return ERR;
1645         }
1647         info = get_line_info(argv[0]);
1648         if (!info) {
1649                 static const struct enum_map obsolete[] = {
1650                         ENUM_MAP("main-delim",  LINE_DELIMITER),
1651                         ENUM_MAP("main-date",   LINE_DATE),
1652                         ENUM_MAP("main-author", LINE_AUTHOR),
1653                 };
1654                 int index;
1656                 if (!map_enum(&index, obsolete, argv[0])) {
1657                         config_msg = "Unknown color name";
1658                         return ERR;
1659                 }
1660                 info = &line_info[index];
1661         }
1663         if (!set_color(&info->fg, argv[1]) ||
1664             !set_color(&info->bg, argv[2])) {
1665                 config_msg = "Unknown color";
1666                 return ERR;
1667         }
1669         info->attr = 0;
1670         while (argc-- > 3) {
1671                 int attr;
1673                 if (!set_attribute(&attr, argv[argc])) {
1674                         config_msg = "Unknown attribute";
1675                         return ERR;
1676                 }
1677                 info->attr |= attr;
1678         }
1680         return OK;
1683 static int parse_bool(bool *opt, const char *arg)
1685         *opt = (!strcmp(arg, "1") || !strcmp(arg, "true") || !strcmp(arg, "yes"))
1686                 ? TRUE : FALSE;
1687         return OK;
1690 static int parse_enum_do(unsigned int *opt, const char *arg,
1691                          const struct enum_map *map, size_t map_size)
1693         bool is_true;
1695         assert(map_size > 1);
1697         if (map_enum_do(map, map_size, (int *) opt, arg))
1698                 return OK;
1700         if (parse_bool(&is_true, arg) != OK)
1701                 return ERR;
1703         *opt = is_true ? map[1].value : map[0].value;
1704         return OK;
1707 #define parse_enum(opt, arg, map) \
1708         parse_enum_do(opt, arg, map, ARRAY_SIZE(map))
1710 static int
1711 parse_string(char *opt, const char *arg, size_t optsize)
1713         int arglen = strlen(arg);
1715         switch (arg[0]) {
1716         case '\"':
1717         case '\'':
1718                 if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1719                         config_msg = "Unmatched quotation";
1720                         return ERR;
1721                 }
1722                 arg += 1; arglen -= 2;
1723         default:
1724                 string_ncopy_do(opt, optsize, arg, arglen);
1725                 return OK;
1726         }
1729 /* Wants: name = value */
1730 static int
1731 option_set_command(int argc, const char *argv[])
1733         if (argc != 3) {
1734                 config_msg = "Wrong number of arguments given to set command";
1735                 return ERR;
1736         }
1738         if (strcmp(argv[1], "=")) {
1739                 config_msg = "No value assigned";
1740                 return ERR;
1741         }
1743         if (!strcmp(argv[0], "show-author"))
1744                 return parse_bool(&opt_author, argv[2]);
1746         if (!strcmp(argv[0], "show-date"))
1747                 return parse_enum(&opt_date, argv[2], date_map);
1749         if (!strcmp(argv[0], "show-rev-graph"))
1750                 return parse_bool(&opt_rev_graph, argv[2]);
1752         if (!strcmp(argv[0], "show-refs"))
1753                 return parse_bool(&opt_show_refs, argv[2]);
1755         if (!strcmp(argv[0], "show-line-numbers"))
1756                 return parse_bool(&opt_line_number, argv[2]);
1758         if (!strcmp(argv[0], "line-graphics"))
1759                 return parse_bool(&opt_line_graphics, argv[2]);
1761         if (!strcmp(argv[0], "line-number-interval"))
1762                 return parse_int(&opt_num_interval, argv[2], 1, 1024);
1764         if (!strcmp(argv[0], "author-width"))
1765                 return parse_int(&opt_author_cols, argv[2], 0, 1024);
1767         if (!strcmp(argv[0], "horizontal-scroll"))
1768                 return parse_step(&opt_hscroll, argv[2]);
1770         if (!strcmp(argv[0], "split-view-height"))
1771                 return parse_step(&opt_scale_split_view, argv[2]);
1773         if (!strcmp(argv[0], "tab-size"))
1774                 return parse_int(&opt_tab_size, argv[2], 1, 1024);
1776         if (!strcmp(argv[0], "commit-encoding"))
1777                 return parse_string(opt_encoding, argv[2], sizeof(opt_encoding));
1779         config_msg = "Unknown variable name";
1780         return ERR;
1783 /* Wants: mode request key */
1784 static int
1785 option_bind_command(int argc, const char *argv[])
1787         enum request request;
1788         int keymap = -1;
1789         int key;
1791         if (argc < 3) {
1792                 config_msg = "Wrong number of arguments given to bind command";
1793                 return ERR;
1794         }
1796         if (set_keymap(&keymap, argv[0]) == ERR) {
1797                 config_msg = "Unknown key map";
1798                 return ERR;
1799         }
1801         key = get_key_value(argv[1]);
1802         if (key == ERR) {
1803                 config_msg = "Unknown key";
1804                 return ERR;
1805         }
1807         request = get_request(argv[2]);
1808         if (request == REQ_NONE) {
1809                 static const struct enum_map obsolete[] = {
1810                         ENUM_MAP("cherry-pick",         REQ_NONE),
1811                         ENUM_MAP("screen-resize",       REQ_NONE),
1812                         ENUM_MAP("tree-parent",         REQ_PARENT),
1813                 };
1814                 int alias;
1816                 if (map_enum(&alias, obsolete, argv[2])) {
1817                         if (alias != REQ_NONE)
1818                                 add_keybinding(keymap, alias, key);
1819                         config_msg = "Obsolete request name";
1820                         return ERR;
1821                 }
1822         }
1823         if (request == REQ_NONE && *argv[2]++ == '!')
1824                 request = add_run_request(keymap, key, argc - 2, argv + 2);
1825         if (request == REQ_NONE) {
1826                 config_msg = "Unknown request name";
1827                 return ERR;
1828         }
1830         add_keybinding(keymap, request, key);
1832         return OK;
1835 static int
1836 set_option(const char *opt, char *value)
1838         const char *argv[SIZEOF_ARG];
1839         int argc = 0;
1841         if (!argv_from_string(argv, &argc, value)) {
1842                 config_msg = "Too many option arguments";
1843                 return ERR;
1844         }
1846         if (!strcmp(opt, "color"))
1847                 return option_color_command(argc, argv);
1849         if (!strcmp(opt, "set"))
1850                 return option_set_command(argc, argv);
1852         if (!strcmp(opt, "bind"))
1853                 return option_bind_command(argc, argv);
1855         config_msg = "Unknown option command";
1856         return ERR;
1859 static int
1860 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
1862         int status = OK;
1864         config_lineno++;
1865         config_msg = "Internal error";
1867         /* Check for comment markers, since read_properties() will
1868          * only ensure opt and value are split at first " \t". */
1869         optlen = strcspn(opt, "#");
1870         if (optlen == 0)
1871                 return OK;
1873         if (opt[optlen] != 0) {
1874                 config_msg = "No option value";
1875                 status = ERR;
1877         }  else {
1878                 /* Look for comment endings in the value. */
1879                 size_t len = strcspn(value, "#");
1881                 if (len < valuelen) {
1882                         valuelen = len;
1883                         value[valuelen] = 0;
1884                 }
1886                 status = set_option(opt, value);
1887         }
1889         if (status == ERR) {
1890                 warn("Error on line %d, near '%.*s': %s",
1891                      config_lineno, (int) optlen, opt, config_msg);
1892                 config_errors = TRUE;
1893         }
1895         /* Always keep going if errors are encountered. */
1896         return OK;
1899 static void
1900 load_option_file(const char *path)
1902         struct io io = {};
1904         /* It's OK that the file doesn't exist. */
1905         if (!io_open(&io, "%s", path))
1906                 return;
1908         config_lineno = 0;
1909         config_errors = FALSE;
1911         if (io_load(&io, " \t", read_option) == ERR ||
1912             config_errors == TRUE)
1913                 warn("Errors while loading %s.", path);
1916 static int
1917 load_options(void)
1919         const char *home = getenv("HOME");
1920         const char *tigrc_user = getenv("TIGRC_USER");
1921         const char *tigrc_system = getenv("TIGRC_SYSTEM");
1922         char buf[SIZEOF_STR];
1924         add_builtin_run_requests();
1926         if (!tigrc_system)
1927                 tigrc_system = SYSCONFDIR "/tigrc";
1928         load_option_file(tigrc_system);
1930         if (!tigrc_user) {
1931                 if (!home || !string_format(buf, "%s/.tigrc", home))
1932                         return ERR;
1933                 tigrc_user = buf;
1934         }
1935         load_option_file(tigrc_user);
1937         return OK;
1941 /*
1942  * The viewer
1943  */
1945 struct view;
1946 struct view_ops;
1948 /* The display array of active views and the index of the current view. */
1949 static struct view *display[2];
1950 static unsigned int current_view;
1952 #define foreach_displayed_view(view, i) \
1953         for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1955 #define displayed_views()       (display[1] != NULL ? 2 : 1)
1957 /* Current head and commit ID */
1958 static char ref_blob[SIZEOF_REF]        = "";
1959 static char ref_commit[SIZEOF_REF]      = "HEAD";
1960 static char ref_head[SIZEOF_REF]        = "HEAD";
1962 struct view {
1963         const char *name;       /* View name */
1964         const char *cmd_env;    /* Command line set via environment */
1965         const char *id;         /* Points to either of ref_{head,commit,blob} */
1967         struct view_ops *ops;   /* View operations */
1969         enum keymap keymap;     /* What keymap does this view have */
1970         bool git_dir;           /* Whether the view requires a git directory. */
1972         char ref[SIZEOF_REF];   /* Hovered commit reference */
1973         char vid[SIZEOF_REF];   /* View ID. Set to id member when updating. */
1975         int height, width;      /* The width and height of the main window */
1976         WINDOW *win;            /* The main window */
1977         WINDOW *title;          /* The title window living below the main window */
1979         /* Navigation */
1980         unsigned long offset;   /* Offset of the window top */
1981         unsigned long yoffset;  /* Offset from the window side. */
1982         unsigned long lineno;   /* Current line number */
1983         unsigned long p_offset; /* Previous offset of the window top */
1984         unsigned long p_yoffset;/* Previous offset from the window side */
1985         unsigned long p_lineno; /* Previous current line number */
1986         bool p_restore;         /* Should the previous position be restored. */
1988         /* Searching */
1989         char grep[SIZEOF_STR];  /* Search string */
1990         regex_t *regex;         /* Pre-compiled regexp */
1992         /* If non-NULL, points to the view that opened this view. If this view
1993          * is closed tig will switch back to the parent view. */
1994         struct view *parent;
1996         /* Buffering */
1997         size_t lines;           /* Total number of lines */
1998         struct line *line;      /* Line index */
1999         unsigned int digits;    /* Number of digits in the lines member. */
2001         /* Drawing */
2002         struct line *curline;   /* Line currently being drawn. */
2003         enum line_type curtype; /* Attribute currently used for drawing. */
2004         unsigned long col;      /* Column when drawing. */
2005         bool has_scrolled;      /* View was scrolled. */
2007         /* Loading */
2008         struct io io;
2009         struct io *pipe;
2010         time_t start_time;
2011         time_t update_secs;
2012 };
2014 struct view_ops {
2015         /* What type of content being displayed. Used in the title bar. */
2016         const char *type;
2017         /* Default command arguments. */
2018         const char **argv;
2019         /* Open and reads in all view content. */
2020         bool (*open)(struct view *view);
2021         /* Read one line; updates view->line. */
2022         bool (*read)(struct view *view, char *data);
2023         /* Draw one line; @lineno must be < view->height. */
2024         bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
2025         /* Depending on view handle a special requests. */
2026         enum request (*request)(struct view *view, enum request request, struct line *line);
2027         /* Search for regexp in a line. */
2028         bool (*grep)(struct view *view, struct line *line);
2029         /* Select line */
2030         void (*select)(struct view *view, struct line *line);
2031         /* Prepare view for loading */
2032         bool (*prepare)(struct view *view);
2033 };
2035 static struct view_ops blame_ops;
2036 static struct view_ops blob_ops;
2037 static struct view_ops diff_ops;
2038 static struct view_ops help_ops;
2039 static struct view_ops log_ops;
2040 static struct view_ops main_ops;
2041 static struct view_ops pager_ops;
2042 static struct view_ops stage_ops;
2043 static struct view_ops status_ops;
2044 static struct view_ops tree_ops;
2045 static struct view_ops branch_ops;
2047 #define VIEW_STR(name, env, ref, ops, map, git) \
2048         { name, #env, ref, ops, map, git }
2050 #define VIEW_(id, name, ops, git, ref) \
2051         VIEW_STR(name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
2054 static struct view views[] = {
2055         VIEW_(MAIN,   "main",   &main_ops,   TRUE,  ref_head),
2056         VIEW_(DIFF,   "diff",   &diff_ops,   TRUE,  ref_commit),
2057         VIEW_(LOG,    "log",    &log_ops,    TRUE,  ref_head),
2058         VIEW_(TREE,   "tree",   &tree_ops,   TRUE,  ref_commit),
2059         VIEW_(BLOB,   "blob",   &blob_ops,   TRUE,  ref_blob),
2060         VIEW_(BLAME,  "blame",  &blame_ops,  TRUE,  ref_commit),
2061         VIEW_(BRANCH, "branch", &branch_ops, TRUE,  ref_head),
2062         VIEW_(HELP,   "help",   &help_ops,   FALSE, ""),
2063         VIEW_(PAGER,  "pager",  &pager_ops,  FALSE, "stdin"),
2064         VIEW_(STATUS, "status", &status_ops, TRUE,  ""),
2065         VIEW_(STAGE,  "stage",  &stage_ops,  TRUE,  ""),
2066 };
2068 #define VIEW(req)       (&views[(req) - REQ_OFFSET - 1])
2069 #define VIEW_REQ(view)  ((view) - views + REQ_OFFSET + 1)
2071 #define foreach_view(view, i) \
2072         for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
2074 #define view_is_displayed(view) \
2075         (view == display[0] || view == display[1])
2078 enum line_graphic {
2079         LINE_GRAPHIC_VLINE
2080 };
2082 static chtype line_graphics[] = {
2083         /* LINE_GRAPHIC_VLINE: */ '|'
2084 };
2086 static inline void
2087 set_view_attr(struct view *view, enum line_type type)
2089         if (!view->curline->selected && view->curtype != type) {
2090                 wattrset(view->win, get_line_attr(type));
2091                 wchgat(view->win, -1, 0, type, NULL);
2092                 view->curtype = type;
2093         }
2096 static int
2097 draw_chars(struct view *view, enum line_type type, const char *string,
2098            int max_len, bool use_tilde)
2100         static char out_buffer[BUFSIZ * 2];
2101         int len = 0;
2102         int col = 0;
2103         int trimmed = FALSE;
2104         size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2106         if (max_len <= 0)
2107                 return 0;
2109         len = utf8_length(&string, skip, &col, max_len, &trimmed, use_tilde);
2111         set_view_attr(view, type);
2112         if (len > 0) {
2113                 if (opt_iconv_out != ICONV_NONE) {
2114                         ICONV_CONST char *inbuf = (ICONV_CONST char *) string;
2115                         size_t inlen = len + 1;
2117                         char *outbuf = out_buffer;
2118                         size_t outlen = sizeof(out_buffer);
2120                         size_t ret;
2122                         ret = iconv(opt_iconv_out, &inbuf, &inlen, &outbuf, &outlen);
2123                         if (ret != (size_t) -1) {
2124                                 string = out_buffer;
2125                                 len = sizeof(out_buffer) - outlen;
2126                         }
2127                 }
2129                 waddnstr(view->win, string, len);
2130         }
2131         if (trimmed && use_tilde) {
2132                 set_view_attr(view, LINE_DELIMITER);
2133                 waddch(view->win, '~');
2134                 col++;
2135         }
2137         return col;
2140 static int
2141 draw_space(struct view *view, enum line_type type, int max, int spaces)
2143         static char space[] = "                    ";
2144         int col = 0;
2146         spaces = MIN(max, spaces);
2148         while (spaces > 0) {
2149                 int len = MIN(spaces, sizeof(space) - 1);
2151                 col += draw_chars(view, type, space, len, FALSE);
2152                 spaces -= len;
2153         }
2155         return col;
2158 static bool
2159 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
2161         view->col += draw_chars(view, type, string, view->width + view->yoffset - view->col, trim);
2162         return view->width + view->yoffset <= view->col;
2165 static bool
2166 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
2168         size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2169         int max = view->width + view->yoffset - view->col;
2170         int i;
2172         if (max < size)
2173                 size = max;
2175         set_view_attr(view, type);
2176         /* Using waddch() instead of waddnstr() ensures that
2177          * they'll be rendered correctly for the cursor line. */
2178         for (i = skip; i < size; i++)
2179                 waddch(view->win, graphic[i]);
2181         view->col += size;
2182         if (size < max && skip <= size)
2183                 waddch(view->win, ' ');
2184         view->col++;
2186         return view->width + view->yoffset <= view->col;
2189 static bool
2190 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
2192         int max = MIN(view->width + view->yoffset - view->col, len);
2193         int col;
2195         if (text)
2196                 col = draw_chars(view, type, text, max - 1, trim);
2197         else
2198                 col = draw_space(view, type, max - 1, max - 1);
2200         view->col += col;
2201         view->col += draw_space(view, LINE_DEFAULT, max - col, max - col);
2202         return view->width + view->yoffset <= view->col;
2205 static bool
2206 draw_date(struct view *view, time_t *time)
2208         const char *date = time ? mkdate(time) : "";
2209         int cols = opt_date == DATE_SHORT ? DATE_SHORT_COLS : DATE_COLS;
2211         return draw_field(view, LINE_DATE, date, cols, FALSE);
2214 static bool
2215 draw_author(struct view *view, const char *author)
2217         bool trim = opt_author_cols == 0 || opt_author_cols > 5 || !author;
2219         if (!trim) {
2220                 static char initials[10];
2221                 size_t pos;
2223 #define is_initial_sep(c) (isspace(c) || ispunct(c) || (c) == '@')
2225                 memset(initials, 0, sizeof(initials));
2226                 for (pos = 0; *author && pos < opt_author_cols - 1; author++, pos++) {
2227                         while (is_initial_sep(*author))
2228                                 author++;
2229                         strncpy(&initials[pos], author, sizeof(initials) - 1 - pos);
2230                         while (*author && !is_initial_sep(author[1]))
2231                                 author++;
2232                 }
2234                 author = initials;
2235         }
2237         return draw_field(view, LINE_AUTHOR, author, opt_author_cols, trim);
2240 static bool
2241 draw_mode(struct view *view, mode_t mode)
2243         const char *str;
2245         if (S_ISDIR(mode))
2246                 str = "drwxr-xr-x";
2247         else if (S_ISLNK(mode))
2248                 str = "lrwxrwxrwx";
2249         else if (S_ISGITLINK(mode))
2250                 str = "m---------";
2251         else if (S_ISREG(mode) && mode & S_IXUSR)
2252                 str = "-rwxr-xr-x";
2253         else if (S_ISREG(mode))
2254                 str = "-rw-r--r--";
2255         else
2256                 str = "----------";
2258         return draw_field(view, LINE_MODE, str, STRING_SIZE("-rw-r--r-- "), FALSE);
2261 static bool
2262 draw_lineno(struct view *view, unsigned int lineno)
2264         char number[10];
2265         int digits3 = view->digits < 3 ? 3 : view->digits;
2266         int max = MIN(view->width + view->yoffset - view->col, digits3);
2267         char *text = NULL;
2269         lineno += view->offset + 1;
2270         if (lineno == 1 || (lineno % opt_num_interval) == 0) {
2271                 static char fmt[] = "%1ld";
2273                 fmt[1] = '0' + (view->digits <= 9 ? digits3 : 1);
2274                 if (string_format(number, fmt, lineno))
2275                         text = number;
2276         }
2277         if (text)
2278                 view->col += draw_chars(view, LINE_LINE_NUMBER, text, max, TRUE);
2279         else
2280                 view->col += draw_space(view, LINE_LINE_NUMBER, max, digits3);
2281         return draw_graphic(view, LINE_DEFAULT, &line_graphics[LINE_GRAPHIC_VLINE], 1);
2284 static bool
2285 draw_view_line(struct view *view, unsigned int lineno)
2287         struct line *line;
2288         bool selected = (view->offset + lineno == view->lineno);
2290         assert(view_is_displayed(view));
2292         if (view->offset + lineno >= view->lines)
2293                 return FALSE;
2295         line = &view->line[view->offset + lineno];
2297         wmove(view->win, lineno, 0);
2298         if (line->cleareol)
2299                 wclrtoeol(view->win);
2300         view->col = 0;
2301         view->curline = line;
2302         view->curtype = LINE_NONE;
2303         line->selected = FALSE;
2304         line->dirty = line->cleareol = 0;
2306         if (selected) {
2307                 set_view_attr(view, LINE_CURSOR);
2308                 line->selected = TRUE;
2309                 view->ops->select(view, line);
2310         }
2312         return view->ops->draw(view, line, lineno);
2315 static void
2316 redraw_view_dirty(struct view *view)
2318         bool dirty = FALSE;
2319         int lineno;
2321         for (lineno = 0; lineno < view->height; lineno++) {
2322                 if (view->offset + lineno >= view->lines)
2323                         break;
2324                 if (!view->line[view->offset + lineno].dirty)
2325                         continue;
2326                 dirty = TRUE;
2327                 if (!draw_view_line(view, lineno))
2328                         break;
2329         }
2331         if (!dirty)
2332                 return;
2333         wnoutrefresh(view->win);
2336 static void
2337 redraw_view_from(struct view *view, int lineno)
2339         assert(0 <= lineno && lineno < view->height);
2341         for (; lineno < view->height; lineno++) {
2342                 if (!draw_view_line(view, lineno))
2343                         break;
2344         }
2346         wnoutrefresh(view->win);
2349 static void
2350 redraw_view(struct view *view)
2352         werase(view->win);
2353         redraw_view_from(view, 0);
2357 static void
2358 update_view_title(struct view *view)
2360         char buf[SIZEOF_STR];
2361         char state[SIZEOF_STR];
2362         size_t bufpos = 0, statelen = 0;
2364         assert(view_is_displayed(view));
2366         if (view != VIEW(REQ_VIEW_STATUS) && view->lines) {
2367                 unsigned int view_lines = view->offset + view->height;
2368                 unsigned int lines = view->lines
2369                                    ? MIN(view_lines, view->lines) * 100 / view->lines
2370                                    : 0;
2372                 string_format_from(state, &statelen, " - %s %d of %d (%d%%)",
2373                                    view->ops->type,
2374                                    view->lineno + 1,
2375                                    view->lines,
2376                                    lines);
2378         }
2380         if (view->pipe) {
2381                 time_t secs = time(NULL) - view->start_time;
2383                 /* Three git seconds are a long time ... */
2384                 if (secs > 2)
2385                         string_format_from(state, &statelen, " loading %lds", secs);
2386         }
2388         string_format_from(buf, &bufpos, "[%s]", view->name);
2389         if (*view->ref && bufpos < view->width) {
2390                 size_t refsize = strlen(view->ref);
2391                 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2393                 if (minsize < view->width)
2394                         refsize = view->width - minsize + 7;
2395                 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2396         }
2398         if (statelen && bufpos < view->width) {
2399                 string_format_from(buf, &bufpos, "%s", state);
2400         }
2402         if (view == display[current_view])
2403                 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2404         else
2405                 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2407         mvwaddnstr(view->title, 0, 0, buf, bufpos);
2408         wclrtoeol(view->title);
2409         wnoutrefresh(view->title);
2412 static int
2413 apply_step(double step, int value)
2415         if (step >= 1)
2416                 return (int) step;
2417         value *= step + 0.01;
2418         return value ? value : 1;
2421 static void
2422 resize_display(void)
2424         int offset, i;
2425         struct view *base = display[0];
2426         struct view *view = display[1] ? display[1] : display[0];
2428         /* Setup window dimensions */
2430         getmaxyx(stdscr, base->height, base->width);
2432         /* Make room for the status window. */
2433         base->height -= 1;
2435         if (view != base) {
2436                 /* Horizontal split. */
2437                 view->width   = base->width;
2438                 view->height  = apply_step(opt_scale_split_view, base->height);
2439                 view->height  = MAX(view->height, MIN_VIEW_HEIGHT);
2440                 view->height  = MIN(view->height, base->height - MIN_VIEW_HEIGHT);
2441                 base->height -= view->height;
2443                 /* Make room for the title bar. */
2444                 view->height -= 1;
2445         }
2447         /* Make room for the title bar. */
2448         base->height -= 1;
2450         offset = 0;
2452         foreach_displayed_view (view, i) {
2453                 if (!view->win) {
2454                         view->win = newwin(view->height, 0, offset, 0);
2455                         if (!view->win)
2456                                 die("Failed to create %s view", view->name);
2458                         scrollok(view->win, FALSE);
2460                         view->title = newwin(1, 0, offset + view->height, 0);
2461                         if (!view->title)
2462                                 die("Failed to create title window");
2464                 } else {
2465                         wresize(view->win, view->height, view->width);
2466                         mvwin(view->win,   offset, 0);
2467                         mvwin(view->title, offset + view->height, 0);
2468                 }
2470                 offset += view->height + 1;
2471         }
2474 static void
2475 redraw_display(bool clear)
2477         struct view *view;
2478         int i;
2480         foreach_displayed_view (view, i) {
2481                 if (clear)
2482                         wclear(view->win);
2483                 redraw_view(view);
2484                 update_view_title(view);
2485         }
2488 static void
2489 toggle_enum_option_do(unsigned int *opt, const char *help,
2490                       const struct enum_map *map, size_t size)
2492         *opt = (*opt + 1) % size;
2493         redraw_display(FALSE);
2494         report("Displaying %s %s", enum_name(map[*opt]), help);
2497 #define toggle_enum_option(opt, help, map) \
2498         toggle_enum_option_do(opt, help, map, ARRAY_SIZE(map))
2500 #define toggle_date() toggle_enum_option(&opt_date, "dates", date_map)
2502 static void
2503 toggle_view_option(bool *option, const char *help)
2505         *option = !*option;
2506         redraw_display(FALSE);
2507         report("%sabling %s", *option ? "En" : "Dis", help);
2510 static void
2511 open_option_menu(void)
2513         const struct menu_item menu[] = {
2514                 { '.', "line numbers", &opt_line_number },
2515                 { 'D', "date display", &opt_date },
2516                 { 'A', "author display", &opt_author },
2517                 { 'g', "revision graph display", &opt_rev_graph },
2518                 { 'F', "reference display", &opt_show_refs },
2519                 { 0 }
2520         };
2521         int selected = 0;
2523         if (prompt_menu("Toggle option", menu, &selected)) {
2524                 if (menu[selected].data == &opt_date)
2525                         toggle_date();
2526                 else
2527                         toggle_view_option(menu[selected].data, menu[selected].text);
2528         }
2531 static void
2532 maximize_view(struct view *view)
2534         memset(display, 0, sizeof(display));
2535         current_view = 0;
2536         display[current_view] = view;
2537         resize_display();
2538         redraw_display(FALSE);
2539         report("");
2543 /*
2544  * Navigation
2545  */
2547 static bool
2548 goto_view_line(struct view *view, unsigned long offset, unsigned long lineno)
2550         if (lineno >= view->lines)
2551                 lineno = view->lines > 0 ? view->lines - 1 : 0;
2553         if (offset > lineno || offset + view->height <= lineno) {
2554                 unsigned long half = view->height / 2;
2556                 if (lineno > half)
2557                         offset = lineno - half;
2558                 else
2559                         offset = 0;
2560         }
2562         if (offset != view->offset || lineno != view->lineno) {
2563                 view->offset = offset;
2564                 view->lineno = lineno;
2565                 return TRUE;
2566         }
2568         return FALSE;
2571 /* Scrolling backend */
2572 static void
2573 do_scroll_view(struct view *view, int lines)
2575         bool redraw_current_line = FALSE;
2577         /* The rendering expects the new offset. */
2578         view->offset += lines;
2580         assert(0 <= view->offset && view->offset < view->lines);
2581         assert(lines);
2583         /* Move current line into the view. */
2584         if (view->lineno < view->offset) {
2585                 view->lineno = view->offset;
2586                 redraw_current_line = TRUE;
2587         } else if (view->lineno >= view->offset + view->height) {
2588                 view->lineno = view->offset + view->height - 1;
2589                 redraw_current_line = TRUE;
2590         }
2592         assert(view->offset <= view->lineno && view->lineno < view->lines);
2594         /* Redraw the whole screen if scrolling is pointless. */
2595         if (view->height < ABS(lines)) {
2596                 redraw_view(view);
2598         } else {
2599                 int line = lines > 0 ? view->height - lines : 0;
2600                 int end = line + ABS(lines);
2602                 scrollok(view->win, TRUE);
2603                 wscrl(view->win, lines);
2604                 scrollok(view->win, FALSE);
2606                 while (line < end && draw_view_line(view, line))
2607                         line++;
2609                 if (redraw_current_line)
2610                         draw_view_line(view, view->lineno - view->offset);
2611                 wnoutrefresh(view->win);
2612         }
2614         view->has_scrolled = TRUE;
2615         report("");
2618 /* Scroll frontend */
2619 static void
2620 scroll_view(struct view *view, enum request request)
2622         int lines = 1;
2624         assert(view_is_displayed(view));
2626         switch (request) {
2627         case REQ_SCROLL_LEFT:
2628                 if (view->yoffset == 0) {
2629                         report("Cannot scroll beyond the first column");
2630                         return;
2631                 }
2632                 if (view->yoffset <= apply_step(opt_hscroll, view->width))
2633                         view->yoffset = 0;
2634                 else
2635                         view->yoffset -= apply_step(opt_hscroll, view->width);
2636                 redraw_view_from(view, 0);
2637                 report("");
2638                 return;
2639         case REQ_SCROLL_RIGHT:
2640                 view->yoffset += apply_step(opt_hscroll, view->width);
2641                 redraw_view(view);
2642                 report("");
2643                 return;
2644         case REQ_SCROLL_PAGE_DOWN:
2645                 lines = view->height;
2646         case REQ_SCROLL_LINE_DOWN:
2647                 if (view->offset + lines > view->lines)
2648                         lines = view->lines - view->offset;
2650                 if (lines == 0 || view->offset + view->height >= view->lines) {
2651                         report("Cannot scroll beyond the last line");
2652                         return;
2653                 }
2654                 break;
2656         case REQ_SCROLL_PAGE_UP:
2657                 lines = view->height;
2658         case REQ_SCROLL_LINE_UP:
2659                 if (lines > view->offset)
2660                         lines = view->offset;
2662                 if (lines == 0) {
2663                         report("Cannot scroll beyond the first line");
2664                         return;
2665                 }
2667                 lines = -lines;
2668                 break;
2670         default:
2671                 die("request %d not handled in switch", request);
2672         }
2674         do_scroll_view(view, lines);
2677 /* Cursor moving */
2678 static void
2679 move_view(struct view *view, enum request request)
2681         int scroll_steps = 0;
2682         int steps;
2684         switch (request) {
2685         case REQ_MOVE_FIRST_LINE:
2686                 steps = -view->lineno;
2687                 break;
2689         case REQ_MOVE_LAST_LINE:
2690                 steps = view->lines - view->lineno - 1;
2691                 break;
2693         case REQ_MOVE_PAGE_UP:
2694                 steps = view->height > view->lineno
2695                       ? -view->lineno : -view->height;
2696                 break;
2698         case REQ_MOVE_PAGE_DOWN:
2699                 steps = view->lineno + view->height >= view->lines
2700                       ? view->lines - view->lineno - 1 : view->height;
2701                 break;
2703         case REQ_MOVE_UP:
2704                 steps = -1;
2705                 break;
2707         case REQ_MOVE_DOWN:
2708                 steps = 1;
2709                 break;
2711         default:
2712                 die("request %d not handled in switch", request);
2713         }
2715         if (steps <= 0 && view->lineno == 0) {
2716                 report("Cannot move beyond the first line");
2717                 return;
2719         } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2720                 report("Cannot move beyond the last line");
2721                 return;
2722         }
2724         /* Move the current line */
2725         view->lineno += steps;
2726         assert(0 <= view->lineno && view->lineno < view->lines);
2728         /* Check whether the view needs to be scrolled */
2729         if (view->lineno < view->offset ||
2730             view->lineno >= view->offset + view->height) {
2731                 scroll_steps = steps;
2732                 if (steps < 0 && -steps > view->offset) {
2733                         scroll_steps = -view->offset;
2735                 } else if (steps > 0) {
2736                         if (view->lineno == view->lines - 1 &&
2737                             view->lines > view->height) {
2738                                 scroll_steps = view->lines - view->offset - 1;
2739                                 if (scroll_steps >= view->height)
2740                                         scroll_steps -= view->height - 1;
2741                         }
2742                 }
2743         }
2745         if (!view_is_displayed(view)) {
2746                 view->offset += scroll_steps;
2747                 assert(0 <= view->offset && view->offset < view->lines);
2748                 view->ops->select(view, &view->line[view->lineno]);
2749                 return;
2750         }
2752         /* Repaint the old "current" line if we be scrolling */
2753         if (ABS(steps) < view->height)
2754                 draw_view_line(view, view->lineno - steps - view->offset);
2756         if (scroll_steps) {
2757                 do_scroll_view(view, scroll_steps);
2758                 return;
2759         }
2761         /* Draw the current line */
2762         draw_view_line(view, view->lineno - view->offset);
2764         wnoutrefresh(view->win);
2765         report("");
2769 /*
2770  * Searching
2771  */
2773 static void search_view(struct view *view, enum request request);
2775 static bool
2776 grep_text(struct view *view, const char *text[])
2778         regmatch_t pmatch;
2779         size_t i;
2781         for (i = 0; text[i]; i++)
2782                 if (*text[i] &&
2783                     regexec(view->regex, text[i], 1, &pmatch, 0) != REG_NOMATCH)
2784                         return TRUE;
2785         return FALSE;
2788 static void
2789 select_view_line(struct view *view, unsigned long lineno)
2791         unsigned long old_lineno = view->lineno;
2792         unsigned long old_offset = view->offset;
2794         if (goto_view_line(view, view->offset, lineno)) {
2795                 if (view_is_displayed(view)) {
2796                         if (old_offset != view->offset) {
2797                                 redraw_view(view);
2798                         } else {
2799                                 draw_view_line(view, old_lineno - view->offset);
2800                                 draw_view_line(view, view->lineno - view->offset);
2801                                 wnoutrefresh(view->win);
2802                         }
2803                 } else {
2804                         view->ops->select(view, &view->line[view->lineno]);
2805                 }
2806         }
2809 static void
2810 find_next(struct view *view, enum request request)
2812         unsigned long lineno = view->lineno;
2813         int direction;
2815         if (!*view->grep) {
2816                 if (!*opt_search)
2817                         report("No previous search");
2818                 else
2819                         search_view(view, request);
2820                 return;
2821         }
2823         switch (request) {
2824         case REQ_SEARCH:
2825         case REQ_FIND_NEXT:
2826                 direction = 1;
2827                 break;
2829         case REQ_SEARCH_BACK:
2830         case REQ_FIND_PREV:
2831                 direction = -1;
2832                 break;
2834         default:
2835                 return;
2836         }
2838         if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
2839                 lineno += direction;
2841         /* Note, lineno is unsigned long so will wrap around in which case it
2842          * will become bigger than view->lines. */
2843         for (; lineno < view->lines; lineno += direction) {
2844                 if (view->ops->grep(view, &view->line[lineno])) {
2845                         select_view_line(view, lineno);
2846                         report("Line %ld matches '%s'", lineno + 1, view->grep);
2847                         return;
2848                 }
2849         }
2851         report("No match found for '%s'", view->grep);
2854 static void
2855 search_view(struct view *view, enum request request)
2857         int regex_err;
2859         if (view->regex) {
2860                 regfree(view->regex);
2861                 *view->grep = 0;
2862         } else {
2863                 view->regex = calloc(1, sizeof(*view->regex));
2864                 if (!view->regex)
2865                         return;
2866         }
2868         regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
2869         if (regex_err != 0) {
2870                 char buf[SIZEOF_STR] = "unknown error";
2872                 regerror(regex_err, view->regex, buf, sizeof(buf));
2873                 report("Search failed: %s", buf);
2874                 return;
2875         }
2877         string_copy(view->grep, opt_search);
2879         find_next(view, request);
2882 /*
2883  * Incremental updating
2884  */
2886 static void
2887 reset_view(struct view *view)
2889         int i;
2891         for (i = 0; i < view->lines; i++)
2892                 free(view->line[i].data);
2893         free(view->line);
2895         view->p_offset = view->offset;
2896         view->p_yoffset = view->yoffset;
2897         view->p_lineno = view->lineno;
2899         view->line = NULL;
2900         view->offset = 0;
2901         view->yoffset = 0;
2902         view->lines  = 0;
2903         view->lineno = 0;
2904         view->vid[0] = 0;
2905         view->update_secs = 0;
2908 static void
2909 free_argv(const char *argv[])
2911         int argc;
2913         for (argc = 0; argv[argc]; argc++)
2914                 free((void *) argv[argc]);
2917 static const char *
2918 format_arg(const char *name)
2920         static struct {
2921                 const char *name;
2922                 size_t namelen;
2923                 const char *value;
2924                 const char *value_if_empty;
2925         } vars[] = {
2926 #define FORMAT_VAR(name, value, value_if_empty) \
2927         { name, STRING_SIZE(name), value, value_if_empty }
2928                 FORMAT_VAR("%(directory)",      opt_path,       ""),
2929                 FORMAT_VAR("%(file)",           opt_file,       ""),
2930                 FORMAT_VAR("%(ref)",            opt_ref,        "HEAD"),
2931                 FORMAT_VAR("%(head)",           ref_head,       ""),
2932                 FORMAT_VAR("%(commit)",         ref_commit,     ""),
2933                 FORMAT_VAR("%(blob)",           ref_blob,       ""),
2934         };
2935         int i;
2937         for (i = 0; i < ARRAY_SIZE(vars); i++)
2938                 if (!strncmp(name, vars[i].name, vars[i].namelen))
2939                         return *vars[i].value ? vars[i].value : vars[i].value_if_empty;
2941         return NULL;
2943 static bool
2944 format_argv(const char *dst_argv[], const char *src_argv[], enum format_flags flags)
2946         char buf[SIZEOF_STR];
2947         int argc;
2948         bool noreplace = flags == FORMAT_NONE;
2950         free_argv(dst_argv);
2952         for (argc = 0; src_argv[argc]; argc++) {
2953                 const char *arg = src_argv[argc];
2954                 size_t bufpos = 0;
2956                 while (arg) {
2957                         char *next = strstr(arg, "%(");
2958                         int len = next - arg;
2959                         const char *value;
2961                         if (!next || noreplace) {
2962                                 if (flags == FORMAT_DASH && !strcmp(arg, "--"))
2963                                         noreplace = TRUE;
2964                                 len = strlen(arg);
2965                                 value = "";
2967                         } else {
2968                                 value = format_arg(next);
2970                                 if (!value) {
2971                                         report("Unknown replacement: `%s`", next);
2972                                         return FALSE;
2973                                 }
2974                         }
2976                         if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
2977                                 return FALSE;
2979                         arg = next && !noreplace ? strchr(next, ')') + 1 : NULL;
2980                 }
2982                 dst_argv[argc] = strdup(buf);
2983                 if (!dst_argv[argc])
2984                         break;
2985         }
2987         dst_argv[argc] = NULL;
2989         return src_argv[argc] == NULL;
2992 static bool
2993 restore_view_position(struct view *view)
2995         if (!view->p_restore || (view->pipe && view->lines <= view->p_lineno))
2996                 return FALSE;
2998         /* Changing the view position cancels the restoring. */
2999         /* FIXME: Changing back to the first line is not detected. */
3000         if (view->offset != 0 || view->lineno != 0) {
3001                 view->p_restore = FALSE;
3002                 return FALSE;
3003         }
3005         if (goto_view_line(view, view->p_offset, view->p_lineno) &&
3006             view_is_displayed(view))
3007                 werase(view->win);
3009         view->yoffset = view->p_yoffset;
3010         view->p_restore = FALSE;
3012         return TRUE;
3015 static void
3016 end_update(struct view *view, bool force)
3018         if (!view->pipe)
3019                 return;
3020         while (!view->ops->read(view, NULL))
3021                 if (!force)
3022                         return;
3023         set_nonblocking_input(FALSE);
3024         if (force)
3025                 kill_io(view->pipe);
3026         done_io(view->pipe);
3027         view->pipe = NULL;
3030 static void
3031 setup_update(struct view *view, const char *vid)
3033         set_nonblocking_input(TRUE);
3034         reset_view(view);
3035         string_copy_rev(view->vid, vid);
3036         view->pipe = &view->io;
3037         view->start_time = time(NULL);
3040 static bool
3041 prepare_update(struct view *view, const char *argv[], const char *dir,
3042                enum format_flags flags)
3044         if (view->pipe)
3045                 end_update(view, TRUE);
3046         return init_io_rd(&view->io, argv, dir, flags);
3049 static bool
3050 prepare_update_file(struct view *view, const char *name)
3052         if (view->pipe)
3053                 end_update(view, TRUE);
3054         return io_open(&view->io, "%s", name);
3057 static bool
3058 begin_update(struct view *view, bool refresh)
3060         if (view->pipe)
3061                 end_update(view, TRUE);
3063         if (!refresh) {
3064                 if (view->ops->prepare) {
3065                         if (!view->ops->prepare(view))
3066                                 return FALSE;
3067                 } else if (!init_io_rd(&view->io, view->ops->argv, NULL, FORMAT_ALL)) {
3068                         return FALSE;
3069                 }
3071                 /* Put the current ref_* value to the view title ref
3072                  * member. This is needed by the blob view. Most other
3073                  * views sets it automatically after loading because the
3074                  * first line is a commit line. */
3075                 string_copy_rev(view->ref, view->id);
3076         }
3078         if (!start_io(&view->io))
3079                 return FALSE;
3081         setup_update(view, view->id);
3083         return TRUE;
3086 static bool
3087 update_view(struct view *view)
3089         char out_buffer[BUFSIZ * 2];
3090         char *line;
3091         /* Clear the view and redraw everything since the tree sorting
3092          * might have rearranged things. */
3093         bool redraw = view->lines == 0;
3094         bool can_read = TRUE;
3096         if (!view->pipe)
3097                 return TRUE;
3099         if (!io_can_read(view->pipe)) {
3100                 if (view->lines == 0 && view_is_displayed(view)) {
3101                         time_t secs = time(NULL) - view->start_time;
3103                         if (secs > 1 && secs > view->update_secs) {
3104                                 if (view->update_secs == 0)
3105                                         redraw_view(view);
3106                                 update_view_title(view);
3107                                 view->update_secs = secs;
3108                         }
3109                 }
3110                 return TRUE;
3111         }
3113         for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
3114                 if (opt_iconv_in != ICONV_NONE) {
3115                         ICONV_CONST char *inbuf = line;
3116                         size_t inlen = strlen(line) + 1;
3118                         char *outbuf = out_buffer;
3119                         size_t outlen = sizeof(out_buffer);
3121                         size_t ret;
3123                         ret = iconv(opt_iconv_in, &inbuf, &inlen, &outbuf, &outlen);
3124                         if (ret != (size_t) -1)
3125                                 line = out_buffer;
3126                 }
3128                 if (!view->ops->read(view, line)) {
3129                         report("Allocation failure");
3130                         end_update(view, TRUE);
3131                         return FALSE;
3132                 }
3133         }
3135         {
3136                 unsigned long lines = view->lines;
3137                 int digits;
3139                 for (digits = 0; lines; digits++)
3140                         lines /= 10;
3142                 /* Keep the displayed view in sync with line number scaling. */
3143                 if (digits != view->digits) {
3144                         view->digits = digits;
3145                         if (opt_line_number || view == VIEW(REQ_VIEW_BLAME))
3146                                 redraw = TRUE;
3147                 }
3148         }
3150         if (io_error(view->pipe)) {
3151                 report("Failed to read: %s", io_strerror(view->pipe));
3152                 end_update(view, TRUE);
3154         } else if (io_eof(view->pipe)) {
3155                 report("");
3156                 end_update(view, FALSE);
3157         }
3159         if (restore_view_position(view))
3160                 redraw = TRUE;
3162         if (!view_is_displayed(view))
3163                 return TRUE;
3165         if (redraw)
3166                 redraw_view_from(view, 0);
3167         else
3168                 redraw_view_dirty(view);
3170         /* Update the title _after_ the redraw so that if the redraw picks up a
3171          * commit reference in view->ref it'll be available here. */
3172         update_view_title(view);
3173         return TRUE;
3176 DEFINE_ALLOCATOR(realloc_lines, struct line, 256)
3178 static struct line *
3179 add_line_data(struct view *view, void *data, enum line_type type)
3181         struct line *line;
3183         if (!realloc_lines(&view->line, view->lines, 1))
3184                 return NULL;
3186         line = &view->line[view->lines++];
3187         memset(line, 0, sizeof(*line));
3188         line->type = type;
3189         line->data = data;
3190         line->dirty = 1;
3192         return line;
3195 static struct line *
3196 add_line_text(struct view *view, const char *text, enum line_type type)
3198         char *data = text ? strdup(text) : NULL;
3200         return data ? add_line_data(view, data, type) : NULL;
3203 static struct line *
3204 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
3206         char buf[SIZEOF_STR];
3207         va_list args;
3209         va_start(args, fmt);
3210         if (vsnprintf(buf, sizeof(buf), fmt, args) >= sizeof(buf))
3211                 buf[0] = 0;
3212         va_end(args);
3214         return buf[0] ? add_line_text(view, buf, type) : NULL;
3217 /*
3218  * View opening
3219  */
3221 enum open_flags {
3222         OPEN_DEFAULT = 0,       /* Use default view switching. */
3223         OPEN_SPLIT = 1,         /* Split current view. */
3224         OPEN_RELOAD = 4,        /* Reload view even if it is the current. */
3225         OPEN_REFRESH = 16,      /* Refresh view using previous command. */
3226         OPEN_PREPARED = 32,     /* Open already prepared command. */
3227 };
3229 static void
3230 open_view(struct view *prev, enum request request, enum open_flags flags)
3232         bool split = !!(flags & OPEN_SPLIT);
3233         bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
3234         bool nomaximize = !!(flags & OPEN_REFRESH);
3235         struct view *view = VIEW(request);
3236         int nviews = displayed_views();
3237         struct view *base_view = display[0];
3239         if (view == prev && nviews == 1 && !reload) {
3240                 report("Already in %s view", view->name);
3241                 return;
3242         }
3244         if (view->git_dir && !opt_git_dir[0]) {
3245                 report("The %s view is disabled in pager view", view->name);
3246                 return;
3247         }
3249         if (split) {
3250                 display[1] = view;
3251                 current_view = 1;
3252         } else if (!nomaximize) {
3253                 /* Maximize the current view. */
3254                 memset(display, 0, sizeof(display));
3255                 current_view = 0;
3256                 display[current_view] = view;
3257         }
3259         /* No parent signals that this is the first loaded view. */
3260         if (prev && view != prev) {
3261                 view->parent = prev;
3262         }
3264         /* Resize the view when switching between split- and full-screen,
3265          * or when switching between two different full-screen views. */
3266         if (nviews != displayed_views() ||
3267             (nviews == 1 && base_view != display[0]))
3268                 resize_display();
3270         if (view->ops->open) {
3271                 if (view->pipe)
3272                         end_update(view, TRUE);
3273                 if (!view->ops->open(view)) {
3274                         report("Failed to load %s view", view->name);
3275                         return;
3276                 }
3277                 restore_view_position(view);
3279         } else if ((reload || strcmp(view->vid, view->id)) &&
3280                    !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
3281                 report("Failed to load %s view", view->name);
3282                 return;
3283         }
3285         if (split && prev->lineno - prev->offset >= prev->height) {
3286                 /* Take the title line into account. */
3287                 int lines = prev->lineno - prev->offset - prev->height + 1;
3289                 /* Scroll the view that was split if the current line is
3290                  * outside the new limited view. */
3291                 do_scroll_view(prev, lines);
3292         }
3294         if (prev && view != prev && split && view_is_displayed(prev)) {
3295                 /* "Blur" the previous view. */
3296                 update_view_title(prev);
3297         }
3299         if (view->pipe && view->lines == 0) {
3300                 /* Clear the old view and let the incremental updating refill
3301                  * the screen. */
3302                 werase(view->win);
3303                 view->p_restore = flags & (OPEN_RELOAD | OPEN_REFRESH);
3304                 report("");
3305         } else if (view_is_displayed(view)) {
3306                 redraw_view(view);
3307                 report("");
3308         }
3311 static void
3312 open_external_viewer(const char *argv[], const char *dir)
3314         def_prog_mode();           /* save current tty modes */
3315         endwin();                  /* restore original tty modes */
3316         run_io_fg(argv, dir);
3317         fprintf(stderr, "Press Enter to continue");
3318         getc(opt_tty);
3319         reset_prog_mode();
3320         redraw_display(TRUE);
3323 static void
3324 open_mergetool(const char *file)
3326         const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
3328         open_external_viewer(mergetool_argv, opt_cdup);
3331 static void
3332 open_editor(bool from_root, const char *file)
3334         const char *editor_argv[] = { "vi", file, NULL };
3335         const char *editor;
3337         editor = getenv("GIT_EDITOR");
3338         if (!editor && *opt_editor)
3339                 editor = opt_editor;
3340         if (!editor)
3341                 editor = getenv("VISUAL");
3342         if (!editor)
3343                 editor = getenv("EDITOR");
3344         if (!editor)
3345                 editor = "vi";
3347         editor_argv[0] = editor;
3348         open_external_viewer(editor_argv, from_root ? opt_cdup : NULL);
3351 static void
3352 open_run_request(enum request request)
3354         struct run_request *req = get_run_request(request);
3355         const char *argv[ARRAY_SIZE(req->argv)] = { NULL };
3357         if (!req) {
3358                 report("Unknown run request");
3359                 return;
3360         }
3362         if (format_argv(argv, req->argv, FORMAT_ALL))
3363                 open_external_viewer(argv, NULL);
3364         free_argv(argv);
3367 /*
3368  * User request switch noodle
3369  */
3371 static int
3372 view_driver(struct view *view, enum request request)
3374         int i;
3376         if (request == REQ_NONE)
3377                 return TRUE;
3379         if (request > REQ_NONE) {
3380                 open_run_request(request);
3381                 /* FIXME: When all views can refresh always do this. */
3382                 if (view == VIEW(REQ_VIEW_STATUS) ||
3383                     view == VIEW(REQ_VIEW_MAIN) ||
3384                     view == VIEW(REQ_VIEW_LOG) ||
3385                     view == VIEW(REQ_VIEW_BRANCH) ||
3386                     view == VIEW(REQ_VIEW_STAGE))
3387                         request = REQ_REFRESH;
3388                 else
3389                         return TRUE;
3390         }
3392         if (view && view->lines) {
3393                 request = view->ops->request(view, request, &view->line[view->lineno]);
3394                 if (request == REQ_NONE)
3395                         return TRUE;
3396         }
3398         switch (request) {
3399         case REQ_MOVE_UP:
3400         case REQ_MOVE_DOWN:
3401         case REQ_MOVE_PAGE_UP:
3402         case REQ_MOVE_PAGE_DOWN:
3403         case REQ_MOVE_FIRST_LINE:
3404         case REQ_MOVE_LAST_LINE:
3405                 move_view(view, request);
3406                 break;
3408         case REQ_SCROLL_LEFT:
3409         case REQ_SCROLL_RIGHT:
3410         case REQ_SCROLL_LINE_DOWN:
3411         case REQ_SCROLL_LINE_UP:
3412         case REQ_SCROLL_PAGE_DOWN:
3413         case REQ_SCROLL_PAGE_UP:
3414                 scroll_view(view, request);
3415                 break;
3417         case REQ_VIEW_BLAME:
3418                 if (!opt_file[0]) {
3419                         report("No file chosen, press %s to open tree view",
3420                                get_key(view->keymap, REQ_VIEW_TREE));
3421                         break;
3422                 }
3423                 open_view(view, request, OPEN_DEFAULT);
3424                 break;
3426         case REQ_VIEW_BLOB:
3427                 if (!ref_blob[0]) {
3428                         report("No file chosen, press %s to open tree view",
3429                                get_key(view->keymap, REQ_VIEW_TREE));
3430                         break;
3431                 }
3432                 open_view(view, request, OPEN_DEFAULT);
3433                 break;
3435         case REQ_VIEW_PAGER:
3436                 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3437                         report("No pager content, press %s to run command from prompt",
3438                                get_key(view->keymap, REQ_PROMPT));
3439                         break;
3440                 }
3441                 open_view(view, request, OPEN_DEFAULT);
3442                 break;
3444         case REQ_VIEW_STAGE:
3445                 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3446                         report("No stage content, press %s to open the status view and choose file",
3447                                get_key(view->keymap, REQ_VIEW_STATUS));
3448                         break;
3449                 }
3450                 open_view(view, request, OPEN_DEFAULT);
3451                 break;
3453         case REQ_VIEW_STATUS:
3454                 if (opt_is_inside_work_tree == FALSE) {
3455                         report("The status view requires a working tree");
3456                         break;
3457                 }
3458                 open_view(view, request, OPEN_DEFAULT);
3459                 break;
3461         case REQ_VIEW_MAIN:
3462         case REQ_VIEW_DIFF:
3463         case REQ_VIEW_LOG:
3464         case REQ_VIEW_TREE:
3465         case REQ_VIEW_HELP:
3466         case REQ_VIEW_BRANCH:
3467                 open_view(view, request, OPEN_DEFAULT);
3468                 break;
3470         case REQ_NEXT:
3471         case REQ_PREVIOUS:
3472                 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3474                 if ((view == VIEW(REQ_VIEW_DIFF) &&
3475                      view->parent == VIEW(REQ_VIEW_MAIN)) ||
3476                    (view == VIEW(REQ_VIEW_DIFF) &&
3477                      view->parent == VIEW(REQ_VIEW_BLAME)) ||
3478                    (view == VIEW(REQ_VIEW_STAGE) &&
3479                      view->parent == VIEW(REQ_VIEW_STATUS)) ||
3480                    (view == VIEW(REQ_VIEW_BLOB) &&
3481                      view->parent == VIEW(REQ_VIEW_TREE)) ||
3482                    (view == VIEW(REQ_VIEW_MAIN) &&
3483                      view->parent == VIEW(REQ_VIEW_BRANCH))) {
3484                         int line;
3486                         view = view->parent;
3487                         line = view->lineno;
3488                         move_view(view, request);
3489                         if (view_is_displayed(view))
3490                                 update_view_title(view);
3491                         if (line != view->lineno)
3492                                 view->ops->request(view, REQ_ENTER,
3493                                                    &view->line[view->lineno]);
3495                 } else {
3496                         move_view(view, request);
3497                 }
3498                 break;
3500         case REQ_VIEW_NEXT:
3501         {
3502                 int nviews = displayed_views();
3503                 int next_view = (current_view + 1) % nviews;
3505                 if (next_view == current_view) {
3506                         report("Only one view is displayed");
3507                         break;
3508                 }
3510                 current_view = next_view;
3511                 /* Blur out the title of the previous view. */
3512                 update_view_title(view);
3513                 report("");
3514                 break;
3515         }
3516         case REQ_REFRESH:
3517                 report("Refreshing is not yet supported for the %s view", view->name);
3518                 break;
3520         case REQ_MAXIMIZE:
3521                 if (displayed_views() == 2)
3522                         maximize_view(view);
3523                 break;
3525         case REQ_OPTIONS:
3526                 open_option_menu();
3527                 break;
3529         case REQ_TOGGLE_LINENO:
3530                 toggle_view_option(&opt_line_number, "line numbers");
3531                 break;
3533         case REQ_TOGGLE_DATE:
3534                 toggle_date();
3535                 break;
3537         case REQ_TOGGLE_AUTHOR:
3538                 toggle_view_option(&opt_author, "author display");
3539                 break;
3541         case REQ_TOGGLE_REV_GRAPH:
3542                 toggle_view_option(&opt_rev_graph, "revision graph display");
3543                 break;
3545         case REQ_TOGGLE_REFS:
3546                 toggle_view_option(&opt_show_refs, "reference display");
3547                 break;
3549         case REQ_TOGGLE_SORT_FIELD:
3550         case REQ_TOGGLE_SORT_ORDER:
3551                 report("Sorting is not yet supported for the %s view", view->name);
3552                 break;
3554         case REQ_SEARCH:
3555         case REQ_SEARCH_BACK:
3556                 search_view(view, request);
3557                 break;
3559         case REQ_FIND_NEXT:
3560         case REQ_FIND_PREV:
3561                 find_next(view, request);
3562                 break;
3564         case REQ_STOP_LOADING:
3565                 for (i = 0; i < ARRAY_SIZE(views); i++) {
3566                         view = &views[i];
3567                         if (view->pipe)
3568                                 report("Stopped loading the %s view", view->name),
3569                         end_update(view, TRUE);
3570                 }
3571                 break;
3573         case REQ_SHOW_VERSION:
3574                 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3575                 return TRUE;
3577         case REQ_SCREEN_REDRAW:
3578                 redraw_display(TRUE);
3579                 break;
3581         case REQ_EDIT:
3582                 report("Nothing to edit");
3583                 break;
3585         case REQ_ENTER:
3586                 report("Nothing to enter");
3587                 break;
3589         case REQ_VIEW_CLOSE:
3590                 /* XXX: Mark closed views by letting view->parent point to the
3591                  * view itself. Parents to closed view should never be
3592                  * followed. */
3593                 if (view->parent &&
3594                     view->parent->parent != view->parent) {
3595                         maximize_view(view->parent);
3596                         view->parent = view;
3597                         break;
3598                 }
3599                 /* Fall-through */
3600         case REQ_QUIT:
3601                 return FALSE;
3603         default:
3604                 report("Unknown key, press %s for help",
3605                        get_key(view->keymap, REQ_VIEW_HELP));
3606                 return TRUE;
3607         }
3609         return TRUE;
3613 /*
3614  * View backend utilities
3615  */
3617 enum sort_field {
3618         ORDERBY_NAME,
3619         ORDERBY_DATE,
3620         ORDERBY_AUTHOR,
3621 };
3623 struct sort_state {
3624         const enum sort_field *fields;
3625         size_t size, current;
3626         bool reverse;
3627 };
3629 #define SORT_STATE(fields) { fields, ARRAY_SIZE(fields), 0 }
3630 #define get_sort_field(state) ((state).fields[(state).current])
3631 #define sort_order(state, result) ((state).reverse ? -(result) : (result))
3633 static void
3634 sort_view(struct view *view, enum request request, struct sort_state *state,
3635           int (*compare)(const void *, const void *))
3637         switch (request) {
3638         case REQ_TOGGLE_SORT_FIELD:
3639                 state->current = (state->current + 1) % state->size;
3640                 break;
3642         case REQ_TOGGLE_SORT_ORDER:
3643                 state->reverse = !state->reverse;
3644                 break;
3645         default:
3646                 die("Not a sort request");
3647         }
3649         qsort(view->line, view->lines, sizeof(*view->line), compare);
3650         redraw_view(view);
3653 DEFINE_ALLOCATOR(realloc_authors, const char *, 256)
3655 /* Small author cache to reduce memory consumption. It uses binary
3656  * search to lookup or find place to position new entries. No entries
3657  * are ever freed. */
3658 static const char *
3659 get_author(const char *name)
3661         static const char **authors;
3662         static size_t authors_size;
3663         int from = 0, to = authors_size - 1;
3665         while (from <= to) {
3666                 size_t pos = (to + from) / 2;
3667                 int cmp = strcmp(name, authors[pos]);
3669                 if (!cmp)
3670                         return authors[pos];
3672                 if (cmp < 0)
3673                         to = pos - 1;
3674                 else
3675                         from = pos + 1;
3676         }
3678         if (!realloc_authors(&authors, authors_size, 1))
3679                 return NULL;
3680         name = strdup(name);
3681         if (!name)
3682                 return NULL;
3684         memmove(authors + from + 1, authors + from, (authors_size - from) * sizeof(*authors));
3685         authors[from] = name;
3686         authors_size++;
3688         return name;
3691 static void
3692 parse_timezone(time_t *time, const char *zone)
3694         long tz;
3696         tz  = ('0' - zone[1]) * 60 * 60 * 10;
3697         tz += ('0' - zone[2]) * 60 * 60;
3698         tz += ('0' - zone[3]) * 60;
3699         tz += ('0' - zone[4]);
3701         if (zone[0] == '-')
3702                 tz = -tz;
3704         *time -= tz;
3707 /* Parse author lines where the name may be empty:
3708  *      author  <email@address.tld> 1138474660 +0100
3709  */
3710 static void
3711 parse_author_line(char *ident, const char **author, time_t *time)
3713         char *nameend = strchr(ident, '<');
3714         char *emailend = strchr(ident, '>');
3716         if (nameend && emailend)
3717                 *nameend = *emailend = 0;
3718         ident = chomp_string(ident);
3719         if (!*ident) {
3720                 if (nameend)
3721                         ident = chomp_string(nameend + 1);
3722                 if (!*ident)
3723                         ident = "Unknown";
3724         }
3726         *author = get_author(ident);
3728         /* Parse epoch and timezone */
3729         if (emailend && emailend[1] == ' ') {
3730                 char *secs = emailend + 2;
3731                 char *zone = strchr(secs, ' ');
3733                 *time = (time_t) atol(secs);
3735                 if (zone && strlen(zone) == STRING_SIZE(" +0700"))
3736                         parse_timezone(time, zone + 1);
3737         }
3740 static bool
3741 open_commit_parent_menu(char buf[SIZEOF_STR], int *parents)
3743         char rev[SIZEOF_REV];
3744         const char *revlist_argv[] = {
3745                 "git", "log", "--no-color", "-1", "--pretty=format:%s", rev, NULL
3746         };
3747         struct menu_item *items;
3748         char text[SIZEOF_STR];
3749         bool ok = TRUE;
3750         int i;
3752         items = calloc(*parents + 1, sizeof(*items));
3753         if (!items)
3754                 return FALSE;
3756         for (i = 0; i < *parents; i++) {
3757                 string_copy_rev(rev, &buf[SIZEOF_REV * i]);
3758                 if (!run_io_buf(revlist_argv, text, sizeof(text)) ||
3759                     !(items[i].text = strdup(text))) {
3760                         ok = FALSE;
3761                         break;
3762                 }
3763         }
3765         if (ok) {
3766                 *parents = 0;
3767                 ok = prompt_menu("Select parent", items, parents);
3768         }
3769         for (i = 0; items[i].text; i++)
3770                 free((char *) items[i].text);
3771         free(items);
3772         return ok;
3775 static bool
3776 select_commit_parent(const char *id, char rev[SIZEOF_REV], const char *path)
3778         char buf[SIZEOF_STR * 4];
3779         const char *revlist_argv[] = {
3780                 "git", "log", "--no-color", "-1",
3781                         "--pretty=format:%P", id, "--", path, NULL
3782         };
3783         int parents;
3785         if (!run_io_buf(revlist_argv, buf, sizeof(buf)) ||
3786             (parents = strlen(buf) / 40) < 0) {
3787                 report("Failed to get parent information");
3788                 return FALSE;
3790         } else if (parents == 0) {
3791                 if (path)
3792                         report("Path '%s' does not exist in the parent", path);
3793                 else
3794                         report("The selected commit has no parents");
3795                 return FALSE;
3796         }
3798         if (parents > 1 && !open_commit_parent_menu(buf, &parents))
3799                 return FALSE;
3801         string_copy_rev(rev, &buf[41 * parents]);
3802         return TRUE;
3805 /*
3806  * Pager backend
3807  */
3809 static bool
3810 pager_draw(struct view *view, struct line *line, unsigned int lineno)
3812         char text[SIZEOF_STR];
3814         if (opt_line_number && draw_lineno(view, lineno))
3815                 return TRUE;
3817         string_expand(text, sizeof(text), line->data, opt_tab_size);
3818         draw_text(view, line->type, text, TRUE);
3819         return TRUE;
3822 static bool
3823 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
3825         const char *describe_argv[] = { "git", "describe", commit_id, NULL };
3826         char ref[SIZEOF_STR];
3828         if (!run_io_buf(describe_argv, ref, sizeof(ref)) || !*ref)
3829                 return TRUE;
3831         /* This is the only fatal call, since it can "corrupt" the buffer. */
3832         if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
3833                 return FALSE;
3835         return TRUE;
3838 static void
3839 add_pager_refs(struct view *view, struct line *line)
3841         char buf[SIZEOF_STR];
3842         char *commit_id = (char *)line->data + STRING_SIZE("commit ");
3843         struct ref_list *list;
3844         size_t bufpos = 0, i;
3845         const char *sep = "Refs: ";
3846         bool is_tag = FALSE;
3848         assert(line->type == LINE_COMMIT);
3850         list = get_ref_list(commit_id);
3851         if (!list) {
3852                 if (view == VIEW(REQ_VIEW_DIFF))
3853                         goto try_add_describe_ref;
3854                 return;
3855         }
3857         for (i = 0; i < list->size; i++) {
3858                 struct ref *ref = list->refs[i];
3859                 const char *fmt = ref->tag    ? "%s[%s]" :
3860                                   ref->remote ? "%s<%s>" : "%s%s";
3862                 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
3863                         return;
3864                 sep = ", ";
3865                 if (ref->tag)
3866                         is_tag = TRUE;
3867         }
3869         if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
3870 try_add_describe_ref:
3871                 /* Add <tag>-g<commit_id> "fake" reference. */
3872                 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
3873                         return;
3874         }
3876         if (bufpos == 0)
3877                 return;
3879         add_line_text(view, buf, LINE_PP_REFS);
3882 static bool
3883 pager_read(struct view *view, char *data)
3885         struct line *line;
3887         if (!data)
3888                 return TRUE;
3890         line = add_line_text(view, data, get_line_type(data));
3891         if (!line)
3892                 return FALSE;
3894         if (line->type == LINE_COMMIT &&
3895             (view == VIEW(REQ_VIEW_DIFF) ||
3896              view == VIEW(REQ_VIEW_LOG)))
3897                 add_pager_refs(view, line);
3899         return TRUE;
3902 static enum request
3903 pager_request(struct view *view, enum request request, struct line *line)
3905         int split = 0;
3907         if (request != REQ_ENTER)
3908                 return request;
3910         if (line->type == LINE_COMMIT &&
3911            (view == VIEW(REQ_VIEW_LOG) ||
3912             view == VIEW(REQ_VIEW_PAGER))) {
3913                 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
3914                 split = 1;
3915         }
3917         /* Always scroll the view even if it was split. That way
3918          * you can use Enter to scroll through the log view and
3919          * split open each commit diff. */
3920         scroll_view(view, REQ_SCROLL_LINE_DOWN);
3922         /* FIXME: A minor workaround. Scrolling the view will call report("")
3923          * but if we are scrolling a non-current view this won't properly
3924          * update the view title. */
3925         if (split)
3926                 update_view_title(view);
3928         return REQ_NONE;
3931 static bool
3932 pager_grep(struct view *view, struct line *line)
3934         const char *text[] = { line->data, NULL };
3936         return grep_text(view, text);
3939 static void
3940 pager_select(struct view *view, struct line *line)
3942         if (line->type == LINE_COMMIT) {
3943                 char *text = (char *)line->data + STRING_SIZE("commit ");
3945                 if (view != VIEW(REQ_VIEW_PAGER))
3946                         string_copy_rev(view->ref, text);
3947                 string_copy_rev(ref_commit, text);
3948         }
3951 static struct view_ops pager_ops = {
3952         "line",
3953         NULL,
3954         NULL,
3955         pager_read,
3956         pager_draw,
3957         pager_request,
3958         pager_grep,
3959         pager_select,
3960 };
3962 static const char *log_argv[SIZEOF_ARG] = {
3963         "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
3964 };
3966 static enum request
3967 log_request(struct view *view, enum request request, struct line *line)
3969         switch (request) {
3970         case REQ_REFRESH:
3971                 load_refs();
3972                 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
3973                 return REQ_NONE;
3974         default:
3975                 return pager_request(view, request, line);
3976         }
3979 static struct view_ops log_ops = {
3980         "line",
3981         log_argv,
3982         NULL,
3983         pager_read,
3984         pager_draw,
3985         log_request,
3986         pager_grep,
3987         pager_select,
3988 };
3990 static const char *diff_argv[SIZEOF_ARG] = {
3991         "git", "show", "--pretty=fuller", "--no-color", "--root",
3992                 "--patch-with-stat", "--find-copies-harder", "-C", "%(commit)", NULL
3993 };
3995 static struct view_ops diff_ops = {
3996         "line",
3997         diff_argv,
3998         NULL,
3999         pager_read,
4000         pager_draw,
4001         pager_request,
4002         pager_grep,
4003         pager_select,
4004 };
4006 /*
4007  * Help backend
4008  */
4010 static bool help_keymap_hidden[ARRAY_SIZE(keymap_table)];
4012 static bool
4013 help_open_keymap_title(struct view *view, enum keymap keymap)
4015         struct line *line;
4017         line = add_line_format(view, LINE_HELP_KEYMAP, "[%c] %s bindings",
4018                                help_keymap_hidden[keymap] ? '+' : '-',
4019                                enum_name(keymap_table[keymap]));
4020         if (line)
4021                 line->other = keymap;
4023         return help_keymap_hidden[keymap];
4026 static void
4027 help_open_keymap(struct view *view, enum keymap keymap)
4029         const char *group = NULL;
4030         char buf[SIZEOF_STR];
4031         size_t bufpos;
4032         bool add_title = TRUE;
4033         int i;
4035         for (i = 0; i < ARRAY_SIZE(req_info); i++) {
4036                 const char *key = NULL;
4038                 if (req_info[i].request == REQ_NONE)
4039                         continue;
4041                 if (!req_info[i].request) {
4042                         group = req_info[i].help;
4043                         continue;
4044                 }
4046                 key = get_keys(keymap, req_info[i].request, TRUE);
4047                 if (!key || !*key)
4048                         continue;
4050                 if (add_title && help_open_keymap_title(view, keymap))
4051                         return;
4052                 add_title = false;
4054                 if (group) {
4055                         add_line_text(view, group, LINE_HELP_GROUP);
4056                         group = NULL;
4057                 }
4059                 add_line_format(view, LINE_DEFAULT, "    %-25s %-20s %s", key,
4060                                 enum_name(req_info[i]), req_info[i].help);
4061         }
4063         group = "External commands:";
4065         for (i = 0; i < run_requests; i++) {
4066                 struct run_request *req = get_run_request(REQ_NONE + i + 1);
4067                 const char *key;
4068                 int argc;
4070                 if (!req || req->keymap != keymap)
4071                         continue;
4073                 key = get_key_name(req->key);
4074                 if (!*key)
4075                         key = "(no key defined)";
4077                 if (add_title && help_open_keymap_title(view, keymap))
4078                         return;
4079                 if (group) {
4080                         add_line_text(view, group, LINE_HELP_GROUP);
4081                         group = NULL;
4082                 }
4084                 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
4085                         if (!string_format_from(buf, &bufpos, "%s%s",
4086                                                 argc ? " " : "", req->argv[argc]))
4087                                 return;
4089                 add_line_format(view, LINE_DEFAULT, "    %-25s `%s`", key, buf);
4090         }
4093 static bool
4094 help_open(struct view *view)
4096         enum keymap keymap;
4098         reset_view(view);
4099         add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
4100         add_line_text(view, "", LINE_DEFAULT);
4102         for (keymap = 0; keymap < ARRAY_SIZE(keymap_table); keymap++)
4103                 help_open_keymap(view, keymap);
4105         return TRUE;
4108 static enum request
4109 help_request(struct view *view, enum request request, struct line *line)
4111         switch (request) {
4112         case REQ_ENTER:
4113                 if (line->type == LINE_HELP_KEYMAP) {
4114                         help_keymap_hidden[line->other] =
4115                                 !help_keymap_hidden[line->other];
4116                         view->p_restore = TRUE;
4117                         open_view(view, REQ_VIEW_HELP, OPEN_REFRESH);
4118                 }
4120                 return REQ_NONE;
4121         default:
4122                 return pager_request(view, request, line);
4123         }
4126 static struct view_ops help_ops = {
4127         "line",
4128         NULL,
4129         help_open,
4130         NULL,
4131         pager_draw,
4132         help_request,
4133         pager_grep,
4134         pager_select,
4135 };
4138 /*
4139  * Tree backend
4140  */
4142 struct tree_stack_entry {
4143         struct tree_stack_entry *prev;  /* Entry below this in the stack */
4144         unsigned long lineno;           /* Line number to restore */
4145         char *name;                     /* Position of name in opt_path */
4146 };
4148 /* The top of the path stack. */
4149 static struct tree_stack_entry *tree_stack = NULL;
4150 unsigned long tree_lineno = 0;
4152 static void
4153 pop_tree_stack_entry(void)
4155         struct tree_stack_entry *entry = tree_stack;
4157         tree_lineno = entry->lineno;
4158         entry->name[0] = 0;
4159         tree_stack = entry->prev;
4160         free(entry);
4163 static void
4164 push_tree_stack_entry(const char *name, unsigned long lineno)
4166         struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
4167         size_t pathlen = strlen(opt_path);
4169         if (!entry)
4170                 return;
4172         entry->prev = tree_stack;
4173         entry->name = opt_path + pathlen;
4174         tree_stack = entry;
4176         if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
4177                 pop_tree_stack_entry();
4178                 return;
4179         }
4181         /* Move the current line to the first tree entry. */
4182         tree_lineno = 1;
4183         entry->lineno = lineno;
4186 /* Parse output from git-ls-tree(1):
4187  *
4188  * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
4189  */
4191 #define SIZEOF_TREE_ATTR \
4192         STRING_SIZE("100644 blob f931e1d229c3e185caad4449bf5b66ed72462657\t")
4194 #define SIZEOF_TREE_MODE \
4195         STRING_SIZE("100644 ")
4197 #define TREE_ID_OFFSET \
4198         STRING_SIZE("100644 blob ")
4200 struct tree_entry {
4201         char id[SIZEOF_REV];
4202         mode_t mode;
4203         time_t time;                    /* Date from the author ident. */
4204         const char *author;             /* Author of the commit. */
4205         char name[1];
4206 };
4208 static const char *
4209 tree_path(const struct line *line)
4211         return ((struct tree_entry *) line->data)->name;
4214 static int
4215 tree_compare_entry(const struct line *line1, const struct line *line2)
4217         if (line1->type != line2->type)
4218                 return line1->type == LINE_TREE_DIR ? -1 : 1;
4219         return strcmp(tree_path(line1), tree_path(line2));
4222 static const enum sort_field tree_sort_fields[] = {
4223         ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
4224 };
4225 static struct sort_state tree_sort_state = SORT_STATE(tree_sort_fields);
4227 static int
4228 tree_compare(const void *l1, const void *l2)
4230         const struct line *line1 = (const struct line *) l1;
4231         const struct line *line2 = (const struct line *) l2;
4232         const struct tree_entry *entry1 = ((const struct line *) l1)->data;
4233         const struct tree_entry *entry2 = ((const struct line *) l2)->data;
4235         if (line1->type == LINE_TREE_HEAD)
4236                 return -1;
4237         if (line2->type == LINE_TREE_HEAD)
4238                 return 1;
4240         switch (get_sort_field(tree_sort_state)) {
4241         case ORDERBY_DATE:
4242                 return sort_order(tree_sort_state, entry1->time - entry2->time);
4244         case ORDERBY_AUTHOR:
4245                 return sort_order(tree_sort_state, strcmp(entry1->author, entry2->author));
4247         case ORDERBY_NAME:
4248         default:
4249                 return sort_order(tree_sort_state, tree_compare_entry(line1, line2));
4250         }
4254 static struct line *
4255 tree_entry(struct view *view, enum line_type type, const char *path,
4256            const char *mode, const char *id)
4258         struct tree_entry *entry = calloc(1, sizeof(*entry) + strlen(path));
4259         struct line *line = entry ? add_line_data(view, entry, type) : NULL;
4261         if (!entry || !line) {
4262                 free(entry);
4263                 return NULL;
4264         }
4266         strncpy(entry->name, path, strlen(path));
4267         if (mode)
4268                 entry->mode = strtoul(mode, NULL, 8);
4269         if (id)
4270                 string_copy_rev(entry->id, id);
4272         return line;
4275 static bool
4276 tree_read_date(struct view *view, char *text, bool *read_date)
4278         static const char *author_name;
4279         static time_t author_time;
4281         if (!text && *read_date) {
4282                 *read_date = FALSE;
4283                 return TRUE;
4285         } else if (!text) {
4286                 char *path = *opt_path ? opt_path : ".";
4287                 /* Find next entry to process */
4288                 const char *log_file[] = {
4289                         "git", "log", "--no-color", "--pretty=raw",
4290                                 "--cc", "--raw", view->id, "--", path, NULL
4291                 };
4292                 struct io io = {};
4294                 if (!view->lines) {
4295                         tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL);
4296                         report("Tree is empty");
4297                         return TRUE;
4298                 }
4300                 if (!run_io_rd(&io, log_file, opt_cdup, FORMAT_NONE)) {
4301                         report("Failed to load tree data");
4302                         return TRUE;
4303                 }
4305                 done_io(view->pipe);
4306                 view->io = io;
4307                 *read_date = TRUE;
4308                 return FALSE;
4310         } else if (*text == 'a' && get_line_type(text) == LINE_AUTHOR) {
4311                 parse_author_line(text + STRING_SIZE("author "),
4312                                   &author_name, &author_time);
4314         } else if (*text == ':') {
4315                 char *pos;
4316                 size_t annotated = 1;
4317                 size_t i;
4319                 pos = strchr(text, '\t');
4320                 if (!pos)
4321                         return TRUE;
4322                 text = pos + 1;
4323                 if (*opt_path && !strncmp(text, opt_path, strlen(opt_path)))
4324                         text += strlen(opt_path);
4325                 pos = strchr(text, '/');
4326                 if (pos)
4327                         *pos = 0;
4329                 for (i = 1; i < view->lines; i++) {
4330                         struct line *line = &view->line[i];
4331                         struct tree_entry *entry = line->data;
4333                         annotated += !!entry->author;
4334                         if (entry->author || strcmp(entry->name, text))
4335                                 continue;
4337                         entry->author = author_name;
4338                         entry->time = author_time;
4339                         line->dirty = 1;
4340                         break;
4341                 }
4343                 if (annotated == view->lines)
4344                         kill_io(view->pipe);
4345         }
4346         return TRUE;
4349 static bool
4350 tree_read(struct view *view, char *text)
4352         static bool read_date = FALSE;
4353         struct tree_entry *data;
4354         struct line *entry, *line;
4355         enum line_type type;
4356         size_t textlen = text ? strlen(text) : 0;
4357         char *path = text + SIZEOF_TREE_ATTR;
4359         if (read_date || !text)
4360                 return tree_read_date(view, text, &read_date);
4362         if (textlen <= SIZEOF_TREE_ATTR)
4363                 return FALSE;
4364         if (view->lines == 0 &&
4365             !tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL))
4366                 return FALSE;
4368         /* Strip the path part ... */
4369         if (*opt_path) {
4370                 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
4371                 size_t striplen = strlen(opt_path);
4373                 if (pathlen > striplen)
4374                         memmove(path, path + striplen,
4375                                 pathlen - striplen + 1);
4377                 /* Insert "link" to parent directory. */
4378                 if (view->lines == 1 &&
4379                     !tree_entry(view, LINE_TREE_DIR, "..", "040000", view->ref))
4380                         return FALSE;
4381         }
4383         type = text[SIZEOF_TREE_MODE] == 't' ? LINE_TREE_DIR : LINE_TREE_FILE;
4384         entry = tree_entry(view, type, path, text, text + TREE_ID_OFFSET);
4385         if (!entry)
4386                 return FALSE;
4387         data = entry->data;
4389         /* Skip "Directory ..." and ".." line. */
4390         for (line = &view->line[1 + !!*opt_path]; line < entry; line++) {
4391                 if (tree_compare_entry(line, entry) <= 0)
4392                         continue;
4394                 memmove(line + 1, line, (entry - line) * sizeof(*entry));
4396                 line->data = data;
4397                 line->type = type;
4398                 for (; line <= entry; line++)
4399                         line->dirty = line->cleareol = 1;
4400                 return TRUE;
4401         }
4403         if (tree_lineno > view->lineno) {
4404                 view->lineno = tree_lineno;
4405                 tree_lineno = 0;
4406         }
4408         return TRUE;
4411 static bool
4412 tree_draw(struct view *view, struct line *line, unsigned int lineno)
4414         struct tree_entry *entry = line->data;
4416         if (line->type == LINE_TREE_HEAD) {
4417                 if (draw_text(view, line->type, "Directory path /", TRUE))
4418                         return TRUE;
4419         } else {
4420                 if (draw_mode(view, entry->mode))
4421                         return TRUE;
4423                 if (opt_author && draw_author(view, entry->author))
4424                         return TRUE;
4426                 if (opt_date && draw_date(view, entry->author ? &entry->time : NULL))
4427                         return TRUE;
4428         }
4429         if (draw_text(view, line->type, entry->name, TRUE))
4430                 return TRUE;
4431         return TRUE;
4434 static void
4435 open_blob_editor()
4437         char file[SIZEOF_STR] = "/tmp/tigblob.XXXXXX";
4438         int fd = mkstemp(file);
4440         if (fd == -1)
4441                 report("Failed to create temporary file");
4442         else if (!run_io_append(blob_ops.argv, FORMAT_ALL, fd))
4443                 report("Failed to save blob data to file");
4444         else
4445                 open_editor(FALSE, file);
4446         if (fd != -1)
4447                 unlink(file);
4450 static enum request
4451 tree_request(struct view *view, enum request request, struct line *line)
4453         enum open_flags flags;
4455         switch (request) {
4456         case REQ_VIEW_BLAME:
4457                 if (line->type != LINE_TREE_FILE) {
4458                         report("Blame only supported for files");
4459                         return REQ_NONE;
4460                 }
4462                 string_copy(opt_ref, view->vid);
4463                 return request;
4465         case REQ_EDIT:
4466                 if (line->type != LINE_TREE_FILE) {
4467                         report("Edit only supported for files");
4468                 } else if (!is_head_commit(view->vid)) {
4469                         open_blob_editor();
4470                 } else {
4471                         open_editor(TRUE, opt_file);
4472                 }
4473                 return REQ_NONE;
4475         case REQ_TOGGLE_SORT_FIELD:
4476         case REQ_TOGGLE_SORT_ORDER:
4477                 sort_view(view, request, &tree_sort_state, tree_compare);
4478                 return REQ_NONE;
4480         case REQ_PARENT:
4481                 if (!*opt_path) {
4482                         /* quit view if at top of tree */
4483                         return REQ_VIEW_CLOSE;
4484                 }
4485                 /* fake 'cd  ..' */
4486                 line = &view->line[1];
4487                 break;
4489         case REQ_ENTER:
4490                 break;
4492         default:
4493                 return request;
4494         }
4496         /* Cleanup the stack if the tree view is at a different tree. */
4497         while (!*opt_path && tree_stack)
4498                 pop_tree_stack_entry();
4500         switch (line->type) {
4501         case LINE_TREE_DIR:
4502                 /* Depending on whether it is a subdirectory or parent link
4503                  * mangle the path buffer. */
4504                 if (line == &view->line[1] && *opt_path) {
4505                         pop_tree_stack_entry();
4507                 } else {
4508                         const char *basename = tree_path(line);
4510                         push_tree_stack_entry(basename, view->lineno);
4511                 }
4513                 /* Trees and subtrees share the same ID, so they are not not
4514                  * unique like blobs. */
4515                 flags = OPEN_RELOAD;
4516                 request = REQ_VIEW_TREE;
4517                 break;
4519         case LINE_TREE_FILE:
4520                 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4521                 request = REQ_VIEW_BLOB;
4522                 break;
4524         default:
4525                 return REQ_NONE;
4526         }
4528         open_view(view, request, flags);
4529         if (request == REQ_VIEW_TREE)
4530                 view->lineno = tree_lineno;
4532         return REQ_NONE;
4535 static bool
4536 tree_grep(struct view *view, struct line *line)
4538         struct tree_entry *entry = line->data;
4539         const char *text[] = {
4540                 entry->name,
4541                 opt_author ? entry->author : "",
4542                 opt_date ? mkdate(&entry->time) : "",
4543                 NULL
4544         };
4546         return grep_text(view, text);
4549 static void
4550 tree_select(struct view *view, struct line *line)
4552         struct tree_entry *entry = line->data;
4554         if (line->type == LINE_TREE_FILE) {
4555                 string_copy_rev(ref_blob, entry->id);
4556                 string_format(opt_file, "%s%s", opt_path, tree_path(line));
4558         } else if (line->type != LINE_TREE_DIR) {
4559                 return;
4560         }
4562         string_copy_rev(view->ref, entry->id);
4565 static bool
4566 tree_prepare(struct view *view)
4568         if (view->lines == 0 && opt_prefix[0]) {
4569                 char *pos = opt_prefix;
4571                 while (pos && *pos) {
4572                         char *end = strchr(pos, '/');
4574                         if (end)
4575                                 *end = 0;
4576                         push_tree_stack_entry(pos, 0);
4577                         pos = end;
4578                         if (end) {
4579                                 *end = '/';
4580                                 pos++;
4581                         }
4582                 }
4584         } else if (strcmp(view->vid, view->id)) {
4585                 opt_path[0] = 0;
4586         }
4588         return init_io_rd(&view->io, view->ops->argv, opt_cdup, FORMAT_ALL);
4591 static const char *tree_argv[SIZEOF_ARG] = {
4592         "git", "ls-tree", "%(commit)", "%(directory)", NULL
4593 };
4595 static struct view_ops tree_ops = {
4596         "file",
4597         tree_argv,
4598         NULL,
4599         tree_read,
4600         tree_draw,
4601         tree_request,
4602         tree_grep,
4603         tree_select,
4604         tree_prepare,
4605 };
4607 static bool
4608 blob_read(struct view *view, char *line)
4610         if (!line)
4611                 return TRUE;
4612         return add_line_text(view, line, LINE_DEFAULT) != NULL;
4615 static enum request
4616 blob_request(struct view *view, enum request request, struct line *line)
4618         switch (request) {
4619         case REQ_EDIT:
4620                 open_blob_editor();
4621                 return REQ_NONE;
4622         default:
4623                 return pager_request(view, request, line);
4624         }
4627 static const char *blob_argv[SIZEOF_ARG] = {
4628         "git", "cat-file", "blob", "%(blob)", NULL
4629 };
4631 static struct view_ops blob_ops = {
4632         "line",
4633         blob_argv,
4634         NULL,
4635         blob_read,
4636         pager_draw,
4637         blob_request,
4638         pager_grep,
4639         pager_select,
4640 };
4642 /*
4643  * Blame backend
4644  *
4645  * Loading the blame view is a two phase job:
4646  *
4647  *  1. File content is read either using opt_file from the
4648  *     filesystem or using git-cat-file.
4649  *  2. Then blame information is incrementally added by
4650  *     reading output from git-blame.
4651  */
4653 static const char *blame_head_argv[] = {
4654         "git", "blame", "--incremental", "--", "%(file)", NULL
4655 };
4657 static const char *blame_ref_argv[] = {
4658         "git", "blame", "--incremental", "%(ref)", "--", "%(file)", NULL
4659 };
4661 static const char *blame_cat_file_argv[] = {
4662         "git", "cat-file", "blob", "%(ref):%(file)", NULL
4663 };
4665 struct blame_commit {
4666         char id[SIZEOF_REV];            /* SHA1 ID. */
4667         char title[128];                /* First line of the commit message. */
4668         const char *author;             /* Author of the commit. */
4669         time_t time;                    /* Date from the author ident. */
4670         char filename[128];             /* Name of file. */
4671         bool has_previous;              /* Was a "previous" line detected. */
4672 };
4674 struct blame {
4675         struct blame_commit *commit;
4676         unsigned long lineno;
4677         char text[1];
4678 };
4680 static bool
4681 blame_open(struct view *view)
4683         char path[SIZEOF_STR];
4685         if (!view->parent && *opt_prefix) {
4686                 string_copy(path, opt_file);
4687                 if (!string_format(opt_file, "%s%s", opt_prefix, path))
4688                         return FALSE;
4689         }
4691         if (*opt_ref || !io_open(&view->io, "%s%s", opt_cdup, opt_file)) {
4692                 if (!run_io_rd(&view->io, blame_cat_file_argv, opt_cdup, FORMAT_ALL))
4693                         return FALSE;
4694         }
4696         setup_update(view, opt_file);
4697         string_format(view->ref, "%s ...", opt_file);
4699         return TRUE;
4702 static struct blame_commit *
4703 get_blame_commit(struct view *view, const char *id)
4705         size_t i;
4707         for (i = 0; i < view->lines; i++) {
4708                 struct blame *blame = view->line[i].data;
4710                 if (!blame->commit)
4711                         continue;
4713                 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
4714                         return blame->commit;
4715         }
4717         {
4718                 struct blame_commit *commit = calloc(1, sizeof(*commit));
4720                 if (commit)
4721                         string_ncopy(commit->id, id, SIZEOF_REV);
4722                 return commit;
4723         }
4726 static bool
4727 parse_number(const char **posref, size_t *number, size_t min, size_t max)
4729         const char *pos = *posref;
4731         *posref = NULL;
4732         pos = strchr(pos + 1, ' ');
4733         if (!pos || !isdigit(pos[1]))
4734                 return FALSE;
4735         *number = atoi(pos + 1);
4736         if (*number < min || *number > max)
4737                 return FALSE;
4739         *posref = pos;
4740         return TRUE;
4743 static struct blame_commit *
4744 parse_blame_commit(struct view *view, const char *text, int *blamed)
4746         struct blame_commit *commit;
4747         struct blame *blame;
4748         const char *pos = text + SIZEOF_REV - 2;
4749         size_t orig_lineno = 0;
4750         size_t lineno;
4751         size_t group;
4753         if (strlen(text) <= SIZEOF_REV || pos[1] != ' ')
4754                 return NULL;
4756         if (!parse_number(&pos, &orig_lineno, 1, 9999999) ||
4757             !parse_number(&pos, &lineno, 1, view->lines) ||
4758             !parse_number(&pos, &group, 1, view->lines - lineno + 1))
4759                 return NULL;
4761         commit = get_blame_commit(view, text);
4762         if (!commit)
4763                 return NULL;
4765         *blamed += group;
4766         while (group--) {
4767                 struct line *line = &view->line[lineno + group - 1];
4769                 blame = line->data;
4770                 blame->commit = commit;
4771                 blame->lineno = orig_lineno + group - 1;
4772                 line->dirty = 1;
4773         }
4775         return commit;
4778 static bool
4779 blame_read_file(struct view *view, const char *line, bool *read_file)
4781         if (!line) {
4782                 const char **argv = *opt_ref ? blame_ref_argv : blame_head_argv;
4783                 struct io io = {};
4785                 if (view->lines == 0 && !view->parent)
4786                         die("No blame exist for %s", view->vid);
4788                 if (view->lines == 0 || !run_io_rd(&io, argv, opt_cdup, FORMAT_ALL)) {
4789                         report("Failed to load blame data");
4790                         return TRUE;
4791                 }
4793                 done_io(view->pipe);
4794                 view->io = io;
4795                 *read_file = FALSE;
4796                 return FALSE;
4798         } else {
4799                 size_t linelen = strlen(line);
4800                 struct blame *blame = malloc(sizeof(*blame) + linelen);
4802                 if (!blame)
4803                         return FALSE;
4805                 blame->commit = NULL;
4806                 strncpy(blame->text, line, linelen);
4807                 blame->text[linelen] = 0;
4808                 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
4809         }
4812 static bool
4813 match_blame_header(const char *name, char **line)
4815         size_t namelen = strlen(name);
4816         bool matched = !strncmp(name, *line, namelen);
4818         if (matched)
4819                 *line += namelen;
4821         return matched;
4824 static bool
4825 blame_read(struct view *view, char *line)
4827         static struct blame_commit *commit = NULL;
4828         static int blamed = 0;
4829         static bool read_file = TRUE;
4831         if (read_file)
4832                 return blame_read_file(view, line, &read_file);
4834         if (!line) {
4835                 /* Reset all! */
4836                 commit = NULL;
4837                 blamed = 0;
4838                 read_file = TRUE;
4839                 string_format(view->ref, "%s", view->vid);
4840                 if (view_is_displayed(view)) {
4841                         update_view_title(view);
4842                         redraw_view_from(view, 0);
4843                 }
4844                 return TRUE;
4845         }
4847         if (!commit) {
4848                 commit = parse_blame_commit(view, line, &blamed);
4849                 string_format(view->ref, "%s %2d%%", view->vid,
4850                               view->lines ? blamed * 100 / view->lines : 0);
4852         } else if (match_blame_header("author ", &line)) {
4853                 commit->author = get_author(line);
4855         } else if (match_blame_header("author-time ", &line)) {
4856                 commit->time = (time_t) atol(line);
4858         } else if (match_blame_header("author-tz ", &line)) {
4859                 parse_timezone(&commit->time, line);
4861         } else if (match_blame_header("summary ", &line)) {
4862                 string_ncopy(commit->title, line, strlen(line));
4864         } else if (match_blame_header("previous ", &line)) {
4865                 commit->has_previous = TRUE;
4867         } else if (match_blame_header("filename ", &line)) {
4868                 string_ncopy(commit->filename, line, strlen(line));
4869                 commit = NULL;
4870         }
4872         return TRUE;
4875 static bool
4876 blame_draw(struct view *view, struct line *line, unsigned int lineno)
4878         struct blame *blame = line->data;
4879         time_t *time = NULL;
4880         const char *id = NULL, *author = NULL;
4881         char text[SIZEOF_STR];
4883         if (blame->commit && *blame->commit->filename) {
4884                 id = blame->commit->id;
4885                 author = blame->commit->author;
4886                 time = &blame->commit->time;
4887         }
4889         if (opt_date && draw_date(view, time))
4890                 return TRUE;
4892         if (opt_author && draw_author(view, author))
4893                 return TRUE;
4895         if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
4896                 return TRUE;
4898         if (draw_lineno(view, lineno))
4899                 return TRUE;
4901         string_expand(text, sizeof(text), blame->text, opt_tab_size);
4902         draw_text(view, LINE_DEFAULT, text, TRUE);
4903         return TRUE;
4906 static bool
4907 check_blame_commit(struct blame *blame, bool check_null_id)
4909         if (!blame->commit)
4910                 report("Commit data not loaded yet");
4911         else if (check_null_id && !strcmp(blame->commit->id, NULL_ID))
4912                 report("No commit exist for the selected line");
4913         else
4914                 return TRUE;
4915         return FALSE;
4918 static void
4919 setup_blame_parent_line(struct view *view, struct blame *blame)
4921         const char *diff_tree_argv[] = {
4922                 "git", "diff-tree", "-U0", blame->commit->id,
4923                         "--", blame->commit->filename, NULL
4924         };
4925         struct io io = {};
4926         int parent_lineno = -1;
4927         int blamed_lineno = -1;
4928         char *line;
4930         if (!run_io(&io, diff_tree_argv, NULL, IO_RD))
4931                 return;
4933         while ((line = io_get(&io, '\n', TRUE))) {
4934                 if (*line == '@') {
4935                         char *pos = strchr(line, '+');
4937                         parent_lineno = atoi(line + 4);
4938                         if (pos)
4939                                 blamed_lineno = atoi(pos + 1);
4941                 } else if (*line == '+' && parent_lineno != -1) {
4942                         if (blame->lineno == blamed_lineno - 1 &&
4943                             !strcmp(blame->text, line + 1)) {
4944                                 view->lineno = parent_lineno ? parent_lineno - 1 : 0;
4945                                 break;
4946                         }
4947                         blamed_lineno++;
4948                 }
4949         }
4951         done_io(&io);
4954 static enum request
4955 blame_request(struct view *view, enum request request, struct line *line)
4957         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4958         struct blame *blame = line->data;
4960         switch (request) {
4961         case REQ_VIEW_BLAME:
4962                 if (check_blame_commit(blame, TRUE)) {
4963                         string_copy(opt_ref, blame->commit->id);
4964                         string_copy(opt_file, blame->commit->filename);
4965                         if (blame->lineno)
4966                                 view->lineno = blame->lineno;
4967                         open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4968                 }
4969                 break;
4971         case REQ_PARENT:
4972                 if (check_blame_commit(blame, TRUE) &&
4973                     select_commit_parent(blame->commit->id, opt_ref,
4974                                          blame->commit->filename)) {
4975                         string_copy(opt_file, blame->commit->filename);
4976                         setup_blame_parent_line(view, blame);
4977                         open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4978                 }
4979                 break;
4981         case REQ_ENTER:
4982                 if (!check_blame_commit(blame, FALSE))
4983                         break;
4985                 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
4986                     !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
4987                         break;
4989                 if (!strcmp(blame->commit->id, NULL_ID)) {
4990                         struct view *diff = VIEW(REQ_VIEW_DIFF);
4991                         const char *diff_index_argv[] = {
4992                                 "git", "diff-index", "--root", "--patch-with-stat",
4993                                         "-C", "-M", "HEAD", "--", view->vid, NULL
4994                         };
4996                         if (!blame->commit->has_previous) {
4997                                 diff_index_argv[1] = "diff";
4998                                 diff_index_argv[2] = "--no-color";
4999                                 diff_index_argv[6] = "--";
5000                                 diff_index_argv[7] = "/dev/null";
5001                         }
5003                         if (!prepare_update(diff, diff_index_argv, NULL, FORMAT_DASH)) {
5004                                 report("Failed to allocate diff command");
5005                                 break;
5006                         }
5007                         flags |= OPEN_PREPARED;
5008                 }
5010                 open_view(view, REQ_VIEW_DIFF, flags);
5011                 if (VIEW(REQ_VIEW_DIFF)->pipe && !strcmp(blame->commit->id, NULL_ID))
5012                         string_copy_rev(VIEW(REQ_VIEW_DIFF)->ref, NULL_ID);
5013                 break;
5015         default:
5016                 return request;
5017         }
5019         return REQ_NONE;
5022 static bool
5023 blame_grep(struct view *view, struct line *line)
5025         struct blame *blame = line->data;
5026         struct blame_commit *commit = blame->commit;
5027         const char *text[] = {
5028                 blame->text,
5029                 commit ? commit->title : "",
5030                 commit ? commit->id : "",
5031                 commit && opt_author ? commit->author : "",
5032                 commit && opt_date ? mkdate(&commit->time) : "",
5033                 NULL
5034         };
5036         return grep_text(view, text);
5039 static void
5040 blame_select(struct view *view, struct line *line)
5042         struct blame *blame = line->data;
5043         struct blame_commit *commit = blame->commit;
5045         if (!commit)
5046                 return;
5048         if (!strcmp(commit->id, NULL_ID))
5049                 string_ncopy(ref_commit, "HEAD", 4);
5050         else
5051                 string_copy_rev(ref_commit, commit->id);
5054 static struct view_ops blame_ops = {
5055         "line",
5056         NULL,
5057         blame_open,
5058         blame_read,
5059         blame_draw,
5060         blame_request,
5061         blame_grep,
5062         blame_select,
5063 };
5065 /*
5066  * Branch backend
5067  */
5069 struct branch {
5070         const char *author;             /* Author of the last commit. */
5071         time_t time;                    /* Date of the last activity. */
5072         const struct ref *ref;          /* Name and commit ID information. */
5073 };
5075 static const struct ref branch_all;
5077 static const enum sort_field branch_sort_fields[] = {
5078         ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
5079 };
5080 static struct sort_state branch_sort_state = SORT_STATE(branch_sort_fields);
5082 static int
5083 branch_compare(const void *l1, const void *l2)
5085         const struct branch *branch1 = ((const struct line *) l1)->data;
5086         const struct branch *branch2 = ((const struct line *) l2)->data;
5088         switch (get_sort_field(branch_sort_state)) {
5089         case ORDERBY_DATE:
5090                 return sort_order(branch_sort_state, branch1->time - branch2->time);
5092         case ORDERBY_AUTHOR:
5093                 return sort_order(branch_sort_state, strcmp(branch1->author, branch2->author));
5095         case ORDERBY_NAME:
5096         default:
5097                 return sort_order(branch_sort_state, strcmp(branch1->ref->name, branch2->ref->name));
5098         }
5101 static bool
5102 branch_draw(struct view *view, struct line *line, unsigned int lineno)
5104         struct branch *branch = line->data;
5105         enum line_type type = branch->ref->head ? LINE_MAIN_HEAD : LINE_DEFAULT;
5107         if (opt_date && draw_date(view, branch->author ? &branch->time : NULL))
5108                 return TRUE;
5110         if (opt_author && draw_author(view, branch->author))
5111                 return TRUE;
5113         draw_text(view, type, branch->ref == &branch_all ? "All branches" : branch->ref->name, TRUE);
5114         return TRUE;
5117 static enum request
5118 branch_request(struct view *view, enum request request, struct line *line)
5120         struct branch *branch = line->data;
5122         switch (request) {
5123         case REQ_REFRESH:
5124                 load_refs();
5125                 open_view(view, REQ_VIEW_BRANCH, OPEN_REFRESH);
5126                 return REQ_NONE;
5128         case REQ_TOGGLE_SORT_FIELD:
5129         case REQ_TOGGLE_SORT_ORDER:
5130                 sort_view(view, request, &branch_sort_state, branch_compare);
5131                 return REQ_NONE;
5133         case REQ_ENTER:
5134                 if (branch->ref == &branch_all) {
5135                         const char *all_branches_argv[] = {
5136                                 "git", "log", "--no-color", "--pretty=raw", "--parents",
5137                                       "--topo-order", "--all", NULL
5138                         };
5139                         struct view *main_view = VIEW(REQ_VIEW_MAIN);
5141                         if (!prepare_update(main_view, all_branches_argv, NULL, FORMAT_NONE)) {
5142                                 report("Failed to load view of all branches");
5143                                 return REQ_NONE;
5144                         }
5145                         open_view(view, REQ_VIEW_MAIN, OPEN_PREPARED | OPEN_SPLIT);
5146                 } else {
5147                         open_view(view, REQ_VIEW_MAIN, OPEN_SPLIT);
5148                 }
5149                 return REQ_NONE;
5151         default:
5152                 return request;
5153         }
5156 static bool
5157 branch_read(struct view *view, char *line)
5159         static char id[SIZEOF_REV];
5160         struct branch *reference;
5161         size_t i;
5163         if (!line)
5164                 return TRUE;
5166         switch (get_line_type(line)) {
5167         case LINE_COMMIT:
5168                 string_copy_rev(id, line + STRING_SIZE("commit "));
5169                 return TRUE;
5171         case LINE_AUTHOR:
5172                 for (i = 0, reference = NULL; i < view->lines; i++) {
5173                         struct branch *branch = view->line[i].data;
5175                         if (strcmp(branch->ref->id, id))
5176                                 continue;
5178                         view->line[i].dirty = TRUE;
5179                         if (reference) {
5180                                 branch->author = reference->author;
5181                                 branch->time = reference->time;
5182                                 continue;
5183                         }
5185                         parse_author_line(line + STRING_SIZE("author "),
5186                                           &branch->author, &branch->time);
5187                         reference = branch;
5188                 }
5189                 return TRUE;
5191         default:
5192                 return TRUE;
5193         }
5197 static bool
5198 branch_open_visitor(void *data, const struct ref *ref)
5200         struct view *view = data;
5201         struct branch *branch;
5203         if (ref->tag || ref->ltag || ref->remote)
5204                 return TRUE;
5206         branch = calloc(1, sizeof(*branch));
5207         if (!branch)
5208                 return FALSE;
5210         branch->ref = ref;
5211         return !!add_line_data(view, branch, LINE_DEFAULT);
5214 static bool
5215 branch_open(struct view *view)
5217         const char *branch_log[] = {
5218                 "git", "log", "--no-color", "--pretty=raw",
5219                         "--simplify-by-decoration", "--all", NULL
5220         };
5222         if (!run_io_rd(&view->io, branch_log, NULL, FORMAT_NONE)) {
5223                 report("Failed to load branch data");
5224                 return TRUE;
5225         }
5227         setup_update(view, view->id);
5228         branch_open_visitor(view, &branch_all);
5229         foreach_ref(branch_open_visitor, view);
5230         view->p_restore = TRUE;
5232         return TRUE;
5235 static bool
5236 branch_grep(struct view *view, struct line *line)
5238         struct branch *branch = line->data;
5239         const char *text[] = {
5240                 branch->ref->name,
5241                 branch->author,
5242                 NULL
5243         };
5245         return grep_text(view, text);
5248 static void
5249 branch_select(struct view *view, struct line *line)
5251         struct branch *branch = line->data;
5253         string_copy_rev(view->ref, branch->ref->id);
5254         string_copy_rev(ref_commit, branch->ref->id);
5255         string_copy_rev(ref_head, branch->ref->id);
5258 static struct view_ops branch_ops = {
5259         "branch",
5260         NULL,
5261         branch_open,
5262         branch_read,
5263         branch_draw,
5264         branch_request,
5265         branch_grep,
5266         branch_select,
5267 };
5269 /*
5270  * Status backend
5271  */
5273 struct status {
5274         char status;
5275         struct {
5276                 mode_t mode;
5277                 char rev[SIZEOF_REV];
5278                 char name[SIZEOF_STR];
5279         } old;
5280         struct {
5281                 mode_t mode;
5282                 char rev[SIZEOF_REV];
5283                 char name[SIZEOF_STR];
5284         } new;
5285 };
5287 static char status_onbranch[SIZEOF_STR];
5288 static struct status stage_status;
5289 static enum line_type stage_line_type;
5290 static size_t stage_chunks;
5291 static int *stage_chunk;
5293 DEFINE_ALLOCATOR(realloc_ints, int, 32)
5295 /* This should work even for the "On branch" line. */
5296 static inline bool
5297 status_has_none(struct view *view, struct line *line)
5299         return line < view->line + view->lines && !line[1].data;
5302 /* Get fields from the diff line:
5303  * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
5304  */
5305 static inline bool
5306 status_get_diff(struct status *file, const char *buf, size_t bufsize)
5308         const char *old_mode = buf +  1;
5309         const char *new_mode = buf +  8;
5310         const char *old_rev  = buf + 15;
5311         const char *new_rev  = buf + 56;
5312         const char *status   = buf + 97;
5314         if (bufsize < 98 ||
5315             old_mode[-1] != ':' ||
5316             new_mode[-1] != ' ' ||
5317             old_rev[-1]  != ' ' ||
5318             new_rev[-1]  != ' ' ||
5319             status[-1]   != ' ')
5320                 return FALSE;
5322         file->status = *status;
5324         string_copy_rev(file->old.rev, old_rev);
5325         string_copy_rev(file->new.rev, new_rev);
5327         file->old.mode = strtoul(old_mode, NULL, 8);
5328         file->new.mode = strtoul(new_mode, NULL, 8);
5330         file->old.name[0] = file->new.name[0] = 0;
5332         return TRUE;
5335 static bool
5336 status_run(struct view *view, const char *argv[], char status, enum line_type type)
5338         struct status *unmerged = NULL;
5339         char *buf;
5340         struct io io = {};
5342         if (!run_io(&io, argv, opt_cdup, IO_RD))
5343                 return FALSE;
5345         add_line_data(view, NULL, type);
5347         while ((buf = io_get(&io, 0, TRUE))) {
5348                 struct status *file = unmerged;
5350                 if (!file) {
5351                         file = calloc(1, sizeof(*file));
5352                         if (!file || !add_line_data(view, file, type))
5353                                 goto error_out;
5354                 }
5356                 /* Parse diff info part. */
5357                 if (status) {
5358                         file->status = status;
5359                         if (status == 'A')
5360                                 string_copy(file->old.rev, NULL_ID);
5362                 } else if (!file->status || file == unmerged) {
5363                         if (!status_get_diff(file, buf, strlen(buf)))
5364                                 goto error_out;
5366                         buf = io_get(&io, 0, TRUE);
5367                         if (!buf)
5368                                 break;
5370                         /* Collapse all modified entries that follow an
5371                          * associated unmerged entry. */
5372                         if (unmerged == file) {
5373                                 unmerged->status = 'U';
5374                                 unmerged = NULL;
5375                         } else if (file->status == 'U') {
5376                                 unmerged = file;
5377                         }
5378                 }
5380                 /* Grab the old name for rename/copy. */
5381                 if (!*file->old.name &&
5382                     (file->status == 'R' || file->status == 'C')) {
5383                         string_ncopy(file->old.name, buf, strlen(buf));
5385                         buf = io_get(&io, 0, TRUE);
5386                         if (!buf)
5387                                 break;
5388                 }
5390                 /* git-ls-files just delivers a NUL separated list of
5391                  * file names similar to the second half of the
5392                  * git-diff-* output. */
5393                 string_ncopy(file->new.name, buf, strlen(buf));
5394                 if (!*file->old.name)
5395                         string_copy(file->old.name, file->new.name);
5396                 file = NULL;
5397         }
5399         if (io_error(&io)) {
5400 error_out:
5401                 done_io(&io);
5402                 return FALSE;
5403         }
5405         if (!view->line[view->lines - 1].data)
5406                 add_line_data(view, NULL, LINE_STAT_NONE);
5408         done_io(&io);
5409         return TRUE;
5412 /* Don't show unmerged entries in the staged section. */
5413 static const char *status_diff_index_argv[] = {
5414         "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
5415                              "--cached", "-M", "HEAD", NULL
5416 };
5418 static const char *status_diff_files_argv[] = {
5419         "git", "diff-files", "-z", NULL
5420 };
5422 static const char *status_list_other_argv[] = {
5423         "git", "ls-files", "-z", "--others", "--exclude-standard", NULL
5424 };
5426 static const char *status_list_no_head_argv[] = {
5427         "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
5428 };
5430 static const char *update_index_argv[] = {
5431         "git", "update-index", "-q", "--unmerged", "--refresh", NULL
5432 };
5434 /* Restore the previous line number to stay in the context or select a
5435  * line with something that can be updated. */
5436 static void
5437 status_restore(struct view *view)
5439         if (view->p_lineno >= view->lines)
5440                 view->p_lineno = view->lines - 1;
5441         while (view->p_lineno < view->lines && !view->line[view->p_lineno].data)
5442                 view->p_lineno++;
5443         while (view->p_lineno > 0 && !view->line[view->p_lineno].data)
5444                 view->p_lineno--;
5446         /* If the above fails, always skip the "On branch" line. */
5447         if (view->p_lineno < view->lines)
5448                 view->lineno = view->p_lineno;
5449         else
5450                 view->lineno = 1;
5452         if (view->lineno < view->offset)
5453                 view->offset = view->lineno;
5454         else if (view->offset + view->height <= view->lineno)
5455                 view->offset = view->lineno - view->height + 1;
5457         view->p_restore = FALSE;
5460 static void
5461 status_update_onbranch(void)
5463         static const char *paths[][2] = {
5464                 { "rebase-apply/rebasing",      "Rebasing" },
5465                 { "rebase-apply/applying",      "Applying mailbox" },
5466                 { "rebase-apply/",              "Rebasing mailbox" },
5467                 { "rebase-merge/interactive",   "Interactive rebase" },
5468                 { "rebase-merge/",              "Rebase merge" },
5469                 { "MERGE_HEAD",                 "Merging" },
5470                 { "BISECT_LOG",                 "Bisecting" },
5471                 { "HEAD",                       "On branch" },
5472         };
5473         char buf[SIZEOF_STR];
5474         struct stat stat;
5475         int i;
5477         if (is_initial_commit()) {
5478                 string_copy(status_onbranch, "Initial commit");
5479                 return;
5480         }
5482         for (i = 0; i < ARRAY_SIZE(paths); i++) {
5483                 char *head = opt_head;
5485                 if (!string_format(buf, "%s/%s", opt_git_dir, paths[i][0]) ||
5486                     lstat(buf, &stat) < 0)
5487                         continue;
5489                 if (!*opt_head) {
5490                         struct io io = {};
5492                         if (io_open(&io, "%s/rebase-merge/head-name", opt_git_dir) &&
5493                             io_read_buf(&io, buf, sizeof(buf))) {
5494                                 head = buf;
5495                                 if (!prefixcmp(head, "refs/heads/"))
5496                                         head += STRING_SIZE("refs/heads/");
5497                         }
5498                 }
5500                 if (!string_format(status_onbranch, "%s %s", paths[i][1], head))
5501                         string_copy(status_onbranch, opt_head);
5502                 return;
5503         }
5505         string_copy(status_onbranch, "Not currently on any branch");
5508 /* First parse staged info using git-diff-index(1), then parse unstaged
5509  * info using git-diff-files(1), and finally untracked files using
5510  * git-ls-files(1). */
5511 static bool
5512 status_open(struct view *view)
5514         reset_view(view);
5516         add_line_data(view, NULL, LINE_STAT_HEAD);
5517         status_update_onbranch();
5519         run_io_bg(update_index_argv);
5521         if (is_initial_commit()) {
5522                 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
5523                         return FALSE;
5524         } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
5525                 return FALSE;
5526         }
5528         if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
5529             !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
5530                 return FALSE;
5532         /* Restore the exact position or use the specialized restore
5533          * mode? */
5534         if (!view->p_restore)
5535                 status_restore(view);
5536         return TRUE;
5539 static bool
5540 status_draw(struct view *view, struct line *line, unsigned int lineno)
5542         struct status *status = line->data;
5543         enum line_type type;
5544         const char *text;
5546         if (!status) {
5547                 switch (line->type) {
5548                 case LINE_STAT_STAGED:
5549                         type = LINE_STAT_SECTION;
5550                         text = "Changes to be committed:";
5551                         break;
5553                 case LINE_STAT_UNSTAGED:
5554                         type = LINE_STAT_SECTION;
5555                         text = "Changed but not updated:";
5556                         break;
5558                 case LINE_STAT_UNTRACKED:
5559                         type = LINE_STAT_SECTION;
5560                         text = "Untracked files:";
5561                         break;
5563                 case LINE_STAT_NONE:
5564                         type = LINE_DEFAULT;
5565                         text = "  (no files)";
5566                         break;
5568                 case LINE_STAT_HEAD:
5569                         type = LINE_STAT_HEAD;
5570                         text = status_onbranch;
5571                         break;
5573                 default:
5574                         return FALSE;
5575                 }
5576         } else {
5577                 static char buf[] = { '?', ' ', ' ', ' ', 0 };
5579                 buf[0] = status->status;
5580                 if (draw_text(view, line->type, buf, TRUE))
5581                         return TRUE;
5582                 type = LINE_DEFAULT;
5583                 text = status->new.name;
5584         }
5586         draw_text(view, type, text, TRUE);
5587         return TRUE;
5590 static enum request
5591 status_load_error(struct view *view, struct view *stage, const char *path)
5593         if (displayed_views() == 2 || display[current_view] != view)
5594                 maximize_view(view);
5595         report("Failed to load '%s': %s", path, io_strerror(&stage->io));
5596         return REQ_NONE;
5599 static enum request
5600 status_enter(struct view *view, struct line *line)
5602         struct status *status = line->data;
5603         const char *oldpath = status ? status->old.name : NULL;
5604         /* Diffs for unmerged entries are empty when passing the new
5605          * path, so leave it empty. */
5606         const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
5607         const char *info;
5608         enum open_flags split;
5609         struct view *stage = VIEW(REQ_VIEW_STAGE);
5611         if (line->type == LINE_STAT_NONE ||
5612             (!status && line[1].type == LINE_STAT_NONE)) {
5613                 report("No file to diff");
5614                 return REQ_NONE;
5615         }
5617         switch (line->type) {
5618         case LINE_STAT_STAGED:
5619                 if (is_initial_commit()) {
5620                         const char *no_head_diff_argv[] = {
5621                                 "git", "diff", "--no-color", "--patch-with-stat",
5622                                         "--", "/dev/null", newpath, NULL
5623                         };
5625                         if (!prepare_update(stage, no_head_diff_argv, opt_cdup, FORMAT_DASH))
5626                                 return status_load_error(view, stage, newpath);
5627                 } else {
5628                         const char *index_show_argv[] = {
5629                                 "git", "diff-index", "--root", "--patch-with-stat",
5630                                         "-C", "-M", "--cached", "HEAD", "--",
5631                                         oldpath, newpath, NULL
5632                         };
5634                         if (!prepare_update(stage, index_show_argv, opt_cdup, FORMAT_DASH))
5635                                 return status_load_error(view, stage, newpath);
5636                 }
5638                 if (status)
5639                         info = "Staged changes to %s";
5640                 else
5641                         info = "Staged changes";
5642                 break;
5644         case LINE_STAT_UNSTAGED:
5645         {
5646                 const char *files_show_argv[] = {
5647                         "git", "diff-files", "--root", "--patch-with-stat",
5648                                 "-C", "-M", "--", oldpath, newpath, NULL
5649                 };
5651                 if (!prepare_update(stage, files_show_argv, opt_cdup, FORMAT_DASH))
5652                         return status_load_error(view, stage, newpath);
5653                 if (status)
5654                         info = "Unstaged changes to %s";
5655                 else
5656                         info = "Unstaged changes";
5657                 break;
5658         }
5659         case LINE_STAT_UNTRACKED:
5660                 if (!newpath) {
5661                         report("No file to show");
5662                         return REQ_NONE;
5663                 }
5665                 if (!suffixcmp(status->new.name, -1, "/")) {
5666                         report("Cannot display a directory");
5667                         return REQ_NONE;
5668                 }
5670                 if (!prepare_update_file(stage, newpath))
5671                         return status_load_error(view, stage, newpath);
5672                 info = "Untracked file %s";
5673                 break;
5675         case LINE_STAT_HEAD:
5676                 return REQ_NONE;
5678         default:
5679                 die("line type %d not handled in switch", line->type);
5680         }
5682         split = view_is_displayed(view) ? OPEN_SPLIT : 0;
5683         open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split);
5684         if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
5685                 if (status) {
5686                         stage_status = *status;
5687                 } else {
5688                         memset(&stage_status, 0, sizeof(stage_status));
5689                 }
5691                 stage_line_type = line->type;
5692                 stage_chunks = 0;
5693                 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
5694         }
5696         return REQ_NONE;
5699 static bool
5700 status_exists(struct status *status, enum line_type type)
5702         struct view *view = VIEW(REQ_VIEW_STATUS);
5703         unsigned long lineno;
5705         for (lineno = 0; lineno < view->lines; lineno++) {
5706                 struct line *line = &view->line[lineno];
5707                 struct status *pos = line->data;
5709                 if (line->type != type)
5710                         continue;
5711                 if (!pos && (!status || !status->status) && line[1].data) {
5712                         select_view_line(view, lineno);
5713                         return TRUE;
5714                 }
5715                 if (pos && !strcmp(status->new.name, pos->new.name)) {
5716                         select_view_line(view, lineno);
5717                         return TRUE;
5718                 }
5719         }
5721         return FALSE;
5725 static bool
5726 status_update_prepare(struct io *io, enum line_type type)
5728         const char *staged_argv[] = {
5729                 "git", "update-index", "-z", "--index-info", NULL
5730         };
5731         const char *others_argv[] = {
5732                 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
5733         };
5735         switch (type) {
5736         case LINE_STAT_STAGED:
5737                 return run_io(io, staged_argv, opt_cdup, IO_WR);
5739         case LINE_STAT_UNSTAGED:
5740         case LINE_STAT_UNTRACKED:
5741                 return run_io(io, others_argv, opt_cdup, IO_WR);
5743         default:
5744                 die("line type %d not handled in switch", type);
5745                 return FALSE;
5746         }
5749 static bool
5750 status_update_write(struct io *io, struct status *status, enum line_type type)
5752         char buf[SIZEOF_STR];
5753         size_t bufsize = 0;
5755         switch (type) {
5756         case LINE_STAT_STAGED:
5757                 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
5758                                         status->old.mode,
5759                                         status->old.rev,
5760                                         status->old.name, 0))
5761                         return FALSE;
5762                 break;
5764         case LINE_STAT_UNSTAGED:
5765         case LINE_STAT_UNTRACKED:
5766                 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
5767                         return FALSE;
5768                 break;
5770         default:
5771                 die("line type %d not handled in switch", type);
5772         }
5774         return io_write(io, buf, bufsize);
5777 static bool
5778 status_update_file(struct status *status, enum line_type type)
5780         struct io io = {};
5781         bool result;
5783         if (!status_update_prepare(&io, type))
5784                 return FALSE;
5786         result = status_update_write(&io, status, type);
5787         return done_io(&io) && result;
5790 static bool
5791 status_update_files(struct view *view, struct line *line)
5793         char buf[sizeof(view->ref)];
5794         struct io io = {};
5795         bool result = TRUE;
5796         struct line *pos = view->line + view->lines;
5797         int files = 0;
5798         int file, done;
5799         int cursor_y = -1, cursor_x = -1;
5801         if (!status_update_prepare(&io, line->type))
5802                 return FALSE;
5804         for (pos = line; pos < view->line + view->lines && pos->data; pos++)
5805                 files++;
5807         string_copy(buf, view->ref);
5808         getsyx(cursor_y, cursor_x);
5809         for (file = 0, done = 5; result && file < files; line++, file++) {
5810                 int almost_done = file * 100 / files;
5812                 if (almost_done > done) {
5813                         done = almost_done;
5814                         string_format(view->ref, "updating file %u of %u (%d%% done)",
5815                                       file, files, done);
5816                         update_view_title(view);
5817                         setsyx(cursor_y, cursor_x);
5818                         doupdate();
5819                 }
5820                 result = status_update_write(&io, line->data, line->type);
5821         }
5822         string_copy(view->ref, buf);
5824         return done_io(&io) && result;
5827 static bool
5828 status_update(struct view *view)
5830         struct line *line = &view->line[view->lineno];
5832         assert(view->lines);
5834         if (!line->data) {
5835                 /* This should work even for the "On branch" line. */
5836                 if (line < view->line + view->lines && !line[1].data) {
5837                         report("Nothing to update");
5838                         return FALSE;
5839                 }
5841                 if (!status_update_files(view, line + 1)) {
5842                         report("Failed to update file status");
5843                         return FALSE;
5844                 }
5846         } else if (!status_update_file(line->data, line->type)) {
5847                 report("Failed to update file status");
5848                 return FALSE;
5849         }
5851         return TRUE;
5854 static bool
5855 status_revert(struct status *status, enum line_type type, bool has_none)
5857         if (!status || type != LINE_STAT_UNSTAGED) {
5858                 if (type == LINE_STAT_STAGED) {
5859                         report("Cannot revert changes to staged files");
5860                 } else if (type == LINE_STAT_UNTRACKED) {
5861                         report("Cannot revert changes to untracked files");
5862                 } else if (has_none) {
5863                         report("Nothing to revert");
5864                 } else {
5865                         report("Cannot revert changes to multiple files");
5866                 }
5868         } else if (prompt_yesno("Are you sure you want to revert changes?")) {
5869                 char mode[10] = "100644";
5870                 const char *reset_argv[] = {
5871                         "git", "update-index", "--cacheinfo", mode,
5872                                 status->old.rev, status->old.name, NULL
5873                 };
5874                 const char *checkout_argv[] = {
5875                         "git", "checkout", "--", status->old.name, NULL
5876                 };
5878                 if (status->status == 'U') {
5879                         string_format(mode, "%5o", status->old.mode);
5881                         if (status->old.mode == 0 && status->new.mode == 0) {
5882                                 reset_argv[2] = "--force-remove";
5883                                 reset_argv[3] = status->old.name;
5884                                 reset_argv[4] = NULL;
5885                         }
5887                         if (!run_io_fg(reset_argv, opt_cdup))
5888                                 return FALSE;
5889                         if (status->old.mode == 0 && status->new.mode == 0)
5890                                 return TRUE;
5891                 }
5893                 return run_io_fg(checkout_argv, opt_cdup);
5894         }
5896         return FALSE;
5899 static enum request
5900 status_request(struct view *view, enum request request, struct line *line)
5902         struct status *status = line->data;
5904         switch (request) {
5905         case REQ_STATUS_UPDATE:
5906                 if (!status_update(view))
5907                         return REQ_NONE;
5908                 break;
5910         case REQ_STATUS_REVERT:
5911                 if (!status_revert(status, line->type, status_has_none(view, line)))
5912                         return REQ_NONE;
5913                 break;
5915         case REQ_STATUS_MERGE:
5916                 if (!status || status->status != 'U') {
5917                         report("Merging only possible for files with unmerged status ('U').");
5918                         return REQ_NONE;
5919                 }
5920                 open_mergetool(status->new.name);
5921                 break;
5923         case REQ_EDIT:
5924                 if (!status)
5925                         return request;
5926                 if (status->status == 'D') {
5927                         report("File has been deleted.");
5928                         return REQ_NONE;
5929                 }
5931                 open_editor(status->status != '?', status->new.name);
5932                 break;
5934         case REQ_VIEW_BLAME:
5935                 if (status)
5936                         opt_ref[0] = 0;
5937                 return request;
5939         case REQ_ENTER:
5940                 /* After returning the status view has been split to
5941                  * show the stage view. No further reloading is
5942                  * necessary. */
5943                 return status_enter(view, line);
5945         case REQ_REFRESH:
5946                 /* Simply reload the view. */
5947                 break;
5949         default:
5950                 return request;
5951         }
5953         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
5955         return REQ_NONE;
5958 static void
5959 status_select(struct view *view, struct line *line)
5961         struct status *status = line->data;
5962         char file[SIZEOF_STR] = "all files";
5963         const char *text;
5964         const char *key;
5966         if (status && !string_format(file, "'%s'", status->new.name))
5967                 return;
5969         if (!status && line[1].type == LINE_STAT_NONE)
5970                 line++;
5972         switch (line->type) {
5973         case LINE_STAT_STAGED:
5974                 text = "Press %s to unstage %s for commit";
5975                 break;
5977         case LINE_STAT_UNSTAGED:
5978                 text = "Press %s to stage %s for commit";
5979                 break;
5981         case LINE_STAT_UNTRACKED:
5982                 text = "Press %s to stage %s for addition";
5983                 break;
5985         case LINE_STAT_HEAD:
5986         case LINE_STAT_NONE:
5987                 text = "Nothing to update";
5988                 break;
5990         default:
5991                 die("line type %d not handled in switch", line->type);
5992         }
5994         if (status && status->status == 'U') {
5995                 text = "Press %s to resolve conflict in %s";
5996                 key = get_key(KEYMAP_STATUS, REQ_STATUS_MERGE);
5998         } else {
5999                 key = get_key(KEYMAP_STATUS, REQ_STATUS_UPDATE);
6000         }
6002         string_format(view->ref, text, key, file);
6003         if (status)
6004                 string_copy(opt_file, status->new.name);
6007 static bool
6008 status_grep(struct view *view, struct line *line)
6010         struct status *status = line->data;
6012         if (status) {
6013                 const char buf[2] = { status->status, 0 };
6014                 const char *text[] = { status->new.name, buf, NULL };
6016                 return grep_text(view, text);
6017         }
6019         return FALSE;
6022 static struct view_ops status_ops = {
6023         "file",
6024         NULL,
6025         status_open,
6026         NULL,
6027         status_draw,
6028         status_request,
6029         status_grep,
6030         status_select,
6031 };
6034 static bool
6035 stage_diff_write(struct io *io, struct line *line, struct line *end)
6037         while (line < end) {
6038                 if (!io_write(io, line->data, strlen(line->data)) ||
6039                     !io_write(io, "\n", 1))
6040                         return FALSE;
6041                 line++;
6042                 if (line->type == LINE_DIFF_CHUNK ||
6043                     line->type == LINE_DIFF_HEADER)
6044                         break;
6045         }
6047         return TRUE;
6050 static struct line *
6051 stage_diff_find(struct view *view, struct line *line, enum line_type type)
6053         for (; view->line < line; line--)
6054                 if (line->type == type)
6055                         return line;
6057         return NULL;
6060 static bool
6061 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
6063         const char *apply_argv[SIZEOF_ARG] = {
6064                 "git", "apply", "--whitespace=nowarn", NULL
6065         };
6066         struct line *diff_hdr;
6067         struct io io = {};
6068         int argc = 3;
6070         diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
6071         if (!diff_hdr)
6072                 return FALSE;
6074         if (!revert)
6075                 apply_argv[argc++] = "--cached";
6076         if (revert || stage_line_type == LINE_STAT_STAGED)
6077                 apply_argv[argc++] = "-R";
6078         apply_argv[argc++] = "-";
6079         apply_argv[argc++] = NULL;
6080         if (!run_io(&io, apply_argv, opt_cdup, IO_WR))
6081                 return FALSE;
6083         if (!stage_diff_write(&io, diff_hdr, chunk) ||
6084             !stage_diff_write(&io, chunk, view->line + view->lines))
6085                 chunk = NULL;
6087         done_io(&io);
6088         run_io_bg(update_index_argv);
6090         return chunk ? TRUE : FALSE;
6093 static bool
6094 stage_update(struct view *view, struct line *line)
6096         struct line *chunk = NULL;
6098         if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
6099                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6101         if (chunk) {
6102                 if (!stage_apply_chunk(view, chunk, FALSE)) {
6103                         report("Failed to apply chunk");
6104                         return FALSE;
6105                 }
6107         } else if (!stage_status.status) {
6108                 view = VIEW(REQ_VIEW_STATUS);
6110                 for (line = view->line; line < view->line + view->lines; line++)
6111                         if (line->type == stage_line_type)
6112                                 break;
6114                 if (!status_update_files(view, line + 1)) {
6115                         report("Failed to update files");
6116                         return FALSE;
6117                 }
6119         } else if (!status_update_file(&stage_status, stage_line_type)) {
6120                 report("Failed to update file");
6121                 return FALSE;
6122         }
6124         return TRUE;
6127 static bool
6128 stage_revert(struct view *view, struct line *line)
6130         struct line *chunk = NULL;
6132         if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
6133                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6135         if (chunk) {
6136                 if (!prompt_yesno("Are you sure you want to revert changes?"))
6137                         return FALSE;
6139                 if (!stage_apply_chunk(view, chunk, TRUE)) {
6140                         report("Failed to revert chunk");
6141                         return FALSE;
6142                 }
6143                 return TRUE;
6145         } else {
6146                 return status_revert(stage_status.status ? &stage_status : NULL,
6147                                      stage_line_type, FALSE);
6148         }
6152 static void
6153 stage_next(struct view *view, struct line *line)
6155         int i;
6157         if (!stage_chunks) {
6158                 for (line = view->line; line < view->line + view->lines; line++) {
6159                         if (line->type != LINE_DIFF_CHUNK)
6160                                 continue;
6162                         if (!realloc_ints(&stage_chunk, stage_chunks, 1)) {
6163                                 report("Allocation failure");
6164                                 return;
6165                         }
6167                         stage_chunk[stage_chunks++] = line - view->line;
6168                 }
6169         }
6171         for (i = 0; i < stage_chunks; i++) {
6172                 if (stage_chunk[i] > view->lineno) {
6173                         do_scroll_view(view, stage_chunk[i] - view->lineno);
6174                         report("Chunk %d of %d", i + 1, stage_chunks);
6175                         return;
6176                 }
6177         }
6179         report("No next chunk found");
6182 static enum request
6183 stage_request(struct view *view, enum request request, struct line *line)
6185         switch (request) {
6186         case REQ_STATUS_UPDATE:
6187                 if (!stage_update(view, line))
6188                         return REQ_NONE;
6189                 break;
6191         case REQ_STATUS_REVERT:
6192                 if (!stage_revert(view, line))
6193                         return REQ_NONE;
6194                 break;
6196         case REQ_STAGE_NEXT:
6197                 if (stage_line_type == LINE_STAT_UNTRACKED) {
6198                         report("File is untracked; press %s to add",
6199                                get_key(KEYMAP_STAGE, REQ_STATUS_UPDATE));
6200                         return REQ_NONE;
6201                 }
6202                 stage_next(view, line);
6203                 return REQ_NONE;
6205         case REQ_EDIT:
6206                 if (!stage_status.new.name[0])
6207                         return request;
6208                 if (stage_status.status == 'D') {
6209                         report("File has been deleted.");
6210                         return REQ_NONE;
6211                 }
6213                 open_editor(stage_status.status != '?', stage_status.new.name);
6214                 break;
6216         case REQ_REFRESH:
6217                 /* Reload everything ... */
6218                 break;
6220         case REQ_VIEW_BLAME:
6221                 if (stage_status.new.name[0]) {
6222                         string_copy(opt_file, stage_status.new.name);
6223                         opt_ref[0] = 0;
6224                 }
6225                 return request;
6227         case REQ_ENTER:
6228                 return pager_request(view, request, line);
6230         default:
6231                 return request;
6232         }
6234         VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
6235         open_view(view, REQ_VIEW_STATUS, OPEN_REFRESH);
6237         /* Check whether the staged entry still exists, and close the
6238          * stage view if it doesn't. */
6239         if (!status_exists(&stage_status, stage_line_type)) {
6240                 status_restore(VIEW(REQ_VIEW_STATUS));
6241                 return REQ_VIEW_CLOSE;
6242         }
6244         if (stage_line_type == LINE_STAT_UNTRACKED) {
6245                 if (!suffixcmp(stage_status.new.name, -1, "/")) {
6246                         report("Cannot display a directory");
6247                         return REQ_NONE;
6248                 }
6250                 if (!prepare_update_file(view, stage_status.new.name)) {
6251                         report("Failed to open file: %s", strerror(errno));
6252                         return REQ_NONE;
6253                 }
6254         }
6255         open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
6257         return REQ_NONE;
6260 static struct view_ops stage_ops = {
6261         "line",
6262         NULL,
6263         NULL,
6264         pager_read,
6265         pager_draw,
6266         stage_request,
6267         pager_grep,
6268         pager_select,
6269 };
6272 /*
6273  * Revision graph
6274  */
6276 struct commit {
6277         char id[SIZEOF_REV];            /* SHA1 ID. */
6278         char title[128];                /* First line of the commit message. */
6279         const char *author;             /* Author of the commit. */
6280         time_t time;                    /* Date from the author ident. */
6281         struct ref_list *refs;          /* Repository references. */
6282         chtype graph[SIZEOF_REVGRAPH];  /* Ancestry chain graphics. */
6283         size_t graph_size;              /* The width of the graph array. */
6284         bool has_parents;               /* Rewritten --parents seen. */
6285 };
6287 /* Size of rev graph with no  "padding" columns */
6288 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
6290 struct rev_graph {
6291         struct rev_graph *prev, *next, *parents;
6292         char rev[SIZEOF_REVITEMS][SIZEOF_REV];
6293         size_t size;
6294         struct commit *commit;
6295         size_t pos;
6296         unsigned int boundary:1;
6297 };
6299 /* Parents of the commit being visualized. */
6300 static struct rev_graph graph_parents[4];
6302 /* The current stack of revisions on the graph. */
6303 static struct rev_graph graph_stacks[4] = {
6304         { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
6305         { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
6306         { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
6307         { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
6308 };
6310 static inline bool
6311 graph_parent_is_merge(struct rev_graph *graph)
6313         return graph->parents->size > 1;
6316 static inline void
6317 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
6319         struct commit *commit = graph->commit;
6321         if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
6322                 commit->graph[commit->graph_size++] = symbol;
6325 static void
6326 clear_rev_graph(struct rev_graph *graph)
6328         graph->boundary = 0;
6329         graph->size = graph->pos = 0;
6330         graph->commit = NULL;
6331         memset(graph->parents, 0, sizeof(*graph->parents));
6334 static void
6335 done_rev_graph(struct rev_graph *graph)
6337         if (graph_parent_is_merge(graph) &&
6338             graph->pos < graph->size - 1 &&
6339             graph->next->size == graph->size + graph->parents->size - 1) {
6340                 size_t i = graph->pos + graph->parents->size - 1;
6342                 graph->commit->graph_size = i * 2;
6343                 while (i < graph->next->size - 1) {
6344                         append_to_rev_graph(graph, ' ');
6345                         append_to_rev_graph(graph, '\\');
6346                         i++;
6347                 }
6348         }
6350         clear_rev_graph(graph);
6353 static void
6354 push_rev_graph(struct rev_graph *graph, const char *parent)
6356         int i;
6358         /* "Collapse" duplicate parents lines.
6359          *
6360          * FIXME: This needs to also update update the drawn graph but
6361          * for now it just serves as a method for pruning graph lines. */
6362         for (i = 0; i < graph->size; i++)
6363                 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
6364                         return;
6366         if (graph->size < SIZEOF_REVITEMS) {
6367                 string_copy_rev(graph->rev[graph->size++], parent);
6368         }
6371 static chtype
6372 get_rev_graph_symbol(struct rev_graph *graph)
6374         chtype symbol;
6376         if (graph->boundary)
6377                 symbol = REVGRAPH_BOUND;
6378         else if (graph->parents->size == 0)
6379                 symbol = REVGRAPH_INIT;
6380         else if (graph_parent_is_merge(graph))
6381                 symbol = REVGRAPH_MERGE;
6382         else if (graph->pos >= graph->size)
6383                 symbol = REVGRAPH_BRANCH;
6384         else
6385                 symbol = REVGRAPH_COMMIT;
6387         return symbol;
6390 static void
6391 draw_rev_graph(struct rev_graph *graph)
6393         struct rev_filler {
6394                 chtype separator, line;
6395         };
6396         enum { DEFAULT, RSHARP, RDIAG, LDIAG };
6397         static struct rev_filler fillers[] = {
6398                 { ' ',  '|' },
6399                 { '`',  '.' },
6400                 { '\'', ' ' },
6401                 { '/',  ' ' },
6402         };
6403         chtype symbol = get_rev_graph_symbol(graph);
6404         struct rev_filler *filler;
6405         size_t i;
6407         if (opt_line_graphics)
6408                 fillers[DEFAULT].line = line_graphics[LINE_GRAPHIC_VLINE];
6410         filler = &fillers[DEFAULT];
6412         for (i = 0; i < graph->pos; i++) {
6413                 append_to_rev_graph(graph, filler->line);
6414                 if (graph_parent_is_merge(graph->prev) &&
6415                     graph->prev->pos == i)
6416                         filler = &fillers[RSHARP];
6418                 append_to_rev_graph(graph, filler->separator);
6419         }
6421         /* Place the symbol for this revision. */
6422         append_to_rev_graph(graph, symbol);
6424         if (graph->prev->size > graph->size)
6425                 filler = &fillers[RDIAG];
6426         else
6427                 filler = &fillers[DEFAULT];
6429         i++;
6431         for (; i < graph->size; i++) {
6432                 append_to_rev_graph(graph, filler->separator);
6433                 append_to_rev_graph(graph, filler->line);
6434                 if (graph_parent_is_merge(graph->prev) &&
6435                     i < graph->prev->pos + graph->parents->size)
6436                         filler = &fillers[RSHARP];
6437                 if (graph->prev->size > graph->size)
6438                         filler = &fillers[LDIAG];
6439         }
6441         if (graph->prev->size > graph->size) {
6442                 append_to_rev_graph(graph, filler->separator);
6443                 if (filler->line != ' ')
6444                         append_to_rev_graph(graph, filler->line);
6445         }
6448 /* Prepare the next rev graph */
6449 static void
6450 prepare_rev_graph(struct rev_graph *graph)
6452         size_t i;
6454         /* First, traverse all lines of revisions up to the active one. */
6455         for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
6456                 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
6457                         break;
6459                 push_rev_graph(graph->next, graph->rev[graph->pos]);
6460         }
6462         /* Interleave the new revision parent(s). */
6463         for (i = 0; !graph->boundary && i < graph->parents->size; i++)
6464                 push_rev_graph(graph->next, graph->parents->rev[i]);
6466         /* Lastly, put any remaining revisions. */
6467         for (i = graph->pos + 1; i < graph->size; i++)
6468                 push_rev_graph(graph->next, graph->rev[i]);
6471 static void
6472 update_rev_graph(struct view *view, struct rev_graph *graph)
6474         /* If this is the finalizing update ... */
6475         if (graph->commit)
6476                 prepare_rev_graph(graph);
6478         /* Graph visualization needs a one rev look-ahead,
6479          * so the first update doesn't visualize anything. */
6480         if (!graph->prev->commit)
6481                 return;
6483         if (view->lines > 2)
6484                 view->line[view->lines - 3].dirty = 1;
6485         if (view->lines > 1)
6486                 view->line[view->lines - 2].dirty = 1;
6487         draw_rev_graph(graph->prev);
6488         done_rev_graph(graph->prev->prev);
6492 /*
6493  * Main view backend
6494  */
6496 static const char *main_argv[SIZEOF_ARG] = {
6497         "git", "log", "--no-color", "--pretty=raw", "--parents",
6498                       "--topo-order", "%(head)", NULL
6499 };
6501 static bool
6502 main_draw(struct view *view, struct line *line, unsigned int lineno)
6504         struct commit *commit = line->data;
6506         if (!commit->author)
6507                 return FALSE;
6509         if (opt_date && draw_date(view, &commit->time))
6510                 return TRUE;
6512         if (opt_author && draw_author(view, commit->author))
6513                 return TRUE;
6515         if (opt_rev_graph && commit->graph_size &&
6516             draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
6517                 return TRUE;
6519         if (opt_show_refs && commit->refs) {
6520                 size_t i;
6522                 for (i = 0; i < commit->refs->size; i++) {
6523                         struct ref *ref = commit->refs->refs[i];
6524                         enum line_type type;
6526                         if (ref->head)
6527                                 type = LINE_MAIN_HEAD;
6528                         else if (ref->ltag)
6529                                 type = LINE_MAIN_LOCAL_TAG;
6530                         else if (ref->tag)
6531                                 type = LINE_MAIN_TAG;
6532                         else if (ref->tracked)
6533                                 type = LINE_MAIN_TRACKED;
6534                         else if (ref->remote)
6535                                 type = LINE_MAIN_REMOTE;
6536                         else
6537                                 type = LINE_MAIN_REF;
6539                         if (draw_text(view, type, "[", TRUE) ||
6540                             draw_text(view, type, ref->name, TRUE) ||
6541                             draw_text(view, type, "]", TRUE))
6542                                 return TRUE;
6544                         if (draw_text(view, LINE_DEFAULT, " ", TRUE))
6545                                 return TRUE;
6546                 }
6547         }
6549         draw_text(view, LINE_DEFAULT, commit->title, TRUE);
6550         return TRUE;
6553 /* Reads git log --pretty=raw output and parses it into the commit struct. */
6554 static bool
6555 main_read(struct view *view, char *line)
6557         static struct rev_graph *graph = graph_stacks;
6558         enum line_type type;
6559         struct commit *commit;
6561         if (!line) {
6562                 int i;
6564                 if (!view->lines && !view->parent)
6565                         die("No revisions match the given arguments.");
6566                 if (view->lines > 0) {
6567                         commit = view->line[view->lines - 1].data;
6568                         view->line[view->lines - 1].dirty = 1;
6569                         if (!commit->author) {
6570                                 view->lines--;
6571                                 free(commit);
6572                                 graph->commit = NULL;
6573                         }
6574                 }
6575                 update_rev_graph(view, graph);
6577                 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
6578                         clear_rev_graph(&graph_stacks[i]);
6579                 return TRUE;
6580         }
6582         type = get_line_type(line);
6583         if (type == LINE_COMMIT) {
6584                 commit = calloc(1, sizeof(struct commit));
6585                 if (!commit)
6586                         return FALSE;
6588                 line += STRING_SIZE("commit ");
6589                 if (*line == '-') {
6590                         graph->boundary = 1;
6591                         line++;
6592                 }
6594                 string_copy_rev(commit->id, line);
6595                 commit->refs = get_ref_list(commit->id);
6596                 graph->commit = commit;
6597                 add_line_data(view, commit, LINE_MAIN_COMMIT);
6599                 while ((line = strchr(line, ' '))) {
6600                         line++;
6601                         push_rev_graph(graph->parents, line);
6602                         commit->has_parents = TRUE;
6603                 }
6604                 return TRUE;
6605         }
6607         if (!view->lines)
6608                 return TRUE;
6609         commit = view->line[view->lines - 1].data;
6611         switch (type) {
6612         case LINE_PARENT:
6613                 if (commit->has_parents)
6614                         break;
6615                 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
6616                 break;
6618         case LINE_AUTHOR:
6619                 parse_author_line(line + STRING_SIZE("author "),
6620                                   &commit->author, &commit->time);
6621                 update_rev_graph(view, graph);
6622                 graph = graph->next;
6623                 break;
6625         default:
6626                 /* Fill in the commit title if it has not already been set. */
6627                 if (commit->title[0])
6628                         break;
6630                 /* Require titles to start with a non-space character at the
6631                  * offset used by git log. */
6632                 if (strncmp(line, "    ", 4))
6633                         break;
6634                 line += 4;
6635                 /* Well, if the title starts with a whitespace character,
6636                  * try to be forgiving.  Otherwise we end up with no title. */
6637                 while (isspace(*line))
6638                         line++;
6639                 if (*line == '\0')
6640                         break;
6641                 /* FIXME: More graceful handling of titles; append "..." to
6642                  * shortened titles, etc. */
6644                 string_expand(commit->title, sizeof(commit->title), line, 1);
6645                 view->line[view->lines - 1].dirty = 1;
6646         }
6648         return TRUE;
6651 static enum request
6652 main_request(struct view *view, enum request request, struct line *line)
6654         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
6656         switch (request) {
6657         case REQ_ENTER:
6658                 open_view(view, REQ_VIEW_DIFF, flags);
6659                 break;
6660         case REQ_REFRESH:
6661                 load_refs();
6662                 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
6663                 break;
6664         default:
6665                 return request;
6666         }
6668         return REQ_NONE;
6671 static bool
6672 grep_refs(struct ref_list *list, regex_t *regex)
6674         regmatch_t pmatch;
6675         size_t i;
6677         if (!opt_show_refs || !list)
6678                 return FALSE;
6680         for (i = 0; i < list->size; i++) {
6681                 if (regexec(regex, list->refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
6682                         return TRUE;
6683         }
6685         return FALSE;
6688 static bool
6689 main_grep(struct view *view, struct line *line)
6691         struct commit *commit = line->data;
6692         const char *text[] = {
6693                 commit->title,
6694                 opt_author ? commit->author : "",
6695                 opt_date ? mkdate(&commit->time) : "",
6696                 NULL
6697         };
6699         return grep_text(view, text) || grep_refs(commit->refs, view->regex);
6702 static void
6703 main_select(struct view *view, struct line *line)
6705         struct commit *commit = line->data;
6707         string_copy_rev(view->ref, commit->id);
6708         string_copy_rev(ref_commit, view->ref);
6711 static struct view_ops main_ops = {
6712         "commit",
6713         main_argv,
6714         NULL,
6715         main_read,
6716         main_draw,
6717         main_request,
6718         main_grep,
6719         main_select,
6720 };
6723 /*
6724  * Unicode / UTF-8 handling
6725  *
6726  * NOTE: Much of the following code for dealing with Unicode is derived from
6727  * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
6728  * src/intl/charset.c from the UTF-8 branch commit elinks-0.11.0-g31f2c28.
6729  */
6731 static inline int
6732 unicode_width(unsigned long c)
6734         if (c >= 0x1100 &&
6735            (c <= 0x115f                         /* Hangul Jamo */
6736             || c == 0x2329
6737             || c == 0x232a
6738             || (c >= 0x2e80  && c <= 0xa4cf && c != 0x303f)
6739                                                 /* CJK ... Yi */
6740             || (c >= 0xac00  && c <= 0xd7a3)    /* Hangul Syllables */
6741             || (c >= 0xf900  && c <= 0xfaff)    /* CJK Compatibility Ideographs */
6742             || (c >= 0xfe30  && c <= 0xfe6f)    /* CJK Compatibility Forms */
6743             || (c >= 0xff00  && c <= 0xff60)    /* Fullwidth Forms */
6744             || (c >= 0xffe0  && c <= 0xffe6)
6745             || (c >= 0x20000 && c <= 0x2fffd)
6746             || (c >= 0x30000 && c <= 0x3fffd)))
6747                 return 2;
6749         if (c == '\t')
6750                 return opt_tab_size;
6752         return 1;
6755 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
6756  * Illegal bytes are set one. */
6757 static const unsigned char utf8_bytes[256] = {
6758         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,
6759         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,
6760         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,
6761         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,
6762         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,
6763         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,
6764         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,
6765         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,
6766 };
6768 /* Decode UTF-8 multi-byte representation into a Unicode character. */
6769 static inline unsigned long
6770 utf8_to_unicode(const char *string, size_t length)
6772         unsigned long unicode;
6774         switch (length) {
6775         case 1:
6776                 unicode  =   string[0];
6777                 break;
6778         case 2:
6779                 unicode  =  (string[0] & 0x1f) << 6;
6780                 unicode +=  (string[1] & 0x3f);
6781                 break;
6782         case 3:
6783                 unicode  =  (string[0] & 0x0f) << 12;
6784                 unicode += ((string[1] & 0x3f) << 6);
6785                 unicode +=  (string[2] & 0x3f);
6786                 break;
6787         case 4:
6788                 unicode  =  (string[0] & 0x0f) << 18;
6789                 unicode += ((string[1] & 0x3f) << 12);
6790                 unicode += ((string[2] & 0x3f) << 6);
6791                 unicode +=  (string[3] & 0x3f);
6792                 break;
6793         case 5:
6794                 unicode  =  (string[0] & 0x0f) << 24;
6795                 unicode += ((string[1] & 0x3f) << 18);
6796                 unicode += ((string[2] & 0x3f) << 12);
6797                 unicode += ((string[3] & 0x3f) << 6);
6798                 unicode +=  (string[4] & 0x3f);
6799                 break;
6800         case 6:
6801                 unicode  =  (string[0] & 0x01) << 30;
6802                 unicode += ((string[1] & 0x3f) << 24);
6803                 unicode += ((string[2] & 0x3f) << 18);
6804                 unicode += ((string[3] & 0x3f) << 12);
6805                 unicode += ((string[4] & 0x3f) << 6);
6806                 unicode +=  (string[5] & 0x3f);
6807                 break;
6808         default:
6809                 die("Invalid Unicode length");
6810         }
6812         /* Invalid characters could return the special 0xfffd value but NUL
6813          * should be just as good. */
6814         return unicode > 0xffff ? 0 : unicode;
6817 /* Calculates how much of string can be shown within the given maximum width
6818  * and sets trimmed parameter to non-zero value if all of string could not be
6819  * shown. If the reserve flag is TRUE, it will reserve at least one
6820  * trailing character, which can be useful when drawing a delimiter.
6821  *
6822  * Returns the number of bytes to output from string to satisfy max_width. */
6823 static size_t
6824 utf8_length(const char **start, size_t skip, int *width, size_t max_width, int *trimmed, bool reserve)
6826         const char *string = *start;
6827         const char *end = strchr(string, '\0');
6828         unsigned char last_bytes = 0;
6829         size_t last_ucwidth = 0;
6831         *width = 0;
6832         *trimmed = 0;
6834         while (string < end) {
6835                 int c = *(unsigned char *) string;
6836                 unsigned char bytes = utf8_bytes[c];
6837                 size_t ucwidth;
6838                 unsigned long unicode;
6840                 if (string + bytes > end)
6841                         break;
6843                 /* Change representation to figure out whether
6844                  * it is a single- or double-width character. */
6846                 unicode = utf8_to_unicode(string, bytes);
6847                 /* FIXME: Graceful handling of invalid Unicode character. */
6848                 if (!unicode)
6849                         break;
6851                 ucwidth = unicode_width(unicode);
6852                 if (skip > 0) {
6853                         skip -= ucwidth <= skip ? ucwidth : skip;
6854                         *start += bytes;
6855                 }
6856                 *width  += ucwidth;
6857                 if (*width > max_width) {
6858                         *trimmed = 1;
6859                         *width -= ucwidth;
6860                         if (reserve && *width == max_width) {
6861                                 string -= last_bytes;
6862                                 *width -= last_ucwidth;
6863                         }
6864                         break;
6865                 }
6867                 string  += bytes;
6868                 last_bytes = ucwidth ? bytes : 0;
6869                 last_ucwidth = ucwidth;
6870         }
6872         return string - *start;
6876 /*
6877  * Status management
6878  */
6880 /* Whether or not the curses interface has been initialized. */
6881 static bool cursed = FALSE;
6883 /* Terminal hacks and workarounds. */
6884 static bool use_scroll_redrawwin;
6885 static bool use_scroll_status_wclear;
6887 /* The status window is used for polling keystrokes. */
6888 static WINDOW *status_win;
6890 /* Reading from the prompt? */
6891 static bool input_mode = FALSE;
6893 static bool status_empty = FALSE;
6895 /* Update status and title window. */
6896 static void
6897 report(const char *msg, ...)
6899         struct view *view = display[current_view];
6901         if (input_mode)
6902                 return;
6904         if (!view) {
6905                 char buf[SIZEOF_STR];
6906                 va_list args;
6908                 va_start(args, msg);
6909                 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
6910                         buf[sizeof(buf) - 1] = 0;
6911                         buf[sizeof(buf) - 2] = '.';
6912                         buf[sizeof(buf) - 3] = '.';
6913                         buf[sizeof(buf) - 4] = '.';
6914                 }
6915                 va_end(args);
6916                 die("%s", buf);
6917         }
6919         if (!status_empty || *msg) {
6920                 va_list args;
6922                 va_start(args, msg);
6924                 wmove(status_win, 0, 0);
6925                 if (view->has_scrolled && use_scroll_status_wclear)
6926                         wclear(status_win);
6927                 if (*msg) {
6928                         vwprintw(status_win, msg, args);
6929                         status_empty = FALSE;
6930                 } else {
6931                         status_empty = TRUE;
6932                 }
6933                 wclrtoeol(status_win);
6934                 wnoutrefresh(status_win);
6936                 va_end(args);
6937         }
6939         update_view_title(view);
6942 /* Controls when nodelay should be in effect when polling user input. */
6943 static void
6944 set_nonblocking_input(bool loading)
6946         static unsigned int loading_views;
6948         if ((loading == FALSE && loading_views-- == 1) ||
6949             (loading == TRUE  && loading_views++ == 0))
6950                 nodelay(status_win, loading);
6953 static void
6954 init_display(void)
6956         const char *term;
6957         int x, y;
6959         /* Initialize the curses library */
6960         if (isatty(STDIN_FILENO)) {
6961                 cursed = !!initscr();
6962                 opt_tty = stdin;
6963         } else {
6964                 /* Leave stdin and stdout alone when acting as a pager. */
6965                 opt_tty = fopen("/dev/tty", "r+");
6966                 if (!opt_tty)
6967                         die("Failed to open /dev/tty");
6968                 cursed = !!newterm(NULL, opt_tty, opt_tty);
6969         }
6971         if (!cursed)
6972                 die("Failed to initialize curses");
6974         nonl();         /* Disable conversion and detect newlines from input. */
6975         cbreak();       /* Take input chars one at a time, no wait for \n */
6976         noecho();       /* Don't echo input */
6977         leaveok(stdscr, FALSE);
6979         if (has_colors())
6980                 init_colors();
6982         getmaxyx(stdscr, y, x);
6983         status_win = newwin(1, 0, y - 1, 0);
6984         if (!status_win)
6985                 die("Failed to create status window");
6987         /* Enable keyboard mapping */
6988         keypad(status_win, TRUE);
6989         wbkgdset(status_win, get_line_attr(LINE_STATUS));
6991         TABSIZE = opt_tab_size;
6992         if (opt_line_graphics) {
6993                 line_graphics[LINE_GRAPHIC_VLINE] = ACS_VLINE;
6994         }
6996         term = getenv("XTERM_VERSION") ? NULL : getenv("COLORTERM");
6997         if (term && !strcmp(term, "gnome-terminal")) {
6998                 /* In the gnome-terminal-emulator, the message from
6999                  * scrolling up one line when impossible followed by
7000                  * scrolling down one line causes corruption of the
7001                  * status line. This is fixed by calling wclear. */
7002                 use_scroll_status_wclear = TRUE;
7003                 use_scroll_redrawwin = FALSE;
7005         } else if (term && !strcmp(term, "xrvt-xpm")) {
7006                 /* No problems with full optimizations in xrvt-(unicode)
7007                  * and aterm. */
7008                 use_scroll_status_wclear = use_scroll_redrawwin = FALSE;
7010         } else {
7011                 /* When scrolling in (u)xterm the last line in the
7012                  * scrolling direction will update slowly. */
7013                 use_scroll_redrawwin = TRUE;
7014                 use_scroll_status_wclear = FALSE;
7015         }
7018 static int
7019 get_input(int prompt_position)
7021         struct view *view;
7022         int i, key, cursor_y, cursor_x;
7024         if (prompt_position)
7025                 input_mode = TRUE;
7027         while (TRUE) {
7028                 foreach_view (view, i) {
7029                         update_view(view);
7030                         if (view_is_displayed(view) && view->has_scrolled &&
7031                             use_scroll_redrawwin)
7032                                 redrawwin(view->win);
7033                         view->has_scrolled = FALSE;
7034                 }
7036                 /* Update the cursor position. */
7037                 if (prompt_position) {
7038                         getbegyx(status_win, cursor_y, cursor_x);
7039                         cursor_x = prompt_position;
7040                 } else {
7041                         view = display[current_view];
7042                         getbegyx(view->win, cursor_y, cursor_x);
7043                         cursor_x = view->width - 1;
7044                         cursor_y += view->lineno - view->offset;
7045                 }
7046                 setsyx(cursor_y, cursor_x);
7048                 /* Refresh, accept single keystroke of input */
7049                 doupdate();
7050                 key = wgetch(status_win);
7052                 /* wgetch() with nodelay() enabled returns ERR when
7053                  * there's no input. */
7054                 if (key == ERR) {
7056                 } else if (key == KEY_RESIZE) {
7057                         int height, width;
7059                         getmaxyx(stdscr, height, width);
7061                         wresize(status_win, 1, width);
7062                         mvwin(status_win, height - 1, 0);
7063                         wnoutrefresh(status_win);
7064                         resize_display();
7065                         redraw_display(TRUE);
7067                 } else {
7068                         input_mode = FALSE;
7069                         return key;
7070                 }
7071         }
7074 static char *
7075 prompt_input(const char *prompt, input_handler handler, void *data)
7077         enum input_status status = INPUT_OK;
7078         static char buf[SIZEOF_STR];
7079         size_t pos = 0;
7081         buf[pos] = 0;
7083         while (status == INPUT_OK || status == INPUT_SKIP) {
7084                 int key;
7086                 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
7087                 wclrtoeol(status_win);
7089                 key = get_input(pos + 1);
7090                 switch (key) {
7091                 case KEY_RETURN:
7092                 case KEY_ENTER:
7093                 case '\n':
7094                         status = pos ? INPUT_STOP : INPUT_CANCEL;
7095                         break;
7097                 case KEY_BACKSPACE:
7098                         if (pos > 0)
7099                                 buf[--pos] = 0;
7100                         else
7101                                 status = INPUT_CANCEL;
7102                         break;
7104                 case KEY_ESC:
7105                         status = INPUT_CANCEL;
7106                         break;
7108                 default:
7109                         if (pos >= sizeof(buf)) {
7110                                 report("Input string too long");
7111                                 return NULL;
7112                         }
7114                         status = handler(data, buf, key);
7115                         if (status == INPUT_OK)
7116                                 buf[pos++] = (char) key;
7117                 }
7118         }
7120         /* Clear the status window */
7121         status_empty = FALSE;
7122         report("");
7124         if (status == INPUT_CANCEL)
7125                 return NULL;
7127         buf[pos++] = 0;
7129         return buf;
7132 static enum input_status
7133 prompt_yesno_handler(void *data, char *buf, int c)
7135         if (c == 'y' || c == 'Y')
7136                 return INPUT_STOP;
7137         if (c == 'n' || c == 'N')
7138                 return INPUT_CANCEL;
7139         return INPUT_SKIP;
7142 static bool
7143 prompt_yesno(const char *prompt)
7145         char prompt2[SIZEOF_STR];
7147         if (!string_format(prompt2, "%s [Yy/Nn]", prompt))
7148                 return FALSE;
7150         return !!prompt_input(prompt2, prompt_yesno_handler, NULL);
7153 static enum input_status
7154 read_prompt_handler(void *data, char *buf, int c)
7156         return isprint(c) ? INPUT_OK : INPUT_SKIP;
7159 static char *
7160 read_prompt(const char *prompt)
7162         return prompt_input(prompt, read_prompt_handler, NULL);
7165 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected)
7167         enum input_status status = INPUT_OK;
7168         int size = 0;
7170         while (items[size].text)
7171                 size++;
7173         while (status == INPUT_OK) {
7174                 const struct menu_item *item = &items[*selected];
7175                 int key;
7176                 int i;
7178                 mvwprintw(status_win, 0, 0, "%s (%d of %d) ",
7179                           prompt, *selected + 1, size);
7180                 if (item->hotkey)
7181                         wprintw(status_win, "[%c] ", (char) item->hotkey);
7182                 wprintw(status_win, "%s", item->text);
7183                 wclrtoeol(status_win);
7185                 key = get_input(COLS - 1);
7186                 switch (key) {
7187                 case KEY_RETURN:
7188                 case KEY_ENTER:
7189                 case '\n':
7190                         status = INPUT_STOP;
7191                         break;
7193                 case KEY_LEFT:
7194                 case KEY_UP:
7195                         *selected = *selected - 1;
7196                         if (*selected < 0)
7197                                 *selected = size - 1;
7198                         break;
7200                 case KEY_RIGHT:
7201                 case KEY_DOWN:
7202                         *selected = (*selected + 1) % size;
7203                         break;
7205                 case KEY_ESC:
7206                         status = INPUT_CANCEL;
7207                         break;
7209                 default:
7210                         for (i = 0; items[i].text; i++)
7211                                 if (items[i].hotkey == key) {
7212                                         *selected = i;
7213                                         status = INPUT_STOP;
7214                                         break;
7215                                 }
7216                 }
7217         }
7219         /* Clear the status window */
7220         status_empty = FALSE;
7221         report("");
7223         return status != INPUT_CANCEL;
7226 /*
7227  * Repository properties
7228  */
7230 static struct ref **refs = NULL;
7231 static size_t refs_size = 0;
7233 static struct ref_list **ref_lists = NULL;
7234 static size_t ref_lists_size = 0;
7236 DEFINE_ALLOCATOR(realloc_refs, struct ref *, 256)
7237 DEFINE_ALLOCATOR(realloc_refs_list, struct ref *, 8)
7238 DEFINE_ALLOCATOR(realloc_ref_lists, struct ref_list *, 8)
7240 static int
7241 compare_refs(const void *ref1_, const void *ref2_)
7243         const struct ref *ref1 = *(const struct ref **)ref1_;
7244         const struct ref *ref2 = *(const struct ref **)ref2_;
7246         if (ref1->tag != ref2->tag)
7247                 return ref2->tag - ref1->tag;
7248         if (ref1->ltag != ref2->ltag)
7249                 return ref2->ltag - ref2->ltag;
7250         if (ref1->head != ref2->head)
7251                 return ref2->head - ref1->head;
7252         if (ref1->tracked != ref2->tracked)
7253                 return ref2->tracked - ref1->tracked;
7254         if (ref1->remote != ref2->remote)
7255                 return ref2->remote - ref1->remote;
7256         return strcmp(ref1->name, ref2->name);
7259 static void
7260 foreach_ref(bool (*visitor)(void *data, const struct ref *ref), void *data)
7262         size_t i;
7264         for (i = 0; i < refs_size; i++)
7265                 if (!visitor(data, refs[i]))
7266                         break;
7269 static struct ref_list *
7270 get_ref_list(const char *id)
7272         struct ref_list *list;
7273         size_t i;
7275         for (i = 0; i < ref_lists_size; i++)
7276                 if (!strcmp(id, ref_lists[i]->id))
7277                         return ref_lists[i];
7279         if (!realloc_ref_lists(&ref_lists, ref_lists_size, 1))
7280                 return NULL;
7281         list = calloc(1, sizeof(*list));
7282         if (!list)
7283                 return NULL;
7285         for (i = 0; i < refs_size; i++) {
7286                 if (!strcmp(id, refs[i]->id) &&
7287                     realloc_refs_list(&list->refs, list->size, 1))
7288                         list->refs[list->size++] = refs[i];
7289         }
7291         if (!list->refs) {
7292                 free(list);
7293                 return NULL;
7294         }
7296         qsort(list->refs, list->size, sizeof(*list->refs), compare_refs);
7297         ref_lists[ref_lists_size++] = list;
7298         return list;
7301 static int
7302 read_ref(char *id, size_t idlen, char *name, size_t namelen)
7304         struct ref *ref = NULL;
7305         bool tag = FALSE;
7306         bool ltag = FALSE;
7307         bool remote = FALSE;
7308         bool tracked = FALSE;
7309         bool head = FALSE;
7310         int from = 0, to = refs_size - 1;
7312         if (!prefixcmp(name, "refs/tags/")) {
7313                 if (!suffixcmp(name, namelen, "^{}")) {
7314                         namelen -= 3;
7315                         name[namelen] = 0;
7316                 } else {
7317                         ltag = TRUE;
7318                 }
7320                 tag = TRUE;
7321                 namelen -= STRING_SIZE("refs/tags/");
7322                 name    += STRING_SIZE("refs/tags/");
7324         } else if (!prefixcmp(name, "refs/remotes/")) {
7325                 remote = TRUE;
7326                 namelen -= STRING_SIZE("refs/remotes/");
7327                 name    += STRING_SIZE("refs/remotes/");
7328                 tracked  = !strcmp(opt_remote, name);
7330         } else if (!prefixcmp(name, "refs/heads/")) {
7331                 namelen -= STRING_SIZE("refs/heads/");
7332                 name    += STRING_SIZE("refs/heads/");
7333                 head     = !strncmp(opt_head, name, namelen);
7335         } else if (!strcmp(name, "HEAD")) {
7336                 string_ncopy(opt_head_rev, id, idlen);
7337                 return OK;
7338         }
7340         /* If we are reloading or it's an annotated tag, replace the
7341          * previous SHA1 with the resolved commit id; relies on the fact
7342          * git-ls-remote lists the commit id of an annotated tag right
7343          * before the commit id it points to. */
7344         while (from <= to) {
7345                 size_t pos = (to + from) / 2;
7346                 int cmp = strcmp(name, refs[pos]->name);
7348                 if (!cmp) {
7349                         ref = refs[pos];
7350                         break;
7351                 }
7353                 if (cmp < 0)
7354                         to = pos - 1;
7355                 else
7356                         from = pos + 1;
7357         }
7359         if (!ref) {
7360                 if (!realloc_refs(&refs, refs_size, 1))
7361                         return ERR;
7362                 ref = calloc(1, sizeof(*ref) + namelen);
7363                 if (!ref)
7364                         return ERR;
7365                 memmove(refs + from + 1, refs + from,
7366                         (refs_size - from) * sizeof(*refs));
7367                 refs[from] = ref;
7368                 strncpy(ref->name, name, namelen);
7369                 refs_size++;
7370         }
7372         ref->head = head;
7373         ref->tag = tag;
7374         ref->ltag = ltag;
7375         ref->remote = remote;
7376         ref->tracked = tracked;
7377         string_copy_rev(ref->id, id);
7379         return OK;
7382 static int
7383 load_refs(void)
7385         const char *head_argv[] = {
7386                 "git", "symbolic-ref", "HEAD", NULL
7387         };
7388         static const char *ls_remote_argv[SIZEOF_ARG] = {
7389                 "git", "ls-remote", opt_git_dir, NULL
7390         };
7391         static bool init = FALSE;
7392         size_t i;
7394         if (!init) {
7395                 argv_from_env(ls_remote_argv, "TIG_LS_REMOTE");
7396                 init = TRUE;
7397         }
7399         if (!*opt_git_dir)
7400                 return OK;
7402         if (run_io_buf(head_argv, opt_head, sizeof(opt_head)) &&
7403             !prefixcmp(opt_head, "refs/heads/")) {
7404                 char *offset = opt_head + STRING_SIZE("refs/heads/");
7406                 memmove(opt_head, offset, strlen(offset) + 1);
7407         }
7409         for (i = 0; i < refs_size; i++)
7410                 refs[i]->id[0] = 0;
7412         if (run_io_load(ls_remote_argv, "\t", read_ref) == ERR)
7413                 return ERR;
7415         /* Update the ref lists to reflect changes. */
7416         for (i = 0; i < ref_lists_size; i++) {
7417                 struct ref_list *list = ref_lists[i];
7418                 size_t old, new;
7420                 for (old = new = 0; old < list->size; old++)
7421                         if (!strcmp(list->id, list->refs[old]->id))
7422                                 list->refs[new++] = list->refs[old];
7423                 list->size = new;
7424         }
7426         return OK;
7429 static void
7430 set_remote_branch(const char *name, const char *value, size_t valuelen)
7432         if (!strcmp(name, ".remote")) {
7433                 string_ncopy(opt_remote, value, valuelen);
7435         } else if (*opt_remote && !strcmp(name, ".merge")) {
7436                 size_t from = strlen(opt_remote);
7438                 if (!prefixcmp(value, "refs/heads/"))
7439                         value += STRING_SIZE("refs/heads/");
7441                 if (!string_format_from(opt_remote, &from, "/%s", value))
7442                         opt_remote[0] = 0;
7443         }
7446 static void
7447 set_repo_config_option(char *name, char *value, int (*cmd)(int, const char **))
7449         const char *argv[SIZEOF_ARG] = { name, "=" };
7450         int argc = 1 + (cmd == option_set_command);
7451         int error = ERR;
7453         if (!argv_from_string(argv, &argc, value))
7454                 config_msg = "Too many option arguments";
7455         else
7456                 error = cmd(argc, argv);
7458         if (error == ERR)
7459                 warn("Option 'tig.%s': %s", name, config_msg);
7462 static bool
7463 set_environment_variable(const char *name, const char *value)
7465         size_t len = strlen(name) + 1 + strlen(value) + 1;
7466         char *env = malloc(len);
7468         if (env &&
7469             string_nformat(env, len, NULL, "%s=%s", name, value) &&
7470             putenv(env) == 0)
7471                 return TRUE;
7472         free(env);
7473         return FALSE;
7476 static void
7477 set_work_tree(const char *value)
7479         char cwd[SIZEOF_STR];
7481         if (!getcwd(cwd, sizeof(cwd)))
7482                 die("Failed to get cwd path: %s", strerror(errno));
7483         if (chdir(opt_git_dir) < 0)
7484                 die("Failed to chdir(%s): %s", strerror(errno));
7485         if (!getcwd(opt_git_dir, sizeof(opt_git_dir)))
7486                 die("Failed to get git path: %s", strerror(errno));
7487         if (chdir(cwd) < 0)
7488                 die("Failed to chdir(%s): %s", cwd, strerror(errno));
7489         if (chdir(value) < 0)
7490                 die("Failed to chdir(%s): %s", value, strerror(errno));
7491         if (!getcwd(cwd, sizeof(cwd)))
7492                 die("Failed to get cwd path: %s", strerror(errno));
7493         if (!set_environment_variable("GIT_WORK_TREE", cwd))
7494                 die("Failed to set GIT_WORK_TREE to '%s'", cwd);
7495         if (!set_environment_variable("GIT_DIR", opt_git_dir))
7496                 die("Failed to set GIT_DIR to '%s'", opt_git_dir);
7497         opt_is_inside_work_tree = TRUE;
7500 static int
7501 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
7503         if (!strcmp(name, "i18n.commitencoding"))
7504                 string_ncopy(opt_encoding, value, valuelen);
7506         else if (!strcmp(name, "core.editor"))
7507                 string_ncopy(opt_editor, value, valuelen);
7509         else if (!strcmp(name, "core.worktree"))
7510                 set_work_tree(value);
7512         else if (!prefixcmp(name, "tig.color."))
7513                 set_repo_config_option(name + 10, value, option_color_command);
7515         else if (!prefixcmp(name, "tig.bind."))
7516                 set_repo_config_option(name + 9, value, option_bind_command);
7518         else if (!prefixcmp(name, "tig."))
7519                 set_repo_config_option(name + 4, value, option_set_command);
7521         else if (*opt_head && !prefixcmp(name, "branch.") &&
7522                  !strncmp(name + 7, opt_head, strlen(opt_head)))
7523                 set_remote_branch(name + 7 + strlen(opt_head), value, valuelen);
7525         return OK;
7528 static int
7529 load_git_config(void)
7531         const char *config_list_argv[] = { "git", "config", "--list", NULL };
7533         return run_io_load(config_list_argv, "=", read_repo_config_option);
7536 static int
7537 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
7539         if (!opt_git_dir[0]) {
7540                 string_ncopy(opt_git_dir, name, namelen);
7542         } else if (opt_is_inside_work_tree == -1) {
7543                 /* This can be 3 different values depending on the
7544                  * version of git being used. If git-rev-parse does not
7545                  * understand --is-inside-work-tree it will simply echo
7546                  * the option else either "true" or "false" is printed.
7547                  * Default to true for the unknown case. */
7548                 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
7550         } else if (*name == '.') {
7551                 string_ncopy(opt_cdup, name, namelen);
7553         } else {
7554                 string_ncopy(opt_prefix, name, namelen);
7555         }
7557         return OK;
7560 static int
7561 load_repo_info(void)
7563         const char *rev_parse_argv[] = {
7564                 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
7565                         "--show-cdup", "--show-prefix", NULL
7566         };
7568         return run_io_load(rev_parse_argv, "=", read_repo_info);
7572 /*
7573  * Main
7574  */
7576 static const char usage[] =
7577 "tig " TIG_VERSION " (" __DATE__ ")\n"
7578 "\n"
7579 "Usage: tig        [options] [revs] [--] [paths]\n"
7580 "   or: tig show   [options] [revs] [--] [paths]\n"
7581 "   or: tig blame  [rev] path\n"
7582 "   or: tig status\n"
7583 "   or: tig <      [git command output]\n"
7584 "\n"
7585 "Options:\n"
7586 "  -v, --version   Show version and exit\n"
7587 "  -h, --help      Show help message and exit";
7589 static void __NORETURN
7590 quit(int sig)
7592         /* XXX: Restore tty modes and let the OS cleanup the rest! */
7593         if (cursed)
7594                 endwin();
7595         exit(0);
7598 static void __NORETURN
7599 die(const char *err, ...)
7601         va_list args;
7603         endwin();
7605         va_start(args, err);
7606         fputs("tig: ", stderr);
7607         vfprintf(stderr, err, args);
7608         fputs("\n", stderr);
7609         va_end(args);
7611         exit(1);
7614 static void
7615 warn(const char *msg, ...)
7617         va_list args;
7619         va_start(args, msg);
7620         fputs("tig warning: ", stderr);
7621         vfprintf(stderr, msg, args);
7622         fputs("\n", stderr);
7623         va_end(args);
7626 static enum request
7627 parse_options(int argc, const char *argv[])
7629         enum request request = REQ_VIEW_MAIN;
7630         const char *subcommand;
7631         bool seen_dashdash = FALSE;
7632         /* XXX: This is vulnerable to the user overriding options
7633          * required for the main view parser. */
7634         const char *custom_argv[SIZEOF_ARG] = {
7635                 "git", "log", "--no-color", "--pretty=raw", "--parents",
7636                         "--topo-order", NULL
7637         };
7638         int i, j = 6;
7640         if (!isatty(STDIN_FILENO)) {
7641                 io_open(&VIEW(REQ_VIEW_PAGER)->io, "");
7642                 return REQ_VIEW_PAGER;
7643         }
7645         if (argc <= 1)
7646                 return REQ_NONE;
7648         subcommand = argv[1];
7649         if (!strcmp(subcommand, "status")) {
7650                 if (argc > 2)
7651                         warn("ignoring arguments after `%s'", subcommand);
7652                 return REQ_VIEW_STATUS;
7654         } else if (!strcmp(subcommand, "blame")) {
7655                 if (argc <= 2 || argc > 4)
7656                         die("invalid number of options to blame\n\n%s", usage);
7658                 i = 2;
7659                 if (argc == 4) {
7660                         string_ncopy(opt_ref, argv[i], strlen(argv[i]));
7661                         i++;
7662                 }
7664                 string_ncopy(opt_file, argv[i], strlen(argv[i]));
7665                 return REQ_VIEW_BLAME;
7667         } else if (!strcmp(subcommand, "show")) {
7668                 request = REQ_VIEW_DIFF;
7670         } else {
7671                 subcommand = NULL;
7672         }
7674         if (subcommand) {
7675                 custom_argv[1] = subcommand;
7676                 j = 2;
7677         }
7679         for (i = 1 + !!subcommand; i < argc; i++) {
7680                 const char *opt = argv[i];
7682                 if (seen_dashdash || !strcmp(opt, "--")) {
7683                         seen_dashdash = TRUE;
7685                 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
7686                         printf("tig version %s\n", TIG_VERSION);
7687                         quit(0);
7689                 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
7690                         printf("%s\n", usage);
7691                         quit(0);
7692                 }
7694                 custom_argv[j++] = opt;
7695                 if (j >= ARRAY_SIZE(custom_argv))
7696                         die("command too long");
7697         }
7699         if (!prepare_update(VIEW(request), custom_argv, NULL, FORMAT_NONE))
7700                 die("Failed to format arguments");
7702         return request;
7705 int
7706 main(int argc, const char *argv[])
7708         enum request request = parse_options(argc, argv);
7709         struct view *view;
7710         size_t i;
7712         signal(SIGINT, quit);
7713         signal(SIGPIPE, SIG_IGN);
7715         if (setlocale(LC_ALL, "")) {
7716                 char *codeset = nl_langinfo(CODESET);
7718                 string_ncopy(opt_codeset, codeset, strlen(codeset));
7719         }
7721         if (load_repo_info() == ERR)
7722                 die("Failed to load repo info.");
7724         if (load_options() == ERR)
7725                 die("Failed to load user config.");
7727         if (load_git_config() == ERR)
7728                 die("Failed to load repo config.");
7730         /* Require a git repository unless when running in pager mode. */
7731         if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
7732                 die("Not a git repository");
7734         if (*opt_encoding && strcmp(opt_codeset, "UTF-8")) {
7735                 opt_iconv_in = iconv_open("UTF-8", opt_encoding);
7736                 if (opt_iconv_in == ICONV_NONE)
7737                         die("Failed to initialize character set conversion");
7738         }
7740         if (*opt_codeset && strcmp(opt_codeset, "UTF-8")) {
7741                 opt_iconv_out = iconv_open(opt_codeset, "UTF-8");
7742                 if (opt_iconv_out == ICONV_NONE)
7743                         die("Failed to initialize character set conversion");
7744         }
7746         if (load_refs() == ERR)
7747                 die("Failed to load refs.");
7749         foreach_view (view, i)
7750                 argv_from_env(view->ops->argv, view->cmd_env);
7752         init_display();
7754         if (request != REQ_NONE)
7755                 open_view(NULL, request, OPEN_PREPARED);
7756         request = request == REQ_NONE ? REQ_VIEW_MAIN : REQ_NONE;
7758         while (view_driver(display[current_view], request)) {
7759                 int key = get_input(0);
7761                 view = display[current_view];
7762                 request = get_keybinding(view->keymap, key);
7764                 /* Some low-level request handling. This keeps access to
7765                  * status_win restricted. */
7766                 switch (request) {
7767                 case REQ_PROMPT:
7768                 {
7769                         char *cmd = read_prompt(":");
7771                         if (cmd && isdigit(*cmd)) {
7772                                 int lineno = view->lineno + 1;
7774                                 if (parse_int(&lineno, cmd, 1, view->lines + 1) == OK) {
7775                                         select_view_line(view, lineno - 1);
7776                                         report("");
7777                                 } else {
7778                                         report("Unable to parse '%s' as a line number", cmd);
7779                                 }
7781                         } else if (cmd) {
7782                                 struct view *next = VIEW(REQ_VIEW_PAGER);
7783                                 const char *argv[SIZEOF_ARG] = { "git" };
7784                                 int argc = 1;
7786                                 /* When running random commands, initially show the
7787                                  * command in the title. However, it maybe later be
7788                                  * overwritten if a commit line is selected. */
7789                                 string_ncopy(next->ref, cmd, strlen(cmd));
7791                                 if (!argv_from_string(argv, &argc, cmd)) {
7792                                         report("Too many arguments");
7793                                 } else if (!prepare_update(next, argv, NULL, FORMAT_DASH)) {
7794                                         report("Failed to format command");
7795                                 } else {
7796                                         open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
7797                                 }
7798                         }
7800                         request = REQ_NONE;
7801                         break;
7802                 }
7803                 case REQ_SEARCH:
7804                 case REQ_SEARCH_BACK:
7805                 {
7806                         const char *prompt = request == REQ_SEARCH ? "/" : "?";
7807                         char *search = read_prompt(prompt);
7809                         if (search)
7810                                 string_ncopy(opt_search, search, strlen(search));
7811                         else if (*opt_search)
7812                                 request = request == REQ_SEARCH ?
7813                                         REQ_FIND_NEXT :
7814                                         REQ_FIND_PREV;
7815                         else
7816                                 request = REQ_NONE;
7817                         break;
7818                 }
7819                 default:
7820                         break;
7821                 }
7822         }
7824         quit(0);
7826         return 0;