Code

run_io_rd_dir: obsolete by switching call sites to run_io_rd_dir
[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, 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 struct enum_map {
301         const char *name;
302         int namelen;
303         int value;
304 };
306 #define ENUM_MAP(name, value) { name, STRING_SIZE(name), value }
308 static bool
309 map_enum_do(const struct enum_map *map, size_t map_size, int *value, const char *name)
311         size_t namelen = strlen(name);
312         int i;
314         for (i = 0; i < map_size; i++)
315                 if (namelen == map[i].namelen &&
316                     !string_enum_compare(name, map[i].name, namelen)) {
317                         *value = map[i].value;
318                         return TRUE;
319                 }
321         return FALSE;
324 #define map_enum(attr, map, name) \
325         map_enum_do(map, ARRAY_SIZE(map), attr, name)
327 #define prefixcmp(str1, str2) \
328         strncmp(str1, str2, STRING_SIZE(str2))
330 static inline int
331 suffixcmp(const char *str, int slen, const char *suffix)
333         size_t len = slen >= 0 ? slen : strlen(str);
334         size_t suffixlen = strlen(suffix);
336         return suffixlen < len ? strcmp(str + len - suffixlen, suffix) : -1;
340 /*
341  * What value of "tz" was in effect back then at "time" in the
342  * local timezone?
343  */
344 static int local_tzoffset(time_t time)
346         time_t t, t_local;
347         struct tm tm;
348         int offset, eastwest; 
350         t = time;
351         localtime_r(&t, &tm);
352         t_local = mktime(&tm);
354         if (t_local < t) {
355                 eastwest = -1;
356                 offset = t - t_local;
357         } else {
358                 eastwest = 1;
359                 offset = t_local - t;
360         }
361         offset /= 60; /* in minutes */
362         offset = (offset % 60) + ((offset / 60) * 100);
363         return offset * eastwest;
366 enum date {
367         DATE_NONE = 0,
368         DATE_DEFAULT,
369         DATE_RELATIVE,
370         DATE_SHORT
371 };
373 static char *
374 string_date(const time_t *time, enum date date)
376         static char buf[DATE_COLS + 1];
377         static const struct enum_map reldate[] = {
378                 { "second", 1,                  60 * 2 },
379                 { "minute", 60,                 60 * 60 * 2 },
380                 { "hour",   60 * 60,            60 * 60 * 24 * 2 },
381                 { "day",    60 * 60 * 24,       60 * 60 * 24 * 7 * 2 },
382                 { "week",   60 * 60 * 24 * 7,   60 * 60 * 24 * 7 * 5 },
383                 { "month",  60 * 60 * 24 * 30,  60 * 60 * 24 * 30 * 12 },
384         };
385         struct tm tm;
387         if (date == DATE_RELATIVE) {
388                 struct timeval now;
389                 time_t date = *time + local_tzoffset(*time);
390                 time_t seconds;
391                 int i;
393                 gettimeofday(&now, NULL);
394                 seconds = now.tv_sec < date ? date - now.tv_sec : now.tv_sec - date;
395                 for (i = 0; i < ARRAY_SIZE(reldate); i++) {
396                         if (seconds >= reldate[i].value)
397                                 continue;
399                         seconds /= reldate[i].namelen;
400                         if (!string_format(buf, "%ld %s%s %s",
401                                            seconds, reldate[i].name,
402                                            seconds > 1 ? "s" : "",
403                                            now.tv_sec >= date ? "ago" : "ahead"))
404                                 break;
405                         return buf;
406                 }
407         }
409         gmtime_r(time, &tm);
410         return strftime(buf, sizeof(buf), DATE_FORMAT, &tm) ? buf : NULL;
414 static bool
415 argv_from_string(const char *argv[SIZEOF_ARG], int *argc, char *cmd)
417         int valuelen;
419         while (*cmd && *argc < SIZEOF_ARG && (valuelen = strcspn(cmd, " \t"))) {
420                 bool advance = cmd[valuelen] != 0;
422                 cmd[valuelen] = 0;
423                 argv[(*argc)++] = chomp_string(cmd);
424                 cmd = chomp_string(cmd + valuelen + advance);
425         }
427         if (*argc < SIZEOF_ARG)
428                 argv[*argc] = NULL;
429         return *argc < SIZEOF_ARG;
432 static void
433 argv_from_env(const char **argv, const char *name)
435         char *env = argv ? getenv(name) : NULL;
436         int argc = 0;
438         if (env && *env)
439                 env = strdup(env);
440         if (env && !argv_from_string(argv, &argc, env))
441                 die("Too many arguments in the `%s` environment variable", name);
445 /*
446  * Executing external commands.
447  */
449 enum io_type {
450         IO_FD,                  /* File descriptor based IO. */
451         IO_BG,                  /* Execute command in the background. */
452         IO_FG,                  /* Execute command with same std{in,out,err}. */
453         IO_RD,                  /* Read only fork+exec IO. */
454         IO_WR,                  /* Write only fork+exec IO. */
455         IO_AP,                  /* Append fork+exec output to file. */
456 };
458 struct io {
459         enum io_type type;      /* The requested type of pipe. */
460         const char *dir;        /* Directory from which to execute. */
461         pid_t pid;              /* Pipe for reading or writing. */
462         int pipe;               /* Pipe end for reading or writing. */
463         int error;              /* Error status. */
464         const char *argv[SIZEOF_ARG];   /* Shell command arguments. */
465         char *buf;              /* Read buffer. */
466         size_t bufalloc;        /* Allocated buffer size. */
467         size_t bufsize;         /* Buffer content size. */
468         char *bufpos;           /* Current buffer position. */
469         unsigned int eof:1;     /* Has end of file been reached. */
470 };
472 static void
473 reset_io(struct io *io)
475         io->pipe = -1;
476         io->pid = 0;
477         io->buf = io->bufpos = NULL;
478         io->bufalloc = io->bufsize = 0;
479         io->error = 0;
480         io->eof = 0;
483 static void
484 init_io(struct io *io, const char *dir, enum io_type type)
486         reset_io(io);
487         io->type = type;
488         io->dir = dir;
491 static bool
492 init_io_rd(struct io *io, const char *argv[], const char *dir,
493                 enum format_flags flags)
495         init_io(io, dir, IO_RD);
496         return format_argv(io->argv, argv, flags);
499 static bool
500 io_open(struct io *io, const char *name)
502         init_io(io, NULL, IO_FD);
503         io->pipe = *name ? open(name, O_RDONLY) : STDIN_FILENO;
504         if (io->pipe == -1)
505                 io->error = errno;
506         return io->pipe != -1;
509 static bool
510 kill_io(struct io *io)
512         return io->pid == 0 || kill(io->pid, SIGKILL) != -1;
515 static bool
516 done_io(struct io *io)
518         pid_t pid = io->pid;
520         if (io->pipe != -1)
521                 close(io->pipe);
522         free(io->buf);
523         reset_io(io);
525         while (pid > 0) {
526                 int status;
527                 pid_t waiting = waitpid(pid, &status, 0);
529                 if (waiting < 0) {
530                         if (errno == EINTR)
531                                 continue;
532                         report("waitpid failed (%s)", strerror(errno));
533                         return FALSE;
534                 }
536                 return waiting == pid &&
537                        !WIFSIGNALED(status) &&
538                        WIFEXITED(status) &&
539                        !WEXITSTATUS(status);
540         }
542         return TRUE;
545 static bool
546 start_io(struct io *io)
548         int pipefds[2] = { -1, -1 };
550         if (io->type == IO_FD)
551                 return TRUE;
553         if ((io->type == IO_RD || io->type == IO_WR) &&
554             pipe(pipefds) < 0)
555                 return FALSE;
556         else if (io->type == IO_AP)
557                 pipefds[1] = io->pipe;
559         if ((io->pid = fork())) {
560                 if (pipefds[!(io->type == IO_WR)] != -1)
561                         close(pipefds[!(io->type == IO_WR)]);
562                 if (io->pid != -1) {
563                         io->pipe = pipefds[!!(io->type == IO_WR)];
564                         return TRUE;
565                 }
567         } else {
568                 if (io->type != IO_FG) {
569                         int devnull = open("/dev/null", O_RDWR);
570                         int readfd  = io->type == IO_WR ? pipefds[0] : devnull;
571                         int writefd = (io->type == IO_RD || io->type == IO_AP)
572                                                         ? pipefds[1] : devnull;
574                         dup2(readfd,  STDIN_FILENO);
575                         dup2(writefd, STDOUT_FILENO);
576                         dup2(devnull, STDERR_FILENO);
578                         close(devnull);
579                         if (pipefds[0] != -1)
580                                 close(pipefds[0]);
581                         if (pipefds[1] != -1)
582                                 close(pipefds[1]);
583                 }
585                 if (io->dir && *io->dir && chdir(io->dir) == -1)
586                         die("Failed to change directory: %s", strerror(errno));
588                 execvp(io->argv[0], (char *const*) io->argv);
589                 die("Failed to execute program: %s", strerror(errno));
590         }
592         if (pipefds[!!(io->type == IO_WR)] != -1)
593                 close(pipefds[!!(io->type == IO_WR)]);
594         return FALSE;
597 static bool
598 run_io(struct io *io, const char **argv, const char *dir, enum io_type type)
600         init_io(io, dir, type);
601         if (!format_argv(io->argv, argv, FORMAT_NONE))
602                 return FALSE;
603         return start_io(io);
606 static int
607 run_io_do(struct io *io)
609         return start_io(io) && done_io(io);
612 static int
613 run_io_bg(const char **argv)
615         struct io io = {};
617         init_io(&io, NULL, IO_BG);
618         if (!format_argv(io.argv, argv, FORMAT_NONE))
619                 return FALSE;
620         return run_io_do(&io);
623 static bool
624 run_io_fg(const char **argv, const char *dir)
626         struct io io = {};
628         init_io(&io, dir, IO_FG);
629         if (!format_argv(io.argv, argv, FORMAT_NONE))
630                 return FALSE;
631         return run_io_do(&io);
634 static bool
635 run_io_append(const char **argv, enum format_flags flags, int fd)
637         struct io io = {};
639         init_io(&io, NULL, IO_AP);
640         io.pipe = fd;
641         if (format_argv(io.argv, argv, flags))
642                 return run_io_do(&io);
643         close(fd);
644         return FALSE;
647 static bool
648 run_io_rd(struct io *io, const char **argv, const char *dir, enum format_flags flags)
650         return init_io_rd(io, argv, dir, flags) && start_io(io);
653 static bool
654 io_eof(struct io *io)
656         return io->eof;
659 static int
660 io_error(struct io *io)
662         return io->error;
665 static char *
666 io_strerror(struct io *io)
668         return strerror(io->error);
671 static bool
672 io_can_read(struct io *io)
674         struct timeval tv = { 0, 500 };
675         fd_set fds;
677         FD_ZERO(&fds);
678         FD_SET(io->pipe, &fds);
680         return select(io->pipe + 1, &fds, NULL, NULL, &tv) > 0;
683 static ssize_t
684 io_read(struct io *io, void *buf, size_t bufsize)
686         do {
687                 ssize_t readsize = read(io->pipe, buf, bufsize);
689                 if (readsize < 0 && (errno == EAGAIN || errno == EINTR))
690                         continue;
691                 else if (readsize == -1)
692                         io->error = errno;
693                 else if (readsize == 0)
694                         io->eof = 1;
695                 return readsize;
696         } while (1);
699 DEFINE_ALLOCATOR(realloc_io_buf, char, BUFSIZ)
701 static char *
702 io_get(struct io *io, int c, bool can_read)
704         char *eol;
705         ssize_t readsize;
707         while (TRUE) {
708                 if (io->bufsize > 0) {
709                         eol = memchr(io->bufpos, c, io->bufsize);
710                         if (eol) {
711                                 char *line = io->bufpos;
713                                 *eol = 0;
714                                 io->bufpos = eol + 1;
715                                 io->bufsize -= io->bufpos - line;
716                                 return line;
717                         }
718                 }
720                 if (io_eof(io)) {
721                         if (io->bufsize) {
722                                 io->bufpos[io->bufsize] = 0;
723                                 io->bufsize = 0;
724                                 return io->bufpos;
725                         }
726                         return NULL;
727                 }
729                 if (!can_read)
730                         return NULL;
732                 if (io->bufsize > 0 && io->bufpos > io->buf)
733                         memmove(io->buf, io->bufpos, io->bufsize);
735                 if (io->bufalloc == io->bufsize) {
736                         if (!realloc_io_buf(&io->buf, io->bufalloc, BUFSIZ))
737                                 return NULL;
738                         io->bufalloc += BUFSIZ;
739                 }
741                 io->bufpos = io->buf;
742                 readsize = io_read(io, io->buf + io->bufsize, io->bufalloc - io->bufsize);
743                 if (io_error(io))
744                         return NULL;
745                 io->bufsize += readsize;
746         }
749 static bool
750 io_write(struct io *io, const void *buf, size_t bufsize)
752         size_t written = 0;
754         while (!io_error(io) && written < bufsize) {
755                 ssize_t size;
757                 size = write(io->pipe, buf + written, bufsize - written);
758                 if (size < 0 && (errno == EAGAIN || errno == EINTR))
759                         continue;
760                 else if (size == -1)
761                         io->error = errno;
762                 else
763                         written += size;
764         }
766         return written == bufsize;
769 static bool
770 io_read_buf(struct io *io, char buf[], size_t bufsize)
772         char *result = io_get(io, '\n', TRUE);
774         if (result) {
775                 result = chomp_string(result);
776                 string_ncopy_do(buf, bufsize, result, strlen(result));
777         }
779         return done_io(io) && result;
782 static bool
783 run_io_buf(const char **argv, char buf[], size_t bufsize)
785         struct io io = {};
787         return run_io_rd(&io, argv, NULL, FORMAT_NONE)
788             && io_read_buf(&io, buf, bufsize);
791 static int
792 io_load(struct io *io, const char *separators,
793         int (*read_property)(char *, size_t, char *, size_t))
795         char *name;
796         int state = OK;
798         if (!start_io(io))
799                 return ERR;
801         while (state == OK && (name = io_get(io, '\n', TRUE))) {
802                 char *value;
803                 size_t namelen;
804                 size_t valuelen;
806                 name = chomp_string(name);
807                 namelen = strcspn(name, separators);
809                 if (name[namelen]) {
810                         name[namelen] = 0;
811                         value = chomp_string(name + namelen + 1);
812                         valuelen = strlen(value);
814                 } else {
815                         value = "";
816                         valuelen = 0;
817                 }
819                 state = read_property(name, namelen, value, valuelen);
820         }
822         if (state != ERR && io_error(io))
823                 state = ERR;
824         done_io(io);
826         return state;
829 static int
830 run_io_load(const char **argv, const char *separators,
831             int (*read_property)(char *, size_t, char *, size_t))
833         struct io io = {};
835         return init_io_rd(&io, argv, NULL, FORMAT_NONE)
836                 ? io_load(&io, separators, read_property) : ERR;
840 /*
841  * User requests
842  */
844 #define REQ_INFO \
845         /* XXX: Keep the view request first and in sync with views[]. */ \
846         REQ_GROUP("View switching") \
847         REQ_(VIEW_MAIN,         "Show main view"), \
848         REQ_(VIEW_DIFF,         "Show diff view"), \
849         REQ_(VIEW_LOG,          "Show log view"), \
850         REQ_(VIEW_TREE,         "Show tree view"), \
851         REQ_(VIEW_BLOB,         "Show blob view"), \
852         REQ_(VIEW_BLAME,        "Show blame view"), \
853         REQ_(VIEW_BRANCH,       "Show branch view"), \
854         REQ_(VIEW_HELP,         "Show help page"), \
855         REQ_(VIEW_PAGER,        "Show pager view"), \
856         REQ_(VIEW_STATUS,       "Show status view"), \
857         REQ_(VIEW_STAGE,        "Show stage view"), \
858         \
859         REQ_GROUP("View manipulation") \
860         REQ_(ENTER,             "Enter current line and scroll"), \
861         REQ_(NEXT,              "Move to next"), \
862         REQ_(PREVIOUS,          "Move to previous"), \
863         REQ_(PARENT,            "Move to parent"), \
864         REQ_(VIEW_NEXT,         "Move focus to next view"), \
865         REQ_(REFRESH,           "Reload and refresh"), \
866         REQ_(MAXIMIZE,          "Maximize the current view"), \
867         REQ_(VIEW_CLOSE,        "Close the current view"), \
868         REQ_(QUIT,              "Close all views and quit"), \
869         \
870         REQ_GROUP("View specific requests") \
871         REQ_(STATUS_UPDATE,     "Update file status"), \
872         REQ_(STATUS_REVERT,     "Revert file changes"), \
873         REQ_(STATUS_MERGE,      "Merge file using external tool"), \
874         REQ_(STAGE_NEXT,        "Find next chunk to stage"), \
875         \
876         REQ_GROUP("Cursor navigation") \
877         REQ_(MOVE_UP,           "Move cursor one line up"), \
878         REQ_(MOVE_DOWN,         "Move cursor one line down"), \
879         REQ_(MOVE_PAGE_DOWN,    "Move cursor one page down"), \
880         REQ_(MOVE_PAGE_UP,      "Move cursor one page up"), \
881         REQ_(MOVE_FIRST_LINE,   "Move cursor to first line"), \
882         REQ_(MOVE_LAST_LINE,    "Move cursor to last line"), \
883         \
884         REQ_GROUP("Scrolling") \
885         REQ_(SCROLL_LEFT,       "Scroll two columns left"), \
886         REQ_(SCROLL_RIGHT,      "Scroll two columns right"), \
887         REQ_(SCROLL_LINE_UP,    "Scroll one line up"), \
888         REQ_(SCROLL_LINE_DOWN,  "Scroll one line down"), \
889         REQ_(SCROLL_PAGE_UP,    "Scroll one page up"), \
890         REQ_(SCROLL_PAGE_DOWN,  "Scroll one page down"), \
891         \
892         REQ_GROUP("Searching") \
893         REQ_(SEARCH,            "Search the view"), \
894         REQ_(SEARCH_BACK,       "Search backwards in the view"), \
895         REQ_(FIND_NEXT,         "Find next search match"), \
896         REQ_(FIND_PREV,         "Find previous search match"), \
897         \
898         REQ_GROUP("Option manipulation") \
899         REQ_(OPTIONS,           "Open option menu"), \
900         REQ_(TOGGLE_LINENO,     "Toggle line numbers"), \
901         REQ_(TOGGLE_DATE,       "Toggle date display"), \
902         REQ_(TOGGLE_DATE_SHORT, "Toggle short (date-only) dates"), \
903         REQ_(TOGGLE_AUTHOR,     "Toggle author display"), \
904         REQ_(TOGGLE_REV_GRAPH,  "Toggle revision graph visualization"), \
905         REQ_(TOGGLE_REFS,       "Toggle reference display (tags/branches)"), \
906         REQ_(TOGGLE_SORT_ORDER, "Toggle ascending/descending sort order"), \
907         REQ_(TOGGLE_SORT_FIELD, "Toggle field to sort by"), \
908         \
909         REQ_GROUP("Misc") \
910         REQ_(PROMPT,            "Bring up the prompt"), \
911         REQ_(SCREEN_REDRAW,     "Redraw the screen"), \
912         REQ_(SHOW_VERSION,      "Show version information"), \
913         REQ_(STOP_LOADING,      "Stop all loading views"), \
914         REQ_(EDIT,              "Open in editor"), \
915         REQ_(NONE,              "Do nothing")
918 /* User action requests. */
919 enum request {
920 #define REQ_GROUP(help)
921 #define REQ_(req, help) REQ_##req
923         /* Offset all requests to avoid conflicts with ncurses getch values. */
924         REQ_OFFSET = KEY_MAX + 1,
925         REQ_INFO
927 #undef  REQ_GROUP
928 #undef  REQ_
929 };
931 struct request_info {
932         enum request request;
933         const char *name;
934         int namelen;
935         const char *help;
936 };
938 static const struct request_info req_info[] = {
939 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
940 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
941         REQ_INFO
942 #undef  REQ_GROUP
943 #undef  REQ_
944 };
946 static enum request
947 get_request(const char *name)
949         int namelen = strlen(name);
950         int i;
952         for (i = 0; i < ARRAY_SIZE(req_info); i++)
953                 if (req_info[i].namelen == namelen &&
954                     !string_enum_compare(req_info[i].name, name, namelen))
955                         return req_info[i].request;
957         return REQ_NONE;
961 /*
962  * Options
963  */
965 /* Option and state variables. */
966 static enum date opt_date               = DATE_DEFAULT;
967 static bool opt_author                  = TRUE;
968 static bool opt_line_number             = FALSE;
969 static bool opt_line_graphics           = TRUE;
970 static bool opt_rev_graph               = FALSE;
971 static bool opt_show_refs               = TRUE;
972 static int opt_num_interval             = 5;
973 static double opt_hscroll               = 0.50;
974 static double opt_scale_split_view      = 2.0 / 3.0;
975 static int opt_tab_size                 = 8;
976 static int opt_author_cols              = 19;
977 static char opt_path[SIZEOF_STR]        = "";
978 static char opt_file[SIZEOF_STR]        = "";
979 static char opt_ref[SIZEOF_REF]         = "";
980 static char opt_head[SIZEOF_REF]        = "";
981 static char opt_head_rev[SIZEOF_REV]    = "";
982 static char opt_remote[SIZEOF_REF]      = "";
983 static char opt_encoding[20]            = "UTF-8";
984 static bool opt_utf8                    = TRUE;
985 static char opt_codeset[20]             = "UTF-8";
986 static iconv_t opt_iconv                = ICONV_NONE;
987 static char opt_search[SIZEOF_STR]      = "";
988 static char opt_cdup[SIZEOF_STR]        = "";
989 static char opt_prefix[SIZEOF_STR]      = "";
990 static char opt_git_dir[SIZEOF_STR]     = "";
991 static signed char opt_is_inside_work_tree      = -1; /* set to TRUE or FALSE */
992 static char opt_editor[SIZEOF_STR]      = "";
993 static FILE *opt_tty                    = NULL;
995 #define is_initial_commit()     (!*opt_head_rev)
996 #define is_head_commit(rev)     (!strcmp((rev), "HEAD") || !strcmp(opt_head_rev, (rev)))
997 #define mkdate(time)            string_date(time, opt_date)
1000 /*
1001  * Line-oriented content detection.
1002  */
1004 #define LINE_INFO \
1005 LINE(DIFF_HEADER,  "diff --git ",       COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1006 LINE(DIFF_CHUNK,   "@@",                COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1007 LINE(DIFF_ADD,     "+",                 COLOR_GREEN,    COLOR_DEFAULT,  0), \
1008 LINE(DIFF_DEL,     "-",                 COLOR_RED,      COLOR_DEFAULT,  0), \
1009 LINE(DIFF_INDEX,        "index ",         COLOR_BLUE,   COLOR_DEFAULT,  0), \
1010 LINE(DIFF_OLDMODE,      "old file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
1011 LINE(DIFF_NEWMODE,      "new file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
1012 LINE(DIFF_COPY_FROM,    "copy from",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
1013 LINE(DIFF_COPY_TO,      "copy to",        COLOR_YELLOW, COLOR_DEFAULT,  0), \
1014 LINE(DIFF_RENAME_FROM,  "rename from",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
1015 LINE(DIFF_RENAME_TO,    "rename to",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
1016 LINE(DIFF_SIMILARITY,   "similarity ",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
1017 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
1018 LINE(DIFF_TREE,         "diff-tree ",     COLOR_BLUE,   COLOR_DEFAULT,  0), \
1019 LINE(PP_AUTHOR,    "Author: ",          COLOR_CYAN,     COLOR_DEFAULT,  0), \
1020 LINE(PP_COMMIT,    "Commit: ",          COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1021 LINE(PP_MERGE,     "Merge: ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
1022 LINE(PP_DATE,      "Date:   ",          COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1023 LINE(PP_ADATE,     "AuthorDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1024 LINE(PP_CDATE,     "CommitDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1025 LINE(PP_REFS,      "Refs: ",            COLOR_RED,      COLOR_DEFAULT,  0), \
1026 LINE(COMMIT,       "commit ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
1027 LINE(PARENT,       "parent ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
1028 LINE(TREE,         "tree ",             COLOR_BLUE,     COLOR_DEFAULT,  0), \
1029 LINE(AUTHOR,       "author ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
1030 LINE(COMMITTER,    "committer ",        COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1031 LINE(SIGNOFF,      "    Signed-off-by", COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1032 LINE(ACKED,        "    Acked-by",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1033 LINE(DEFAULT,      "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
1034 LINE(CURSOR,       "",                  COLOR_WHITE,    COLOR_GREEN,    A_BOLD), \
1035 LINE(STATUS,       "",                  COLOR_GREEN,    COLOR_DEFAULT,  0), \
1036 LINE(DELIMITER,    "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1037 LINE(DATE,         "",                  COLOR_BLUE,     COLOR_DEFAULT,  0), \
1038 LINE(MODE,         "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1039 LINE(LINE_NUMBER,  "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1040 LINE(TITLE_BLUR,   "",                  COLOR_WHITE,    COLOR_BLUE,     0), \
1041 LINE(TITLE_FOCUS,  "",                  COLOR_WHITE,    COLOR_BLUE,     A_BOLD), \
1042 LINE(MAIN_COMMIT,  "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
1043 LINE(MAIN_TAG,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  A_BOLD), \
1044 LINE(MAIN_LOCAL_TAG,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1045 LINE(MAIN_REMOTE,  "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1046 LINE(MAIN_TRACKED, "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_BOLD), \
1047 LINE(MAIN_REF,     "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1048 LINE(MAIN_HEAD,    "",                  COLOR_CYAN,     COLOR_DEFAULT,  A_BOLD), \
1049 LINE(MAIN_REVGRAPH,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1050 LINE(TREE_HEAD,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_BOLD), \
1051 LINE(TREE_DIR,     "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_NORMAL), \
1052 LINE(TREE_FILE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
1053 LINE(STAT_HEAD,    "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1054 LINE(STAT_SECTION, "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1055 LINE(STAT_NONE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
1056 LINE(STAT_STAGED,  "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1057 LINE(STAT_UNSTAGED,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1058 LINE(STAT_UNTRACKED,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1059 LINE(HELP_KEYMAP,  "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1060 LINE(HELP_GROUP,   "",                  COLOR_BLUE,     COLOR_DEFAULT,  0), \
1061 LINE(BLAME_ID,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0)
1063 enum line_type {
1064 #define LINE(type, line, fg, bg, attr) \
1065         LINE_##type
1066         LINE_INFO,
1067         LINE_NONE
1068 #undef  LINE
1069 };
1071 struct line_info {
1072         const char *name;       /* Option name. */
1073         int namelen;            /* Size of option name. */
1074         const char *line;       /* The start of line to match. */
1075         int linelen;            /* Size of string to match. */
1076         int fg, bg, attr;       /* Color and text attributes for the lines. */
1077 };
1079 static struct line_info line_info[] = {
1080 #define LINE(type, line, fg, bg, attr) \
1081         { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
1082         LINE_INFO
1083 #undef  LINE
1084 };
1086 static enum line_type
1087 get_line_type(const char *line)
1089         int linelen = strlen(line);
1090         enum line_type type;
1092         for (type = 0; type < ARRAY_SIZE(line_info); type++)
1093                 /* Case insensitive search matches Signed-off-by lines better. */
1094                 if (linelen >= line_info[type].linelen &&
1095                     !strncasecmp(line_info[type].line, line, line_info[type].linelen))
1096                         return type;
1098         return LINE_DEFAULT;
1101 static inline int
1102 get_line_attr(enum line_type type)
1104         assert(type < ARRAY_SIZE(line_info));
1105         return COLOR_PAIR(type) | line_info[type].attr;
1108 static struct line_info *
1109 get_line_info(const char *name)
1111         size_t namelen = strlen(name);
1112         enum line_type type;
1114         for (type = 0; type < ARRAY_SIZE(line_info); type++)
1115                 if (namelen == line_info[type].namelen &&
1116                     !string_enum_compare(line_info[type].name, name, namelen))
1117                         return &line_info[type];
1119         return NULL;
1122 static void
1123 init_colors(void)
1125         int default_bg = line_info[LINE_DEFAULT].bg;
1126         int default_fg = line_info[LINE_DEFAULT].fg;
1127         enum line_type type;
1129         start_color();
1131         if (assume_default_colors(default_fg, default_bg) == ERR) {
1132                 default_bg = COLOR_BLACK;
1133                 default_fg = COLOR_WHITE;
1134         }
1136         for (type = 0; type < ARRAY_SIZE(line_info); type++) {
1137                 struct line_info *info = &line_info[type];
1138                 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
1139                 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
1141                 init_pair(type, fg, bg);
1142         }
1145 struct line {
1146         enum line_type type;
1148         /* State flags */
1149         unsigned int selected:1;
1150         unsigned int dirty:1;
1151         unsigned int cleareol:1;
1152         unsigned int other:16;
1154         void *data;             /* User data */
1155 };
1158 /*
1159  * Keys
1160  */
1162 struct keybinding {
1163         int alias;
1164         enum request request;
1165 };
1167 static const struct keybinding default_keybindings[] = {
1168         /* View switching */
1169         { 'm',          REQ_VIEW_MAIN },
1170         { 'd',          REQ_VIEW_DIFF },
1171         { 'l',          REQ_VIEW_LOG },
1172         { 't',          REQ_VIEW_TREE },
1173         { 'f',          REQ_VIEW_BLOB },
1174         { 'B',          REQ_VIEW_BLAME },
1175         { 'H',          REQ_VIEW_BRANCH },
1176         { 'p',          REQ_VIEW_PAGER },
1177         { 'h',          REQ_VIEW_HELP },
1178         { 'S',          REQ_VIEW_STATUS },
1179         { 'c',          REQ_VIEW_STAGE },
1181         /* View manipulation */
1182         { 'q',          REQ_VIEW_CLOSE },
1183         { KEY_TAB,      REQ_VIEW_NEXT },
1184         { KEY_RETURN,   REQ_ENTER },
1185         { KEY_UP,       REQ_PREVIOUS },
1186         { KEY_DOWN,     REQ_NEXT },
1187         { 'R',          REQ_REFRESH },
1188         { KEY_F(5),     REQ_REFRESH },
1189         { 'O',          REQ_MAXIMIZE },
1191         /* Cursor navigation */
1192         { 'k',          REQ_MOVE_UP },
1193         { 'j',          REQ_MOVE_DOWN },
1194         { KEY_HOME,     REQ_MOVE_FIRST_LINE },
1195         { KEY_END,      REQ_MOVE_LAST_LINE },
1196         { KEY_NPAGE,    REQ_MOVE_PAGE_DOWN },
1197         { ' ',          REQ_MOVE_PAGE_DOWN },
1198         { KEY_PPAGE,    REQ_MOVE_PAGE_UP },
1199         { 'b',          REQ_MOVE_PAGE_UP },
1200         { '-',          REQ_MOVE_PAGE_UP },
1202         /* Scrolling */
1203         { KEY_LEFT,     REQ_SCROLL_LEFT },
1204         { KEY_RIGHT,    REQ_SCROLL_RIGHT },
1205         { KEY_IC,       REQ_SCROLL_LINE_UP },
1206         { KEY_DC,       REQ_SCROLL_LINE_DOWN },
1207         { 'w',          REQ_SCROLL_PAGE_UP },
1208         { 's',          REQ_SCROLL_PAGE_DOWN },
1210         /* Searching */
1211         { '/',          REQ_SEARCH },
1212         { '?',          REQ_SEARCH_BACK },
1213         { 'n',          REQ_FIND_NEXT },
1214         { 'N',          REQ_FIND_PREV },
1216         /* Misc */
1217         { 'Q',          REQ_QUIT },
1218         { 'z',          REQ_STOP_LOADING },
1219         { 'v',          REQ_SHOW_VERSION },
1220         { 'r',          REQ_SCREEN_REDRAW },
1221         { 'o',          REQ_OPTIONS },
1222         { '.',          REQ_TOGGLE_LINENO },
1223         { 'D',          REQ_TOGGLE_DATE },
1224         { 'A',          REQ_TOGGLE_AUTHOR },
1225         { 'g',          REQ_TOGGLE_REV_GRAPH },
1226         { 'F',          REQ_TOGGLE_REFS },
1227         { 'I',          REQ_TOGGLE_SORT_ORDER },
1228         { 'i',          REQ_TOGGLE_SORT_FIELD },
1229         { ':',          REQ_PROMPT },
1230         { 'u',          REQ_STATUS_UPDATE },
1231         { '!',          REQ_STATUS_REVERT },
1232         { 'M',          REQ_STATUS_MERGE },
1233         { '@',          REQ_STAGE_NEXT },
1234         { ',',          REQ_PARENT },
1235         { 'e',          REQ_EDIT },
1236 };
1238 #define KEYMAP_INFO \
1239         KEYMAP_(GENERIC), \
1240         KEYMAP_(MAIN), \
1241         KEYMAP_(DIFF), \
1242         KEYMAP_(LOG), \
1243         KEYMAP_(TREE), \
1244         KEYMAP_(BLOB), \
1245         KEYMAP_(BLAME), \
1246         KEYMAP_(BRANCH), \
1247         KEYMAP_(PAGER), \
1248         KEYMAP_(HELP), \
1249         KEYMAP_(STATUS), \
1250         KEYMAP_(STAGE)
1252 enum keymap {
1253 #define KEYMAP_(name) KEYMAP_##name
1254         KEYMAP_INFO
1255 #undef  KEYMAP_
1256 };
1258 static const struct enum_map keymap_table[] = {
1259 #define KEYMAP_(name) ENUM_MAP(#name, KEYMAP_##name)
1260         KEYMAP_INFO
1261 #undef  KEYMAP_
1262 };
1264 #define set_keymap(map, name) map_enum(map, keymap_table, name)
1266 struct keybinding_table {
1267         struct keybinding *data;
1268         size_t size;
1269 };
1271 static struct keybinding_table keybindings[ARRAY_SIZE(keymap_table)];
1273 static void
1274 add_keybinding(enum keymap keymap, enum request request, int key)
1276         struct keybinding_table *table = &keybindings[keymap];
1278         table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data));
1279         if (!table->data)
1280                 die("Failed to allocate keybinding");
1281         table->data[table->size].alias = key;
1282         table->data[table->size++].request = request;
1285 /* Looks for a key binding first in the given map, then in the generic map, and
1286  * lastly in the default keybindings. */
1287 static enum request
1288 get_keybinding(enum keymap keymap, int key)
1290         size_t i;
1292         for (i = 0; i < keybindings[keymap].size; i++)
1293                 if (keybindings[keymap].data[i].alias == key)
1294                         return keybindings[keymap].data[i].request;
1296         for (i = 0; i < keybindings[KEYMAP_GENERIC].size; i++)
1297                 if (keybindings[KEYMAP_GENERIC].data[i].alias == key)
1298                         return keybindings[KEYMAP_GENERIC].data[i].request;
1300         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1301                 if (default_keybindings[i].alias == key)
1302                         return default_keybindings[i].request;
1304         return (enum request) key;
1308 struct key {
1309         const char *name;
1310         int value;
1311 };
1313 static const struct key key_table[] = {
1314         { "Enter",      KEY_RETURN },
1315         { "Space",      ' ' },
1316         { "Backspace",  KEY_BACKSPACE },
1317         { "Tab",        KEY_TAB },
1318         { "Escape",     KEY_ESC },
1319         { "Left",       KEY_LEFT },
1320         { "Right",      KEY_RIGHT },
1321         { "Up",         KEY_UP },
1322         { "Down",       KEY_DOWN },
1323         { "Insert",     KEY_IC },
1324         { "Delete",     KEY_DC },
1325         { "Hash",       '#' },
1326         { "Home",       KEY_HOME },
1327         { "End",        KEY_END },
1328         { "PageUp",     KEY_PPAGE },
1329         { "PageDown",   KEY_NPAGE },
1330         { "F1",         KEY_F(1) },
1331         { "F2",         KEY_F(2) },
1332         { "F3",         KEY_F(3) },
1333         { "F4",         KEY_F(4) },
1334         { "F5",         KEY_F(5) },
1335         { "F6",         KEY_F(6) },
1336         { "F7",         KEY_F(7) },
1337         { "F8",         KEY_F(8) },
1338         { "F9",         KEY_F(9) },
1339         { "F10",        KEY_F(10) },
1340         { "F11",        KEY_F(11) },
1341         { "F12",        KEY_F(12) },
1342 };
1344 static int
1345 get_key_value(const char *name)
1347         int i;
1349         for (i = 0; i < ARRAY_SIZE(key_table); i++)
1350                 if (!strcasecmp(key_table[i].name, name))
1351                         return key_table[i].value;
1353         if (strlen(name) == 1 && isprint(*name))
1354                 return (int) *name;
1356         return ERR;
1359 static const char *
1360 get_key_name(int key_value)
1362         static char key_char[] = "'X'";
1363         const char *seq = NULL;
1364         int key;
1366         for (key = 0; key < ARRAY_SIZE(key_table); key++)
1367                 if (key_table[key].value == key_value)
1368                         seq = key_table[key].name;
1370         if (seq == NULL &&
1371             key_value < 127 &&
1372             isprint(key_value)) {
1373                 key_char[1] = (char) key_value;
1374                 seq = key_char;
1375         }
1377         return seq ? seq : "(no key)";
1380 static bool
1381 append_key(char *buf, size_t *pos, const struct keybinding *keybinding)
1383         const char *sep = *pos > 0 ? ", " : "";
1384         const char *keyname = get_key_name(keybinding->alias);
1386         return string_nformat(buf, BUFSIZ, pos, "%s%s", sep, keyname);
1389 static bool
1390 append_keymap_request_keys(char *buf, size_t *pos, enum request request,
1391                            enum keymap keymap, bool all)
1393         int i;
1395         for (i = 0; i < keybindings[keymap].size; i++) {
1396                 if (keybindings[keymap].data[i].request == request) {
1397                         if (!append_key(buf, pos, &keybindings[keymap].data[i]))
1398                                 return FALSE;
1399                         if (!all)
1400                                 break;
1401                 }
1402         }
1404         return TRUE;
1407 #define get_key(keymap, request) get_keys(keymap, request, FALSE)
1409 static const char *
1410 get_keys(enum keymap keymap, enum request request, bool all)
1412         static char buf[BUFSIZ];
1413         size_t pos = 0;
1414         int i;
1416         buf[pos] = 0;
1418         if (!append_keymap_request_keys(buf, &pos, request, keymap, all))
1419                 return "Too many keybindings!";
1420         if (pos > 0 && !all)
1421                 return buf;
1423         if (keymap != KEYMAP_GENERIC) {
1424                 /* Only the generic keymap includes the default keybindings when
1425                  * listing all keys. */
1426                 if (all)
1427                         return buf;
1429                 if (!append_keymap_request_keys(buf, &pos, request, KEYMAP_GENERIC, all))
1430                         return "Too many keybindings!";
1431                 if (pos)
1432                         return buf;
1433         }
1435         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1436                 if (default_keybindings[i].request == request) {
1437                         if (!append_key(buf, &pos, &default_keybindings[i]))
1438                                 return "Too many keybindings!";
1439                         if (!all)
1440                                 return buf;
1441                 }
1442         }
1444         return buf;
1447 struct run_request {
1448         enum keymap keymap;
1449         int key;
1450         const char *argv[SIZEOF_ARG];
1451 };
1453 static struct run_request *run_request;
1454 static size_t run_requests;
1456 DEFINE_ALLOCATOR(realloc_run_requests, struct run_request, 8)
1458 static enum request
1459 add_run_request(enum keymap keymap, int key, int argc, const char **argv)
1461         struct run_request *req;
1463         if (argc >= ARRAY_SIZE(req->argv) - 1)
1464                 return REQ_NONE;
1466         if (!realloc_run_requests(&run_request, run_requests, 1))
1467                 return REQ_NONE;
1469         req = &run_request[run_requests];
1470         req->keymap = keymap;
1471         req->key = key;
1472         req->argv[0] = NULL;
1474         if (!format_argv(req->argv, argv, FORMAT_NONE))
1475                 return REQ_NONE;
1477         return REQ_NONE + ++run_requests;
1480 static struct run_request *
1481 get_run_request(enum request request)
1483         if (request <= REQ_NONE)
1484                 return NULL;
1485         return &run_request[request - REQ_NONE - 1];
1488 static void
1489 add_builtin_run_requests(void)
1491         const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1492         const char *commit[] = { "git", "commit", NULL };
1493         const char *gc[] = { "git", "gc", NULL };
1494         struct {
1495                 enum keymap keymap;
1496                 int key;
1497                 int argc;
1498                 const char **argv;
1499         } reqs[] = {
1500                 { KEYMAP_MAIN,    'C', ARRAY_SIZE(cherry_pick) - 1, cherry_pick },
1501                 { KEYMAP_STATUS,  'C', ARRAY_SIZE(commit) - 1, commit },
1502                 { KEYMAP_GENERIC, 'G', ARRAY_SIZE(gc) - 1, gc },
1503         };
1504         int i;
1506         for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1507                 enum request req;
1509                 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argc, reqs[i].argv);
1510                 if (req != REQ_NONE)
1511                         add_keybinding(reqs[i].keymap, req, reqs[i].key);
1512         }
1515 /*
1516  * User config file handling.
1517  */
1519 static int   config_lineno;
1520 static bool  config_errors;
1521 static const char *config_msg;
1523 static const struct enum_map color_map[] = {
1524 #define COLOR_MAP(name) ENUM_MAP(#name, COLOR_##name)
1525         COLOR_MAP(DEFAULT),
1526         COLOR_MAP(BLACK),
1527         COLOR_MAP(BLUE),
1528         COLOR_MAP(CYAN),
1529         COLOR_MAP(GREEN),
1530         COLOR_MAP(MAGENTA),
1531         COLOR_MAP(RED),
1532         COLOR_MAP(WHITE),
1533         COLOR_MAP(YELLOW),
1534 };
1536 static const struct enum_map attr_map[] = {
1537 #define ATTR_MAP(name) ENUM_MAP(#name, A_##name)
1538         ATTR_MAP(NORMAL),
1539         ATTR_MAP(BLINK),
1540         ATTR_MAP(BOLD),
1541         ATTR_MAP(DIM),
1542         ATTR_MAP(REVERSE),
1543         ATTR_MAP(STANDOUT),
1544         ATTR_MAP(UNDERLINE),
1545 };
1547 #define set_attribute(attr, name)       map_enum(attr, attr_map, name)
1549 static int parse_step(double *opt, const char *arg)
1551         *opt = atoi(arg);
1552         if (!strchr(arg, '%'))
1553                 return OK;
1555         /* "Shift down" so 100% and 1 does not conflict. */
1556         *opt = (*opt - 1) / 100;
1557         if (*opt >= 1.0) {
1558                 *opt = 0.99;
1559                 config_msg = "Step value larger than 100%";
1560                 return ERR;
1561         }
1562         if (*opt < 0.0) {
1563                 *opt = 1;
1564                 config_msg = "Invalid step value";
1565                 return ERR;
1566         }
1567         return OK;
1570 static int
1571 parse_int(int *opt, const char *arg, int min, int max)
1573         int value = atoi(arg);
1575         if (min <= value && value <= max) {
1576                 *opt = value;
1577                 return OK;
1578         }
1580         config_msg = "Integer value out of bound";
1581         return ERR;
1584 static bool
1585 set_color(int *color, const char *name)
1587         if (map_enum(color, color_map, name))
1588                 return TRUE;
1589         if (!prefixcmp(name, "color"))
1590                 return parse_int(color, name + 5, 0, 255) == OK;
1591         return FALSE;
1594 /* Wants: object fgcolor bgcolor [attribute] */
1595 static int
1596 option_color_command(int argc, const char *argv[])
1598         struct line_info *info;
1600         if (argc < 3) {
1601                 config_msg = "Wrong number of arguments given to color command";
1602                 return ERR;
1603         }
1605         info = get_line_info(argv[0]);
1606         if (!info) {
1607                 static const struct enum_map obsolete[] = {
1608                         ENUM_MAP("main-delim",  LINE_DELIMITER),
1609                         ENUM_MAP("main-date",   LINE_DATE),
1610                         ENUM_MAP("main-author", LINE_AUTHOR),
1611                 };
1612                 int index;
1614                 if (!map_enum(&index, obsolete, argv[0])) {
1615                         config_msg = "Unknown color name";
1616                         return ERR;
1617                 }
1618                 info = &line_info[index];
1619         }
1621         if (!set_color(&info->fg, argv[1]) ||
1622             !set_color(&info->bg, argv[2])) {
1623                 config_msg = "Unknown color";
1624                 return ERR;
1625         }
1627         info->attr = 0;
1628         while (argc-- > 3) {
1629                 int attr;
1631                 if (!set_attribute(&attr, argv[argc])) {
1632                         config_msg = "Unknown attribute";
1633                         return ERR;
1634                 }
1635                 info->attr |= attr;
1636         }
1638         return OK;
1641 static int parse_bool(bool *opt, const char *arg)
1643         *opt = (!strcmp(arg, "1") || !strcmp(arg, "true") || !strcmp(arg, "yes"))
1644                 ? TRUE : FALSE;
1645         return OK;
1648 static int
1649 parse_string(char *opt, const char *arg, size_t optsize)
1651         int arglen = strlen(arg);
1653         switch (arg[0]) {
1654         case '\"':
1655         case '\'':
1656                 if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1657                         config_msg = "Unmatched quotation";
1658                         return ERR;
1659                 }
1660                 arg += 1; arglen -= 2;
1661         default:
1662                 string_ncopy_do(opt, optsize, arg, arglen);
1663                 return OK;
1664         }
1667 /* Wants: name = value */
1668 static int
1669 option_set_command(int argc, const char *argv[])
1671         if (argc != 3) {
1672                 config_msg = "Wrong number of arguments given to set command";
1673                 return ERR;
1674         }
1676         if (strcmp(argv[1], "=")) {
1677                 config_msg = "No value assigned";
1678                 return ERR;
1679         }
1681         if (!strcmp(argv[0], "show-author"))
1682                 return parse_bool(&opt_author, argv[2]);
1684         if (!strcmp(argv[0], "show-date")) {
1685                 bool show_date;
1687                 if (!strcmp(argv[2], "relative")) {
1688                         opt_date = DATE_RELATIVE;
1689                         return OK;
1690                 } else if (!strcmp(argv[2], "short")) {
1691                         opt_date = DATE_SHORT;
1692                         return OK;
1693                 } else if (parse_bool(&show_date, argv[2])) {
1694                         opt_date = show_date ? DATE_DEFAULT : DATE_NONE;
1695                 }
1696                 return ERR;
1697         }
1699         if (!strcmp(argv[0], "show-rev-graph"))
1700                 return parse_bool(&opt_rev_graph, argv[2]);
1702         if (!strcmp(argv[0], "show-refs"))
1703                 return parse_bool(&opt_show_refs, argv[2]);
1705         if (!strcmp(argv[0], "show-line-numbers"))
1706                 return parse_bool(&opt_line_number, argv[2]);
1708         if (!strcmp(argv[0], "line-graphics"))
1709                 return parse_bool(&opt_line_graphics, argv[2]);
1711         if (!strcmp(argv[0], "line-number-interval"))
1712                 return parse_int(&opt_num_interval, argv[2], 1, 1024);
1714         if (!strcmp(argv[0], "author-width"))
1715                 return parse_int(&opt_author_cols, argv[2], 0, 1024);
1717         if (!strcmp(argv[0], "horizontal-scroll"))
1718                 return parse_step(&opt_hscroll, argv[2]);
1720         if (!strcmp(argv[0], "split-view-height"))
1721                 return parse_step(&opt_scale_split_view, argv[2]);
1723         if (!strcmp(argv[0], "tab-size"))
1724                 return parse_int(&opt_tab_size, argv[2], 1, 1024);
1726         if (!strcmp(argv[0], "commit-encoding"))
1727                 return parse_string(opt_encoding, argv[2], sizeof(opt_encoding));
1729         config_msg = "Unknown variable name";
1730         return ERR;
1733 /* Wants: mode request key */
1734 static int
1735 option_bind_command(int argc, const char *argv[])
1737         enum request request;
1738         int keymap = -1;
1739         int key;
1741         if (argc < 3) {
1742                 config_msg = "Wrong number of arguments given to bind command";
1743                 return ERR;
1744         }
1746         if (set_keymap(&keymap, argv[0]) == ERR) {
1747                 config_msg = "Unknown key map";
1748                 return ERR;
1749         }
1751         key = get_key_value(argv[1]);
1752         if (key == ERR) {
1753                 config_msg = "Unknown key";
1754                 return ERR;
1755         }
1757         request = get_request(argv[2]);
1758         if (request == REQ_NONE) {
1759                 static const struct enum_map obsolete[] = {
1760                         ENUM_MAP("cherry-pick",         REQ_NONE),
1761                         ENUM_MAP("screen-resize",       REQ_NONE),
1762                         ENUM_MAP("tree-parent",         REQ_PARENT),
1763                 };
1764                 int alias;
1766                 if (map_enum(&alias, obsolete, argv[2])) {
1767                         if (alias != REQ_NONE)
1768                                 add_keybinding(keymap, alias, key);
1769                         config_msg = "Obsolete request name";
1770                         return ERR;
1771                 }
1772         }
1773         if (request == REQ_NONE && *argv[2]++ == '!')
1774                 request = add_run_request(keymap, key, argc - 2, argv + 2);
1775         if (request == REQ_NONE) {
1776                 config_msg = "Unknown request name";
1777                 return ERR;
1778         }
1780         add_keybinding(keymap, request, key);
1782         return OK;
1785 static int
1786 set_option(const char *opt, char *value)
1788         const char *argv[SIZEOF_ARG];
1789         int argc = 0;
1791         if (!argv_from_string(argv, &argc, value)) {
1792                 config_msg = "Too many option arguments";
1793                 return ERR;
1794         }
1796         if (!strcmp(opt, "color"))
1797                 return option_color_command(argc, argv);
1799         if (!strcmp(opt, "set"))
1800                 return option_set_command(argc, argv);
1802         if (!strcmp(opt, "bind"))
1803                 return option_bind_command(argc, argv);
1805         config_msg = "Unknown option command";
1806         return ERR;
1809 static int
1810 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
1812         int status = OK;
1814         config_lineno++;
1815         config_msg = "Internal error";
1817         /* Check for comment markers, since read_properties() will
1818          * only ensure opt and value are split at first " \t". */
1819         optlen = strcspn(opt, "#");
1820         if (optlen == 0)
1821                 return OK;
1823         if (opt[optlen] != 0) {
1824                 config_msg = "No option value";
1825                 status = ERR;
1827         }  else {
1828                 /* Look for comment endings in the value. */
1829                 size_t len = strcspn(value, "#");
1831                 if (len < valuelen) {
1832                         valuelen = len;
1833                         value[valuelen] = 0;
1834                 }
1836                 status = set_option(opt, value);
1837         }
1839         if (status == ERR) {
1840                 warn("Error on line %d, near '%.*s': %s",
1841                      config_lineno, (int) optlen, opt, config_msg);
1842                 config_errors = TRUE;
1843         }
1845         /* Always keep going if errors are encountered. */
1846         return OK;
1849 static void
1850 load_option_file(const char *path)
1852         struct io io = {};
1854         /* It's OK that the file doesn't exist. */
1855         if (!io_open(&io, path))
1856                 return;
1858         config_lineno = 0;
1859         config_errors = FALSE;
1861         if (io_load(&io, " \t", read_option) == ERR ||
1862             config_errors == TRUE)
1863                 warn("Errors while loading %s.", path);
1866 static int
1867 load_options(void)
1869         const char *home = getenv("HOME");
1870         const char *tigrc_user = getenv("TIGRC_USER");
1871         const char *tigrc_system = getenv("TIGRC_SYSTEM");
1872         char buf[SIZEOF_STR];
1874         add_builtin_run_requests();
1876         if (!tigrc_system)
1877                 tigrc_system = SYSCONFDIR "/tigrc";
1878         load_option_file(tigrc_system);
1880         if (!tigrc_user) {
1881                 if (!home || !string_format(buf, "%s/.tigrc", home))
1882                         return ERR;
1883                 tigrc_user = buf;
1884         }
1885         load_option_file(tigrc_user);
1887         return OK;
1891 /*
1892  * The viewer
1893  */
1895 struct view;
1896 struct view_ops;
1898 /* The display array of active views and the index of the current view. */
1899 static struct view *display[2];
1900 static unsigned int current_view;
1902 #define foreach_displayed_view(view, i) \
1903         for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1905 #define displayed_views()       (display[1] != NULL ? 2 : 1)
1907 /* Current head and commit ID */
1908 static char ref_blob[SIZEOF_REF]        = "";
1909 static char ref_commit[SIZEOF_REF]      = "HEAD";
1910 static char ref_head[SIZEOF_REF]        = "HEAD";
1912 struct view {
1913         const char *name;       /* View name */
1914         const char *cmd_env;    /* Command line set via environment */
1915         const char *id;         /* Points to either of ref_{head,commit,blob} */
1917         struct view_ops *ops;   /* View operations */
1919         enum keymap keymap;     /* What keymap does this view have */
1920         bool git_dir;           /* Whether the view requires a git directory. */
1922         char ref[SIZEOF_REF];   /* Hovered commit reference */
1923         char vid[SIZEOF_REF];   /* View ID. Set to id member when updating. */
1925         int height, width;      /* The width and height of the main window */
1926         WINDOW *win;            /* The main window */
1927         WINDOW *title;          /* The title window living below the main window */
1929         /* Navigation */
1930         unsigned long offset;   /* Offset of the window top */
1931         unsigned long yoffset;  /* Offset from the window side. */
1932         unsigned long lineno;   /* Current line number */
1933         unsigned long p_offset; /* Previous offset of the window top */
1934         unsigned long p_yoffset;/* Previous offset from the window side */
1935         unsigned long p_lineno; /* Previous current line number */
1936         bool p_restore;         /* Should the previous position be restored. */
1938         /* Searching */
1939         char grep[SIZEOF_STR];  /* Search string */
1940         regex_t *regex;         /* Pre-compiled regexp */
1942         /* If non-NULL, points to the view that opened this view. If this view
1943          * is closed tig will switch back to the parent view. */
1944         struct view *parent;
1946         /* Buffering */
1947         size_t lines;           /* Total number of lines */
1948         struct line *line;      /* Line index */
1949         unsigned int digits;    /* Number of digits in the lines member. */
1951         /* Drawing */
1952         struct line *curline;   /* Line currently being drawn. */
1953         enum line_type curtype; /* Attribute currently used for drawing. */
1954         unsigned long col;      /* Column when drawing. */
1955         bool has_scrolled;      /* View was scrolled. */
1957         /* Loading */
1958         struct io io;
1959         struct io *pipe;
1960         time_t start_time;
1961         time_t update_secs;
1962 };
1964 struct view_ops {
1965         /* What type of content being displayed. Used in the title bar. */
1966         const char *type;
1967         /* Default command arguments. */
1968         const char **argv;
1969         /* Open and reads in all view content. */
1970         bool (*open)(struct view *view);
1971         /* Read one line; updates view->line. */
1972         bool (*read)(struct view *view, char *data);
1973         /* Draw one line; @lineno must be < view->height. */
1974         bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
1975         /* Depending on view handle a special requests. */
1976         enum request (*request)(struct view *view, enum request request, struct line *line);
1977         /* Search for regexp in a line. */
1978         bool (*grep)(struct view *view, struct line *line);
1979         /* Select line */
1980         void (*select)(struct view *view, struct line *line);
1981         /* Prepare view for loading */
1982         bool (*prepare)(struct view *view);
1983 };
1985 static struct view_ops blame_ops;
1986 static struct view_ops blob_ops;
1987 static struct view_ops diff_ops;
1988 static struct view_ops help_ops;
1989 static struct view_ops log_ops;
1990 static struct view_ops main_ops;
1991 static struct view_ops pager_ops;
1992 static struct view_ops stage_ops;
1993 static struct view_ops status_ops;
1994 static struct view_ops tree_ops;
1995 static struct view_ops branch_ops;
1997 #define VIEW_STR(name, env, ref, ops, map, git) \
1998         { name, #env, ref, ops, map, git }
2000 #define VIEW_(id, name, ops, git, ref) \
2001         VIEW_STR(name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
2004 static struct view views[] = {
2005         VIEW_(MAIN,   "main",   &main_ops,   TRUE,  ref_head),
2006         VIEW_(DIFF,   "diff",   &diff_ops,   TRUE,  ref_commit),
2007         VIEW_(LOG,    "log",    &log_ops,    TRUE,  ref_head),
2008         VIEW_(TREE,   "tree",   &tree_ops,   TRUE,  ref_commit),
2009         VIEW_(BLOB,   "blob",   &blob_ops,   TRUE,  ref_blob),
2010         VIEW_(BLAME,  "blame",  &blame_ops,  TRUE,  ref_commit),
2011         VIEW_(BRANCH, "branch", &branch_ops, TRUE,  ref_head),
2012         VIEW_(HELP,   "help",   &help_ops,   FALSE, ""),
2013         VIEW_(PAGER,  "pager",  &pager_ops,  FALSE, "stdin"),
2014         VIEW_(STATUS, "status", &status_ops, TRUE,  ""),
2015         VIEW_(STAGE,  "stage",  &stage_ops,  TRUE,  ""),
2016 };
2018 #define VIEW(req)       (&views[(req) - REQ_OFFSET - 1])
2019 #define VIEW_REQ(view)  ((view) - views + REQ_OFFSET + 1)
2021 #define foreach_view(view, i) \
2022         for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
2024 #define view_is_displayed(view) \
2025         (view == display[0] || view == display[1])
2028 enum line_graphic {
2029         LINE_GRAPHIC_VLINE
2030 };
2032 static chtype line_graphics[] = {
2033         /* LINE_GRAPHIC_VLINE: */ '|'
2034 };
2036 static inline void
2037 set_view_attr(struct view *view, enum line_type type)
2039         if (!view->curline->selected && view->curtype != type) {
2040                 wattrset(view->win, get_line_attr(type));
2041                 wchgat(view->win, -1, 0, type, NULL);
2042                 view->curtype = type;
2043         }
2046 static int
2047 draw_chars(struct view *view, enum line_type type, const char *string,
2048            int max_len, bool use_tilde)
2050         int len = 0;
2051         int col = 0;
2052         int trimmed = FALSE;
2053         size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2055         if (max_len <= 0)
2056                 return 0;
2058         if (opt_utf8) {
2059                 len = utf8_length(&string, skip, &col, max_len, &trimmed, use_tilde);
2060         } else {
2061                 col = len = strlen(string);
2062                 if (len > max_len) {
2063                         if (use_tilde) {
2064                                 max_len -= 1;
2065                         }
2066                         col = len = max_len;
2067                         trimmed = TRUE;
2068                 }
2069         }
2071         set_view_attr(view, type);
2072         if (len > 0)
2073                 waddnstr(view->win, string, len);
2074         if (trimmed && use_tilde) {
2075                 set_view_attr(view, LINE_DELIMITER);
2076                 waddch(view->win, '~');
2077                 col++;
2078         }
2080         return col;
2083 static int
2084 draw_space(struct view *view, enum line_type type, int max, int spaces)
2086         static char space[] = "                    ";
2087         int col = 0;
2089         spaces = MIN(max, spaces);
2091         while (spaces > 0) {
2092                 int len = MIN(spaces, sizeof(space) - 1);
2094                 col += draw_chars(view, type, space, len, FALSE);
2095                 spaces -= len;
2096         }
2098         return col;
2101 static bool
2102 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
2104         view->col += draw_chars(view, type, string, view->width + view->yoffset - view->col, trim);
2105         return view->width + view->yoffset <= view->col;
2108 static bool
2109 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
2111         size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2112         int max = view->width + view->yoffset - view->col;
2113         int i;
2115         if (max < size)
2116                 size = max;
2118         set_view_attr(view, type);
2119         /* Using waddch() instead of waddnstr() ensures that
2120          * they'll be rendered correctly for the cursor line. */
2121         for (i = skip; i < size; i++)
2122                 waddch(view->win, graphic[i]);
2124         view->col += size;
2125         if (size < max && skip <= size)
2126                 waddch(view->win, ' ');
2127         view->col++;
2129         return view->width + view->yoffset <= view->col;
2132 static bool
2133 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
2135         int max = MIN(view->width + view->yoffset - view->col, len);
2136         int col;
2138         if (text)
2139                 col = draw_chars(view, type, text, max - 1, trim);
2140         else
2141                 col = draw_space(view, type, max - 1, max - 1);
2143         view->col += col;
2144         view->col += draw_space(view, LINE_DEFAULT, max - col, max - col);
2145         return view->width + view->yoffset <= view->col;
2148 static bool
2149 draw_date(struct view *view, time_t *time)
2151         const char *date = time ? mkdate(time) : "";
2152         int cols = opt_date == DATE_SHORT ? DATE_SHORT_COLS : DATE_COLS;
2154         return draw_field(view, LINE_DATE, date, cols, FALSE);
2157 static bool
2158 draw_author(struct view *view, const char *author)
2160         bool trim = opt_author_cols == 0 || opt_author_cols > 5 || !author;
2162         if (!trim) {
2163                 static char initials[10];
2164                 size_t pos;
2166 #define is_initial_sep(c) (isspace(c) || ispunct(c) || (c) == '@')
2168                 memset(initials, 0, sizeof(initials));
2169                 for (pos = 0; *author && pos < opt_author_cols - 1; author++, pos++) {
2170                         while (is_initial_sep(*author))
2171                                 author++;
2172                         strncpy(&initials[pos], author, sizeof(initials) - 1 - pos);
2173                         while (*author && !is_initial_sep(author[1]))
2174                                 author++;
2175                 }
2177                 author = initials;
2178         }
2180         return draw_field(view, LINE_AUTHOR, author, opt_author_cols, trim);
2183 static bool
2184 draw_mode(struct view *view, mode_t mode)
2186         const char *str;
2188         if (S_ISDIR(mode))
2189                 str = "drwxr-xr-x";
2190         else if (S_ISLNK(mode))
2191                 str = "lrwxrwxrwx";
2192         else if (S_ISGITLINK(mode))
2193                 str = "m---------";
2194         else if (S_ISREG(mode) && mode & S_IXUSR)
2195                 str = "-rwxr-xr-x";
2196         else if (S_ISREG(mode))
2197                 str = "-rw-r--r--";
2198         else
2199                 str = "----------";
2201         return draw_field(view, LINE_MODE, str, STRING_SIZE("-rw-r--r-- "), FALSE);
2204 static bool
2205 draw_lineno(struct view *view, unsigned int lineno)
2207         char number[10];
2208         int digits3 = view->digits < 3 ? 3 : view->digits;
2209         int max = MIN(view->width + view->yoffset - view->col, digits3);
2210         char *text = NULL;
2212         lineno += view->offset + 1;
2213         if (lineno == 1 || (lineno % opt_num_interval) == 0) {
2214                 static char fmt[] = "%1ld";
2216                 fmt[1] = '0' + (view->digits <= 9 ? digits3 : 1);
2217                 if (string_format(number, fmt, lineno))
2218                         text = number;
2219         }
2220         if (text)
2221                 view->col += draw_chars(view, LINE_LINE_NUMBER, text, max, TRUE);
2222         else
2223                 view->col += draw_space(view, LINE_LINE_NUMBER, max, digits3);
2224         return draw_graphic(view, LINE_DEFAULT, &line_graphics[LINE_GRAPHIC_VLINE], 1);
2227 static bool
2228 draw_view_line(struct view *view, unsigned int lineno)
2230         struct line *line;
2231         bool selected = (view->offset + lineno == view->lineno);
2233         assert(view_is_displayed(view));
2235         if (view->offset + lineno >= view->lines)
2236                 return FALSE;
2238         line = &view->line[view->offset + lineno];
2240         wmove(view->win, lineno, 0);
2241         if (line->cleareol)
2242                 wclrtoeol(view->win);
2243         view->col = 0;
2244         view->curline = line;
2245         view->curtype = LINE_NONE;
2246         line->selected = FALSE;
2247         line->dirty = line->cleareol = 0;
2249         if (selected) {
2250                 set_view_attr(view, LINE_CURSOR);
2251                 line->selected = TRUE;
2252                 view->ops->select(view, line);
2253         }
2255         return view->ops->draw(view, line, lineno);
2258 static void
2259 redraw_view_dirty(struct view *view)
2261         bool dirty = FALSE;
2262         int lineno;
2264         for (lineno = 0; lineno < view->height; lineno++) {
2265                 if (view->offset + lineno >= view->lines)
2266                         break;
2267                 if (!view->line[view->offset + lineno].dirty)
2268                         continue;
2269                 dirty = TRUE;
2270                 if (!draw_view_line(view, lineno))
2271                         break;
2272         }
2274         if (!dirty)
2275                 return;
2276         wnoutrefresh(view->win);
2279 static void
2280 redraw_view_from(struct view *view, int lineno)
2282         assert(0 <= lineno && lineno < view->height);
2284         for (; lineno < view->height; lineno++) {
2285                 if (!draw_view_line(view, lineno))
2286                         break;
2287         }
2289         wnoutrefresh(view->win);
2292 static void
2293 redraw_view(struct view *view)
2295         werase(view->win);
2296         redraw_view_from(view, 0);
2300 static void
2301 update_view_title(struct view *view)
2303         char buf[SIZEOF_STR];
2304         char state[SIZEOF_STR];
2305         size_t bufpos = 0, statelen = 0;
2307         assert(view_is_displayed(view));
2309         if (view != VIEW(REQ_VIEW_STATUS) && view->lines) {
2310                 unsigned int view_lines = view->offset + view->height;
2311                 unsigned int lines = view->lines
2312                                    ? MIN(view_lines, view->lines) * 100 / view->lines
2313                                    : 0;
2315                 string_format_from(state, &statelen, " - %s %d of %d (%d%%)",
2316                                    view->ops->type,
2317                                    view->lineno + 1,
2318                                    view->lines,
2319                                    lines);
2321         }
2323         if (view->pipe) {
2324                 time_t secs = time(NULL) - view->start_time;
2326                 /* Three git seconds are a long time ... */
2327                 if (secs > 2)
2328                         string_format_from(state, &statelen, " loading %lds", secs);
2329         }
2331         string_format_from(buf, &bufpos, "[%s]", view->name);
2332         if (*view->ref && bufpos < view->width) {
2333                 size_t refsize = strlen(view->ref);
2334                 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2336                 if (minsize < view->width)
2337                         refsize = view->width - minsize + 7;
2338                 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2339         }
2341         if (statelen && bufpos < view->width) {
2342                 string_format_from(buf, &bufpos, "%s", state);
2343         }
2345         if (view == display[current_view])
2346                 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2347         else
2348                 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2350         mvwaddnstr(view->title, 0, 0, buf, bufpos);
2351         wclrtoeol(view->title);
2352         wnoutrefresh(view->title);
2355 static int
2356 apply_step(double step, int value)
2358         if (step >= 1)
2359                 return (int) step;
2360         value *= step + 0.01;
2361         return value ? value : 1;
2364 static void
2365 resize_display(void)
2367         int offset, i;
2368         struct view *base = display[0];
2369         struct view *view = display[1] ? display[1] : display[0];
2371         /* Setup window dimensions */
2373         getmaxyx(stdscr, base->height, base->width);
2375         /* Make room for the status window. */
2376         base->height -= 1;
2378         if (view != base) {
2379                 /* Horizontal split. */
2380                 view->width   = base->width;
2381                 view->height  = apply_step(opt_scale_split_view, base->height);
2382                 view->height  = MAX(view->height, MIN_VIEW_HEIGHT);
2383                 view->height  = MIN(view->height, base->height - MIN_VIEW_HEIGHT);
2384                 base->height -= view->height;
2386                 /* Make room for the title bar. */
2387                 view->height -= 1;
2388         }
2390         /* Make room for the title bar. */
2391         base->height -= 1;
2393         offset = 0;
2395         foreach_displayed_view (view, i) {
2396                 if (!view->win) {
2397                         view->win = newwin(view->height, 0, offset, 0);
2398                         if (!view->win)
2399                                 die("Failed to create %s view", view->name);
2401                         scrollok(view->win, FALSE);
2403                         view->title = newwin(1, 0, offset + view->height, 0);
2404                         if (!view->title)
2405                                 die("Failed to create title window");
2407                 } else {
2408                         wresize(view->win, view->height, view->width);
2409                         mvwin(view->win,   offset, 0);
2410                         mvwin(view->title, offset + view->height, 0);
2411                 }
2413                 offset += view->height + 1;
2414         }
2417 static void
2418 redraw_display(bool clear)
2420         struct view *view;
2421         int i;
2423         foreach_displayed_view (view, i) {
2424                 if (clear)
2425                         wclear(view->win);
2426                 redraw_view(view);
2427                 update_view_title(view);
2428         }
2431 static void
2432 toggle_date_option(enum date *date)
2434         static const char *help[] = {
2435                 "no",
2436                 "default",
2437                 "relative",
2438                 "short"
2439         };
2441         opt_date = (opt_date + 1) % ARRAY_SIZE(help);
2442         redraw_display(FALSE);
2443         report("Displaying %s dates", help[opt_date]);
2446 static void
2447 toggle_view_option(bool *option, const char *help)
2449         *option = !*option;
2450         redraw_display(FALSE);
2451         report("%sabling %s", *option ? "En" : "Dis", help);
2454 static void
2455 open_option_menu(void)
2457         const struct menu_item menu[] = {
2458                 { '.', "line numbers", &opt_line_number },
2459                 { 'D', "date display", &opt_date },
2460                 { 'A', "author display", &opt_author },
2461                 { 'g', "revision graph display", &opt_rev_graph },
2462                 { 'F', "reference display", &opt_show_refs },
2463                 { 0 }
2464         };
2465         int selected = 0;
2467         if (prompt_menu("Toggle option", menu, &selected)) {
2468                 if (menu[selected].data == &opt_date)
2469                         toggle_date_option(menu[selected].data);
2470                 else
2471                         toggle_view_option(menu[selected].data, menu[selected].text);
2472         }
2475 static void
2476 maximize_view(struct view *view)
2478         memset(display, 0, sizeof(display));
2479         current_view = 0;
2480         display[current_view] = view;
2481         resize_display();
2482         redraw_display(FALSE);
2483         report("");
2487 /*
2488  * Navigation
2489  */
2491 static bool
2492 goto_view_line(struct view *view, unsigned long offset, unsigned long lineno)
2494         if (lineno >= view->lines)
2495                 lineno = view->lines > 0 ? view->lines - 1 : 0;
2497         if (offset > lineno || offset + view->height <= lineno) {
2498                 unsigned long half = view->height / 2;
2500                 if (lineno > half)
2501                         offset = lineno - half;
2502                 else
2503                         offset = 0;
2504         }
2506         if (offset != view->offset || lineno != view->lineno) {
2507                 view->offset = offset;
2508                 view->lineno = lineno;
2509                 return TRUE;
2510         }
2512         return FALSE;
2515 /* Scrolling backend */
2516 static void
2517 do_scroll_view(struct view *view, int lines)
2519         bool redraw_current_line = FALSE;
2521         /* The rendering expects the new offset. */
2522         view->offset += lines;
2524         assert(0 <= view->offset && view->offset < view->lines);
2525         assert(lines);
2527         /* Move current line into the view. */
2528         if (view->lineno < view->offset) {
2529                 view->lineno = view->offset;
2530                 redraw_current_line = TRUE;
2531         } else if (view->lineno >= view->offset + view->height) {
2532                 view->lineno = view->offset + view->height - 1;
2533                 redraw_current_line = TRUE;
2534         }
2536         assert(view->offset <= view->lineno && view->lineno < view->lines);
2538         /* Redraw the whole screen if scrolling is pointless. */
2539         if (view->height < ABS(lines)) {
2540                 redraw_view(view);
2542         } else {
2543                 int line = lines > 0 ? view->height - lines : 0;
2544                 int end = line + ABS(lines);
2546                 scrollok(view->win, TRUE);
2547                 wscrl(view->win, lines);
2548                 scrollok(view->win, FALSE);
2550                 while (line < end && draw_view_line(view, line))
2551                         line++;
2553                 if (redraw_current_line)
2554                         draw_view_line(view, view->lineno - view->offset);
2555                 wnoutrefresh(view->win);
2556         }
2558         view->has_scrolled = TRUE;
2559         report("");
2562 /* Scroll frontend */
2563 static void
2564 scroll_view(struct view *view, enum request request)
2566         int lines = 1;
2568         assert(view_is_displayed(view));
2570         switch (request) {
2571         case REQ_SCROLL_LEFT:
2572                 if (view->yoffset == 0) {
2573                         report("Cannot scroll beyond the first column");
2574                         return;
2575                 }
2576                 if (view->yoffset <= apply_step(opt_hscroll, view->width))
2577                         view->yoffset = 0;
2578                 else
2579                         view->yoffset -= apply_step(opt_hscroll, view->width);
2580                 redraw_view_from(view, 0);
2581                 report("");
2582                 return;
2583         case REQ_SCROLL_RIGHT:
2584                 view->yoffset += apply_step(opt_hscroll, view->width);
2585                 redraw_view(view);
2586                 report("");
2587                 return;
2588         case REQ_SCROLL_PAGE_DOWN:
2589                 lines = view->height;
2590         case REQ_SCROLL_LINE_DOWN:
2591                 if (view->offset + lines > view->lines)
2592                         lines = view->lines - view->offset;
2594                 if (lines == 0 || view->offset + view->height >= view->lines) {
2595                         report("Cannot scroll beyond the last line");
2596                         return;
2597                 }
2598                 break;
2600         case REQ_SCROLL_PAGE_UP:
2601                 lines = view->height;
2602         case REQ_SCROLL_LINE_UP:
2603                 if (lines > view->offset)
2604                         lines = view->offset;
2606                 if (lines == 0) {
2607                         report("Cannot scroll beyond the first line");
2608                         return;
2609                 }
2611                 lines = -lines;
2612                 break;
2614         default:
2615                 die("request %d not handled in switch", request);
2616         }
2618         do_scroll_view(view, lines);
2621 /* Cursor moving */
2622 static void
2623 move_view(struct view *view, enum request request)
2625         int scroll_steps = 0;
2626         int steps;
2628         switch (request) {
2629         case REQ_MOVE_FIRST_LINE:
2630                 steps = -view->lineno;
2631                 break;
2633         case REQ_MOVE_LAST_LINE:
2634                 steps = view->lines - view->lineno - 1;
2635                 break;
2637         case REQ_MOVE_PAGE_UP:
2638                 steps = view->height > view->lineno
2639                       ? -view->lineno : -view->height;
2640                 break;
2642         case REQ_MOVE_PAGE_DOWN:
2643                 steps = view->lineno + view->height >= view->lines
2644                       ? view->lines - view->lineno - 1 : view->height;
2645                 break;
2647         case REQ_MOVE_UP:
2648                 steps = -1;
2649                 break;
2651         case REQ_MOVE_DOWN:
2652                 steps = 1;
2653                 break;
2655         default:
2656                 die("request %d not handled in switch", request);
2657         }
2659         if (steps <= 0 && view->lineno == 0) {
2660                 report("Cannot move beyond the first line");
2661                 return;
2663         } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2664                 report("Cannot move beyond the last line");
2665                 return;
2666         }
2668         /* Move the current line */
2669         view->lineno += steps;
2670         assert(0 <= view->lineno && view->lineno < view->lines);
2672         /* Check whether the view needs to be scrolled */
2673         if (view->lineno < view->offset ||
2674             view->lineno >= view->offset + view->height) {
2675                 scroll_steps = steps;
2676                 if (steps < 0 && -steps > view->offset) {
2677                         scroll_steps = -view->offset;
2679                 } else if (steps > 0) {
2680                         if (view->lineno == view->lines - 1 &&
2681                             view->lines > view->height) {
2682                                 scroll_steps = view->lines - view->offset - 1;
2683                                 if (scroll_steps >= view->height)
2684                                         scroll_steps -= view->height - 1;
2685                         }
2686                 }
2687         }
2689         if (!view_is_displayed(view)) {
2690                 view->offset += scroll_steps;
2691                 assert(0 <= view->offset && view->offset < view->lines);
2692                 view->ops->select(view, &view->line[view->lineno]);
2693                 return;
2694         }
2696         /* Repaint the old "current" line if we be scrolling */
2697         if (ABS(steps) < view->height)
2698                 draw_view_line(view, view->lineno - steps - view->offset);
2700         if (scroll_steps) {
2701                 do_scroll_view(view, scroll_steps);
2702                 return;
2703         }
2705         /* Draw the current line */
2706         draw_view_line(view, view->lineno - view->offset);
2708         wnoutrefresh(view->win);
2709         report("");
2713 /*
2714  * Searching
2715  */
2717 static void search_view(struct view *view, enum request request);
2719 static bool
2720 grep_text(struct view *view, const char *text[])
2722         regmatch_t pmatch;
2723         size_t i;
2725         for (i = 0; text[i]; i++)
2726                 if (*text[i] &&
2727                     regexec(view->regex, text[i], 1, &pmatch, 0) != REG_NOMATCH)
2728                         return TRUE;
2729         return FALSE;
2732 static void
2733 select_view_line(struct view *view, unsigned long lineno)
2735         unsigned long old_lineno = view->lineno;
2736         unsigned long old_offset = view->offset;
2738         if (goto_view_line(view, view->offset, lineno)) {
2739                 if (view_is_displayed(view)) {
2740                         if (old_offset != view->offset) {
2741                                 redraw_view(view);
2742                         } else {
2743                                 draw_view_line(view, old_lineno - view->offset);
2744                                 draw_view_line(view, view->lineno - view->offset);
2745                                 wnoutrefresh(view->win);
2746                         }
2747                 } else {
2748                         view->ops->select(view, &view->line[view->lineno]);
2749                 }
2750         }
2753 static void
2754 find_next(struct view *view, enum request request)
2756         unsigned long lineno = view->lineno;
2757         int direction;
2759         if (!*view->grep) {
2760                 if (!*opt_search)
2761                         report("No previous search");
2762                 else
2763                         search_view(view, request);
2764                 return;
2765         }
2767         switch (request) {
2768         case REQ_SEARCH:
2769         case REQ_FIND_NEXT:
2770                 direction = 1;
2771                 break;
2773         case REQ_SEARCH_BACK:
2774         case REQ_FIND_PREV:
2775                 direction = -1;
2776                 break;
2778         default:
2779                 return;
2780         }
2782         if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
2783                 lineno += direction;
2785         /* Note, lineno is unsigned long so will wrap around in which case it
2786          * will become bigger than view->lines. */
2787         for (; lineno < view->lines; lineno += direction) {
2788                 if (view->ops->grep(view, &view->line[lineno])) {
2789                         select_view_line(view, lineno);
2790                         report("Line %ld matches '%s'", lineno + 1, view->grep);
2791                         return;
2792                 }
2793         }
2795         report("No match found for '%s'", view->grep);
2798 static void
2799 search_view(struct view *view, enum request request)
2801         int regex_err;
2803         if (view->regex) {
2804                 regfree(view->regex);
2805                 *view->grep = 0;
2806         } else {
2807                 view->regex = calloc(1, sizeof(*view->regex));
2808                 if (!view->regex)
2809                         return;
2810         }
2812         regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
2813         if (regex_err != 0) {
2814                 char buf[SIZEOF_STR] = "unknown error";
2816                 regerror(regex_err, view->regex, buf, sizeof(buf));
2817                 report("Search failed: %s", buf);
2818                 return;
2819         }
2821         string_copy(view->grep, opt_search);
2823         find_next(view, request);
2826 /*
2827  * Incremental updating
2828  */
2830 static void
2831 reset_view(struct view *view)
2833         int i;
2835         for (i = 0; i < view->lines; i++)
2836                 free(view->line[i].data);
2837         free(view->line);
2839         view->p_offset = view->offset;
2840         view->p_yoffset = view->yoffset;
2841         view->p_lineno = view->lineno;
2843         view->line = NULL;
2844         view->offset = 0;
2845         view->yoffset = 0;
2846         view->lines  = 0;
2847         view->lineno = 0;
2848         view->vid[0] = 0;
2849         view->update_secs = 0;
2852 static void
2853 free_argv(const char *argv[])
2855         int argc;
2857         for (argc = 0; argv[argc]; argc++)
2858                 free((void *) argv[argc]);
2861 static bool
2862 format_argv(const char *dst_argv[], const char *src_argv[], enum format_flags flags)
2864         char buf[SIZEOF_STR];
2865         int argc;
2866         bool noreplace = flags == FORMAT_NONE;
2868         free_argv(dst_argv);
2870         for (argc = 0; src_argv[argc]; argc++) {
2871                 const char *arg = src_argv[argc];
2872                 size_t bufpos = 0;
2874                 while (arg) {
2875                         char *next = strstr(arg, "%(");
2876                         int len = next - arg;
2877                         const char *value;
2879                         if (!next || noreplace) {
2880                                 if (flags == FORMAT_DASH && !strcmp(arg, "--"))
2881                                         noreplace = TRUE;
2882                                 len = strlen(arg);
2883                                 value = "";
2885                         } else if (!prefixcmp(next, "%(directory)")) {
2886                                 value = opt_path;
2888                         } else if (!prefixcmp(next, "%(file)")) {
2889                                 value = opt_file;
2891                         } else if (!prefixcmp(next, "%(ref)")) {
2892                                 value = *opt_ref ? opt_ref : "HEAD";
2894                         } else if (!prefixcmp(next, "%(head)")) {
2895                                 value = ref_head;
2897                         } else if (!prefixcmp(next, "%(commit)")) {
2898                                 value = ref_commit;
2900                         } else if (!prefixcmp(next, "%(blob)")) {
2901                                 value = ref_blob;
2903                         } else {
2904                                 report("Unknown replacement: `%s`", next);
2905                                 return FALSE;
2906                         }
2908                         if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
2909                                 return FALSE;
2911                         arg = next && !noreplace ? strchr(next, ')') + 1 : NULL;
2912                 }
2914                 dst_argv[argc] = strdup(buf);
2915                 if (!dst_argv[argc])
2916                         break;
2917         }
2919         dst_argv[argc] = NULL;
2921         return src_argv[argc] == NULL;
2924 static bool
2925 restore_view_position(struct view *view)
2927         if (!view->p_restore || (view->pipe && view->lines <= view->p_lineno))
2928                 return FALSE;
2930         /* Changing the view position cancels the restoring. */
2931         /* FIXME: Changing back to the first line is not detected. */
2932         if (view->offset != 0 || view->lineno != 0) {
2933                 view->p_restore = FALSE;
2934                 return FALSE;
2935         }
2937         if (goto_view_line(view, view->p_offset, view->p_lineno) &&
2938             view_is_displayed(view))
2939                 werase(view->win);
2941         view->yoffset = view->p_yoffset;
2942         view->p_restore = FALSE;
2944         return TRUE;
2947 static void
2948 end_update(struct view *view, bool force)
2950         if (!view->pipe)
2951                 return;
2952         while (!view->ops->read(view, NULL))
2953                 if (!force)
2954                         return;
2955         set_nonblocking_input(FALSE);
2956         if (force)
2957                 kill_io(view->pipe);
2958         done_io(view->pipe);
2959         view->pipe = NULL;
2962 static void
2963 setup_update(struct view *view, const char *vid)
2965         set_nonblocking_input(TRUE);
2966         reset_view(view);
2967         string_copy_rev(view->vid, vid);
2968         view->pipe = &view->io;
2969         view->start_time = time(NULL);
2972 static bool
2973 prepare_update(struct view *view, const char *argv[], const char *dir,
2974                enum format_flags flags)
2976         if (view->pipe)
2977                 end_update(view, TRUE);
2978         return init_io_rd(&view->io, argv, dir, flags);
2981 static bool
2982 prepare_update_file(struct view *view, const char *name)
2984         if (view->pipe)
2985                 end_update(view, TRUE);
2986         return io_open(&view->io, name);
2989 static bool
2990 begin_update(struct view *view, bool refresh)
2992         if (view->pipe)
2993                 end_update(view, TRUE);
2995         if (!refresh) {
2996                 if (view->ops->prepare) {
2997                         if (!view->ops->prepare(view))
2998                                 return FALSE;
2999                 } else if (!init_io_rd(&view->io, view->ops->argv, NULL, FORMAT_ALL)) {
3000                         return FALSE;
3001                 }
3003                 /* Put the current ref_* value to the view title ref
3004                  * member. This is needed by the blob view. Most other
3005                  * views sets it automatically after loading because the
3006                  * first line is a commit line. */
3007                 string_copy_rev(view->ref, view->id);
3008         }
3010         if (!start_io(&view->io))
3011                 return FALSE;
3013         setup_update(view, view->id);
3015         return TRUE;
3018 static bool
3019 update_view(struct view *view)
3021         char out_buffer[BUFSIZ * 2];
3022         char *line;
3023         /* Clear the view and redraw everything since the tree sorting
3024          * might have rearranged things. */
3025         bool redraw = view->lines == 0;
3026         bool can_read = TRUE;
3028         if (!view->pipe)
3029                 return TRUE;
3031         if (!io_can_read(view->pipe)) {
3032                 if (view->lines == 0 && view_is_displayed(view)) {
3033                         time_t secs = time(NULL) - view->start_time;
3035                         if (secs > 1 && secs > view->update_secs) {
3036                                 if (view->update_secs == 0)
3037                                         redraw_view(view);
3038                                 update_view_title(view);
3039                                 view->update_secs = secs;
3040                         }
3041                 }
3042                 return TRUE;
3043         }
3045         for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
3046                 if (opt_iconv != ICONV_NONE) {
3047                         ICONV_CONST char *inbuf = line;
3048                         size_t inlen = strlen(line) + 1;
3050                         char *outbuf = out_buffer;
3051                         size_t outlen = sizeof(out_buffer);
3053                         size_t ret;
3055                         ret = iconv(opt_iconv, &inbuf, &inlen, &outbuf, &outlen);
3056                         if (ret != (size_t) -1)
3057                                 line = out_buffer;
3058                 }
3060                 if (!view->ops->read(view, line)) {
3061                         report("Allocation failure");
3062                         end_update(view, TRUE);
3063                         return FALSE;
3064                 }
3065         }
3067         {
3068                 unsigned long lines = view->lines;
3069                 int digits;
3071                 for (digits = 0; lines; digits++)
3072                         lines /= 10;
3074                 /* Keep the displayed view in sync with line number scaling. */
3075                 if (digits != view->digits) {
3076                         view->digits = digits;
3077                         if (opt_line_number || view == VIEW(REQ_VIEW_BLAME))
3078                                 redraw = TRUE;
3079                 }
3080         }
3082         if (io_error(view->pipe)) {
3083                 report("Failed to read: %s", io_strerror(view->pipe));
3084                 end_update(view, TRUE);
3086         } else if (io_eof(view->pipe)) {
3087                 report("");
3088                 end_update(view, FALSE);
3089         }
3091         if (restore_view_position(view))
3092                 redraw = TRUE;
3094         if (!view_is_displayed(view))
3095                 return TRUE;
3097         if (redraw)
3098                 redraw_view_from(view, 0);
3099         else
3100                 redraw_view_dirty(view);
3102         /* Update the title _after_ the redraw so that if the redraw picks up a
3103          * commit reference in view->ref it'll be available here. */
3104         update_view_title(view);
3105         return TRUE;
3108 DEFINE_ALLOCATOR(realloc_lines, struct line, 256)
3110 static struct line *
3111 add_line_data(struct view *view, void *data, enum line_type type)
3113         struct line *line;
3115         if (!realloc_lines(&view->line, view->lines, 1))
3116                 return NULL;
3118         line = &view->line[view->lines++];
3119         memset(line, 0, sizeof(*line));
3120         line->type = type;
3121         line->data = data;
3122         line->dirty = 1;
3124         return line;
3127 static struct line *
3128 add_line_text(struct view *view, const char *text, enum line_type type)
3130         char *data = text ? strdup(text) : NULL;
3132         return data ? add_line_data(view, data, type) : NULL;
3135 static struct line *
3136 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
3138         char buf[SIZEOF_STR];
3139         va_list args;
3141         va_start(args, fmt);
3142         if (vsnprintf(buf, sizeof(buf), fmt, args) >= sizeof(buf))
3143                 buf[0] = 0;
3144         va_end(args);
3146         return buf[0] ? add_line_text(view, buf, type) : NULL;
3149 /*
3150  * View opening
3151  */
3153 enum open_flags {
3154         OPEN_DEFAULT = 0,       /* Use default view switching. */
3155         OPEN_SPLIT = 1,         /* Split current view. */
3156         OPEN_RELOAD = 4,        /* Reload view even if it is the current. */
3157         OPEN_REFRESH = 16,      /* Refresh view using previous command. */
3158         OPEN_PREPARED = 32,     /* Open already prepared command. */
3159 };
3161 static void
3162 open_view(struct view *prev, enum request request, enum open_flags flags)
3164         bool split = !!(flags & OPEN_SPLIT);
3165         bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
3166         bool nomaximize = !!(flags & OPEN_REFRESH);
3167         struct view *view = VIEW(request);
3168         int nviews = displayed_views();
3169         struct view *base_view = display[0];
3171         if (view == prev && nviews == 1 && !reload) {
3172                 report("Already in %s view", view->name);
3173                 return;
3174         }
3176         if (view->git_dir && !opt_git_dir[0]) {
3177                 report("The %s view is disabled in pager view", view->name);
3178                 return;
3179         }
3181         if (split) {
3182                 display[1] = view;
3183                 current_view = 1;
3184         } else if (!nomaximize) {
3185                 /* Maximize the current view. */
3186                 memset(display, 0, sizeof(display));
3187                 current_view = 0;
3188                 display[current_view] = view;
3189         }
3191         /* No parent signals that this is the first loaded view. */
3192         if (prev && view != prev) {
3193                 view->parent = prev;
3194         }
3196         /* Resize the view when switching between split- and full-screen,
3197          * or when switching between two different full-screen views. */
3198         if (nviews != displayed_views() ||
3199             (nviews == 1 && base_view != display[0]))
3200                 resize_display();
3202         if (view->ops->open) {
3203                 if (view->pipe)
3204                         end_update(view, TRUE);
3205                 if (!view->ops->open(view)) {
3206                         report("Failed to load %s view", view->name);
3207                         return;
3208                 }
3209                 restore_view_position(view);
3211         } else if ((reload || strcmp(view->vid, view->id)) &&
3212                    !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
3213                 report("Failed to load %s view", view->name);
3214                 return;
3215         }
3217         if (split && prev->lineno - prev->offset >= prev->height) {
3218                 /* Take the title line into account. */
3219                 int lines = prev->lineno - prev->offset - prev->height + 1;
3221                 /* Scroll the view that was split if the current line is
3222                  * outside the new limited view. */
3223                 do_scroll_view(prev, lines);
3224         }
3226         if (prev && view != prev) {
3227                 if (split) {
3228                         /* "Blur" the previous view. */
3229                         update_view_title(prev);
3230                 }
3231         }
3233         if (view->pipe && view->lines == 0) {
3234                 /* Clear the old view and let the incremental updating refill
3235                  * the screen. */
3236                 werase(view->win);
3237                 view->p_restore = flags & (OPEN_RELOAD | OPEN_REFRESH);
3238                 report("");
3239         } else if (view_is_displayed(view)) {
3240                 redraw_view(view);
3241                 report("");
3242         }
3245 static void
3246 open_external_viewer(const char *argv[], const char *dir)
3248         def_prog_mode();           /* save current tty modes */
3249         endwin();                  /* restore original tty modes */
3250         run_io_fg(argv, dir);
3251         fprintf(stderr, "Press Enter to continue");
3252         getc(opt_tty);
3253         reset_prog_mode();
3254         redraw_display(TRUE);
3257 static void
3258 open_mergetool(const char *file)
3260         const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
3262         open_external_viewer(mergetool_argv, opt_cdup);
3265 static void
3266 open_editor(bool from_root, const char *file)
3268         const char *editor_argv[] = { "vi", file, NULL };
3269         const char *editor;
3271         editor = getenv("GIT_EDITOR");
3272         if (!editor && *opt_editor)
3273                 editor = opt_editor;
3274         if (!editor)
3275                 editor = getenv("VISUAL");
3276         if (!editor)
3277                 editor = getenv("EDITOR");
3278         if (!editor)
3279                 editor = "vi";
3281         editor_argv[0] = editor;
3282         open_external_viewer(editor_argv, from_root ? opt_cdup : NULL);
3285 static void
3286 open_run_request(enum request request)
3288         struct run_request *req = get_run_request(request);
3289         const char *argv[ARRAY_SIZE(req->argv)] = { NULL };
3291         if (!req) {
3292                 report("Unknown run request");
3293                 return;
3294         }
3296         if (format_argv(argv, req->argv, FORMAT_ALL))
3297                 open_external_viewer(argv, NULL);
3298         free_argv(argv);
3301 /*
3302  * User request switch noodle
3303  */
3305 static int
3306 view_driver(struct view *view, enum request request)
3308         int i;
3310         if (request == REQ_NONE)
3311                 return TRUE;
3313         if (request > REQ_NONE) {
3314                 open_run_request(request);
3315                 /* FIXME: When all views can refresh always do this. */
3316                 if (view == VIEW(REQ_VIEW_STATUS) ||
3317                     view == VIEW(REQ_VIEW_MAIN) ||
3318                     view == VIEW(REQ_VIEW_LOG) ||
3319                     view == VIEW(REQ_VIEW_BRANCH) ||
3320                     view == VIEW(REQ_VIEW_STAGE))
3321                         request = REQ_REFRESH;
3322                 else
3323                         return TRUE;
3324         }
3326         if (view && view->lines) {
3327                 request = view->ops->request(view, request, &view->line[view->lineno]);
3328                 if (request == REQ_NONE)
3329                         return TRUE;
3330         }
3332         switch (request) {
3333         case REQ_MOVE_UP:
3334         case REQ_MOVE_DOWN:
3335         case REQ_MOVE_PAGE_UP:
3336         case REQ_MOVE_PAGE_DOWN:
3337         case REQ_MOVE_FIRST_LINE:
3338         case REQ_MOVE_LAST_LINE:
3339                 move_view(view, request);
3340                 break;
3342         case REQ_SCROLL_LEFT:
3343         case REQ_SCROLL_RIGHT:
3344         case REQ_SCROLL_LINE_DOWN:
3345         case REQ_SCROLL_LINE_UP:
3346         case REQ_SCROLL_PAGE_DOWN:
3347         case REQ_SCROLL_PAGE_UP:
3348                 scroll_view(view, request);
3349                 break;
3351         case REQ_VIEW_BLAME:
3352                 if (!opt_file[0]) {
3353                         report("No file chosen, press %s to open tree view",
3354                                get_key(view->keymap, REQ_VIEW_TREE));
3355                         break;
3356                 }
3357                 open_view(view, request, OPEN_DEFAULT);
3358                 break;
3360         case REQ_VIEW_BLOB:
3361                 if (!ref_blob[0]) {
3362                         report("No file chosen, press %s to open tree view",
3363                                get_key(view->keymap, REQ_VIEW_TREE));
3364                         break;
3365                 }
3366                 open_view(view, request, OPEN_DEFAULT);
3367                 break;
3369         case REQ_VIEW_PAGER:
3370                 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3371                         report("No pager content, press %s to run command from prompt",
3372                                get_key(view->keymap, REQ_PROMPT));
3373                         break;
3374                 }
3375                 open_view(view, request, OPEN_DEFAULT);
3376                 break;
3378         case REQ_VIEW_STAGE:
3379                 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3380                         report("No stage content, press %s to open the status view and choose file",
3381                                get_key(view->keymap, REQ_VIEW_STATUS));
3382                         break;
3383                 }
3384                 open_view(view, request, OPEN_DEFAULT);
3385                 break;
3387         case REQ_VIEW_STATUS:
3388                 if (opt_is_inside_work_tree == FALSE) {
3389                         report("The status view requires a working tree");
3390                         break;
3391                 }
3392                 open_view(view, request, OPEN_DEFAULT);
3393                 break;
3395         case REQ_VIEW_MAIN:
3396         case REQ_VIEW_DIFF:
3397         case REQ_VIEW_LOG:
3398         case REQ_VIEW_TREE:
3399         case REQ_VIEW_HELP:
3400         case REQ_VIEW_BRANCH:
3401                 open_view(view, request, OPEN_DEFAULT);
3402                 break;
3404         case REQ_NEXT:
3405         case REQ_PREVIOUS:
3406                 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3408                 if ((view == VIEW(REQ_VIEW_DIFF) &&
3409                      view->parent == VIEW(REQ_VIEW_MAIN)) ||
3410                    (view == VIEW(REQ_VIEW_DIFF) &&
3411                      view->parent == VIEW(REQ_VIEW_BLAME)) ||
3412                    (view == VIEW(REQ_VIEW_STAGE) &&
3413                      view->parent == VIEW(REQ_VIEW_STATUS)) ||
3414                    (view == VIEW(REQ_VIEW_BLOB) &&
3415                      view->parent == VIEW(REQ_VIEW_TREE)) ||
3416                    (view == VIEW(REQ_VIEW_MAIN) &&
3417                      view->parent == VIEW(REQ_VIEW_BRANCH))) {
3418                         int line;
3420                         view = view->parent;
3421                         line = view->lineno;
3422                         move_view(view, request);
3423                         if (view_is_displayed(view))
3424                                 update_view_title(view);
3425                         if (line != view->lineno)
3426                                 view->ops->request(view, REQ_ENTER,
3427                                                    &view->line[view->lineno]);
3429                 } else {
3430                         move_view(view, request);
3431                 }
3432                 break;
3434         case REQ_VIEW_NEXT:
3435         {
3436                 int nviews = displayed_views();
3437                 int next_view = (current_view + 1) % nviews;
3439                 if (next_view == current_view) {
3440                         report("Only one view is displayed");
3441                         break;
3442                 }
3444                 current_view = next_view;
3445                 /* Blur out the title of the previous view. */
3446                 update_view_title(view);
3447                 report("");
3448                 break;
3449         }
3450         case REQ_REFRESH:
3451                 report("Refreshing is not yet supported for the %s view", view->name);
3452                 break;
3454         case REQ_MAXIMIZE:
3455                 if (displayed_views() == 2)
3456                         maximize_view(view);
3457                 break;
3459         case REQ_OPTIONS:
3460                 open_option_menu();
3461                 break;
3463         case REQ_TOGGLE_LINENO:
3464                 toggle_view_option(&opt_line_number, "line numbers");
3465                 break;
3467         case REQ_TOGGLE_DATE:
3468                 toggle_date_option(&opt_date);
3469                 break;
3471         case REQ_TOGGLE_AUTHOR:
3472                 toggle_view_option(&opt_author, "author display");
3473                 break;
3475         case REQ_TOGGLE_REV_GRAPH:
3476                 toggle_view_option(&opt_rev_graph, "revision graph display");
3477                 break;
3479         case REQ_TOGGLE_REFS:
3480                 toggle_view_option(&opt_show_refs, "reference display");
3481                 break;
3483         case REQ_TOGGLE_SORT_FIELD:
3484         case REQ_TOGGLE_SORT_ORDER:
3485                 report("Sorting is not yet supported for the %s view", view->name);
3486                 break;
3488         case REQ_SEARCH:
3489         case REQ_SEARCH_BACK:
3490                 search_view(view, request);
3491                 break;
3493         case REQ_FIND_NEXT:
3494         case REQ_FIND_PREV:
3495                 find_next(view, request);
3496                 break;
3498         case REQ_STOP_LOADING:
3499                 for (i = 0; i < ARRAY_SIZE(views); i++) {
3500                         view = &views[i];
3501                         if (view->pipe)
3502                                 report("Stopped loading the %s view", view->name),
3503                         end_update(view, TRUE);
3504                 }
3505                 break;
3507         case REQ_SHOW_VERSION:
3508                 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3509                 return TRUE;
3511         case REQ_SCREEN_REDRAW:
3512                 redraw_display(TRUE);
3513                 break;
3515         case REQ_EDIT:
3516                 report("Nothing to edit");
3517                 break;
3519         case REQ_ENTER:
3520                 report("Nothing to enter");
3521                 break;
3523         case REQ_VIEW_CLOSE:
3524                 /* XXX: Mark closed views by letting view->parent point to the
3525                  * view itself. Parents to closed view should never be
3526                  * followed. */
3527                 if (view->parent &&
3528                     view->parent->parent != view->parent) {
3529                         maximize_view(view->parent);
3530                         view->parent = view;
3531                         break;
3532                 }
3533                 /* Fall-through */
3534         case REQ_QUIT:
3535                 return FALSE;
3537         default:
3538                 report("Unknown key, press %s for help",
3539                        get_key(view->keymap, REQ_VIEW_HELP));
3540                 return TRUE;
3541         }
3543         return TRUE;
3547 /*
3548  * View backend utilities
3549  */
3551 enum sort_field {
3552         ORDERBY_NAME,
3553         ORDERBY_DATE,
3554         ORDERBY_AUTHOR,
3555 };
3557 struct sort_state {
3558         const enum sort_field *fields;
3559         size_t size, current;
3560         bool reverse;
3561 };
3563 #define SORT_STATE(fields) { fields, ARRAY_SIZE(fields), 0 }
3564 #define get_sort_field(state) ((state).fields[(state).current])
3565 #define sort_order(state, result) ((state).reverse ? -(result) : (result))
3567 static void
3568 sort_view(struct view *view, enum request request, struct sort_state *state,
3569           int (*compare)(const void *, const void *))
3571         switch (request) {
3572         case REQ_TOGGLE_SORT_FIELD:
3573                 state->current = (state->current + 1) % state->size;
3574                 break;
3576         case REQ_TOGGLE_SORT_ORDER:
3577                 state->reverse = !state->reverse;
3578                 break;
3579         default:
3580                 die("Not a sort request");
3581         }
3583         qsort(view->line, view->lines, sizeof(*view->line), compare);
3584         redraw_view(view);
3587 DEFINE_ALLOCATOR(realloc_authors, const char *, 256)
3589 /* Small author cache to reduce memory consumption. It uses binary
3590  * search to lookup or find place to position new entries. No entries
3591  * are ever freed. */
3592 static const char *
3593 get_author(const char *name)
3595         static const char **authors;
3596         static size_t authors_size;
3597         int from = 0, to = authors_size - 1;
3599         while (from <= to) {
3600                 size_t pos = (to + from) / 2;
3601                 int cmp = strcmp(name, authors[pos]);
3603                 if (!cmp)
3604                         return authors[pos];
3606                 if (cmp < 0)
3607                         to = pos - 1;
3608                 else
3609                         from = pos + 1;
3610         }
3612         if (!realloc_authors(&authors, authors_size, 1))
3613                 return NULL;
3614         name = strdup(name);
3615         if (!name)
3616                 return NULL;
3618         memmove(authors + from + 1, authors + from, (authors_size - from) * sizeof(*authors));
3619         authors[from] = name;
3620         authors_size++;
3622         return name;
3625 static void
3626 parse_timezone(time_t *time, const char *zone)
3628         long tz;
3630         tz  = ('0' - zone[1]) * 60 * 60 * 10;
3631         tz += ('0' - zone[2]) * 60 * 60;
3632         tz += ('0' - zone[3]) * 60;
3633         tz += ('0' - zone[4]);
3635         if (zone[0] == '-')
3636                 tz = -tz;
3638         *time -= tz;
3641 /* Parse author lines where the name may be empty:
3642  *      author  <email@address.tld> 1138474660 +0100
3643  */
3644 static void
3645 parse_author_line(char *ident, const char **author, time_t *time)
3647         char *nameend = strchr(ident, '<');
3648         char *emailend = strchr(ident, '>');
3650         if (nameend && emailend)
3651                 *nameend = *emailend = 0;
3652         ident = chomp_string(ident);
3653         if (!*ident) {
3654                 if (nameend)
3655                         ident = chomp_string(nameend + 1);
3656                 if (!*ident)
3657                         ident = "Unknown";
3658         }
3660         *author = get_author(ident);
3662         /* Parse epoch and timezone */
3663         if (emailend && emailend[1] == ' ') {
3664                 char *secs = emailend + 2;
3665                 char *zone = strchr(secs, ' ');
3667                 *time = (time_t) atol(secs);
3669                 if (zone && strlen(zone) == STRING_SIZE(" +0700"))
3670                         parse_timezone(time, zone + 1);
3671         }
3674 static bool
3675 open_commit_parent_menu(char buf[SIZEOF_STR], int *parents)
3677         char rev[SIZEOF_REV];
3678         const char *revlist_argv[] = {
3679                 "git", "log", "--no-color", "-1", "--pretty=format:%s", rev, NULL
3680         };
3681         struct menu_item *items;
3682         char text[SIZEOF_STR];
3683         bool ok = TRUE;
3684         int i;
3686         items = calloc(*parents + 1, sizeof(*items));
3687         if (!items)
3688                 return FALSE;
3690         for (i = 0; i < *parents; i++) {
3691                 string_copy_rev(rev, &buf[SIZEOF_REV * i]);
3692                 if (!run_io_buf(revlist_argv, text, sizeof(text)) ||
3693                     !(items[i].text = strdup(text))) {
3694                         ok = FALSE;
3695                         break;
3696                 }
3697         }
3699         if (ok) {
3700                 *parents = 0;
3701                 ok = prompt_menu("Select parent", items, parents);
3702         }
3703         for (i = 0; items[i].text; i++)
3704                 free((char *) items[i].text);
3705         free(items);
3706         return ok;
3709 static bool
3710 select_commit_parent(const char *id, char rev[SIZEOF_REV], const char *path)
3712         char buf[SIZEOF_STR * 4];
3713         const char *revlist_argv[] = {
3714                 "git", "log", "--no-color", "-1",
3715                         "--pretty=format:%P", id, "--", path, NULL
3716         };
3717         int parents;
3719         if (!run_io_buf(revlist_argv, buf, sizeof(buf)) ||
3720             (parents = strlen(buf) / 40) < 0) {
3721                 report("Failed to get parent information");
3722                 return FALSE;
3724         } else if (parents == 0) {
3725                 if (path)
3726                         report("Path '%s' does not exist in the parent", path);
3727                 else
3728                         report("The selected commit has no parents");
3729                 return FALSE;
3730         }
3732         if (parents > 1 && !open_commit_parent_menu(buf, &parents))
3733                 return FALSE;
3735         string_copy_rev(rev, &buf[41 * parents]);
3736         return TRUE;
3739 /*
3740  * Pager backend
3741  */
3743 static bool
3744 pager_draw(struct view *view, struct line *line, unsigned int lineno)
3746         char text[SIZEOF_STR];
3748         if (opt_line_number && draw_lineno(view, lineno))
3749                 return TRUE;
3751         string_expand(text, sizeof(text), line->data, opt_tab_size);
3752         draw_text(view, line->type, text, TRUE);
3753         return TRUE;
3756 static bool
3757 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
3759         const char *describe_argv[] = { "git", "describe", commit_id, NULL };
3760         char ref[SIZEOF_STR];
3762         if (!run_io_buf(describe_argv, ref, sizeof(ref)) || !*ref)
3763                 return TRUE;
3765         /* This is the only fatal call, since it can "corrupt" the buffer. */
3766         if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
3767                 return FALSE;
3769         return TRUE;
3772 static void
3773 add_pager_refs(struct view *view, struct line *line)
3775         char buf[SIZEOF_STR];
3776         char *commit_id = (char *)line->data + STRING_SIZE("commit ");
3777         struct ref_list *list;
3778         size_t bufpos = 0, i;
3779         const char *sep = "Refs: ";
3780         bool is_tag = FALSE;
3782         assert(line->type == LINE_COMMIT);
3784         list = get_ref_list(commit_id);
3785         if (!list) {
3786                 if (view == VIEW(REQ_VIEW_DIFF))
3787                         goto try_add_describe_ref;
3788                 return;
3789         }
3791         for (i = 0; i < list->size; i++) {
3792                 struct ref *ref = list->refs[i];
3793                 const char *fmt = ref->tag    ? "%s[%s]" :
3794                                   ref->remote ? "%s<%s>" : "%s%s";
3796                 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
3797                         return;
3798                 sep = ", ";
3799                 if (ref->tag)
3800                         is_tag = TRUE;
3801         }
3803         if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
3804 try_add_describe_ref:
3805                 /* Add <tag>-g<commit_id> "fake" reference. */
3806                 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
3807                         return;
3808         }
3810         if (bufpos == 0)
3811                 return;
3813         add_line_text(view, buf, LINE_PP_REFS);
3816 static bool
3817 pager_read(struct view *view, char *data)
3819         struct line *line;
3821         if (!data)
3822                 return TRUE;
3824         line = add_line_text(view, data, get_line_type(data));
3825         if (!line)
3826                 return FALSE;
3828         if (line->type == LINE_COMMIT &&
3829             (view == VIEW(REQ_VIEW_DIFF) ||
3830              view == VIEW(REQ_VIEW_LOG)))
3831                 add_pager_refs(view, line);
3833         return TRUE;
3836 static enum request
3837 pager_request(struct view *view, enum request request, struct line *line)
3839         int split = 0;
3841         if (request != REQ_ENTER)
3842                 return request;
3844         if (line->type == LINE_COMMIT &&
3845            (view == VIEW(REQ_VIEW_LOG) ||
3846             view == VIEW(REQ_VIEW_PAGER))) {
3847                 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
3848                 split = 1;
3849         }
3851         /* Always scroll the view even if it was split. That way
3852          * you can use Enter to scroll through the log view and
3853          * split open each commit diff. */
3854         scroll_view(view, REQ_SCROLL_LINE_DOWN);
3856         /* FIXME: A minor workaround. Scrolling the view will call report("")
3857          * but if we are scrolling a non-current view this won't properly
3858          * update the view title. */
3859         if (split)
3860                 update_view_title(view);
3862         return REQ_NONE;
3865 static bool
3866 pager_grep(struct view *view, struct line *line)
3868         const char *text[] = { line->data, NULL };
3870         return grep_text(view, text);
3873 static void
3874 pager_select(struct view *view, struct line *line)
3876         if (line->type == LINE_COMMIT) {
3877                 char *text = (char *)line->data + STRING_SIZE("commit ");
3879                 if (view != VIEW(REQ_VIEW_PAGER))
3880                         string_copy_rev(view->ref, text);
3881                 string_copy_rev(ref_commit, text);
3882         }
3885 static struct view_ops pager_ops = {
3886         "line",
3887         NULL,
3888         NULL,
3889         pager_read,
3890         pager_draw,
3891         pager_request,
3892         pager_grep,
3893         pager_select,
3894 };
3896 static const char *log_argv[SIZEOF_ARG] = {
3897         "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
3898 };
3900 static enum request
3901 log_request(struct view *view, enum request request, struct line *line)
3903         switch (request) {
3904         case REQ_REFRESH:
3905                 load_refs();
3906                 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
3907                 return REQ_NONE;
3908         default:
3909                 return pager_request(view, request, line);
3910         }
3913 static struct view_ops log_ops = {
3914         "line",
3915         log_argv,
3916         NULL,
3917         pager_read,
3918         pager_draw,
3919         log_request,
3920         pager_grep,
3921         pager_select,
3922 };
3924 static const char *diff_argv[SIZEOF_ARG] = {
3925         "git", "show", "--pretty=fuller", "--no-color", "--root",
3926                 "--patch-with-stat", "--find-copies-harder", "-C", "%(commit)", NULL
3927 };
3929 static struct view_ops diff_ops = {
3930         "line",
3931         diff_argv,
3932         NULL,
3933         pager_read,
3934         pager_draw,
3935         pager_request,
3936         pager_grep,
3937         pager_select,
3938 };
3940 /*
3941  * Help backend
3942  */
3944 static bool help_keymap_hidden[ARRAY_SIZE(keymap_table)];
3946 static char *
3947 help_name(char buf[SIZEOF_STR], const char *name, size_t namelen)
3949         int bufpos;
3951         for (bufpos = 0; bufpos <= namelen; bufpos++) {
3952                 buf[bufpos] = tolower(name[bufpos]);
3953                 if (buf[bufpos] == '_')
3954                         buf[bufpos] = '-';
3955         }
3957         buf[bufpos] = 0;
3958         return buf;
3961 #define help_keymap_name(buf, keymap) \
3962         help_name(buf, keymap_table[keymap].name, keymap_table[keymap].namelen)
3964 static bool
3965 help_open_keymap_title(struct view *view, enum keymap keymap)
3967         char buf[SIZEOF_STR];
3968         struct line *line;
3970         line = add_line_format(view, LINE_HELP_KEYMAP, "[%c] %s bindings",
3971                                help_keymap_hidden[keymap] ? '+' : '-',
3972                                help_keymap_name(buf, keymap));
3973         if (line)
3974                 line->other = keymap;
3976         return help_keymap_hidden[keymap];
3979 static void
3980 help_open_keymap(struct view *view, enum keymap keymap)
3982         const char *group = NULL;
3983         char buf[SIZEOF_STR];
3984         size_t bufpos;
3985         bool add_title = TRUE;
3986         int i;
3988         for (i = 0; i < ARRAY_SIZE(req_info); i++) {
3989                 const char *key = NULL;
3991                 if (req_info[i].request == REQ_NONE)
3992                         continue;
3994                 if (!req_info[i].request) {
3995                         group = req_info[i].help;
3996                         continue;
3997                 }
3999                 key = get_keys(keymap, req_info[i].request, TRUE);
4000                 if (!key || !*key)
4001                         continue;
4003                 if (add_title && help_open_keymap_title(view, keymap))
4004                         return;
4005                 add_title = false;
4007                 if (group) {
4008                         add_line_text(view, group, LINE_HELP_GROUP);
4009                         group = NULL;
4010                 }
4012                 add_line_format(view, LINE_DEFAULT, "    %-25s %-20s %s", key,
4013                                 help_name(buf, req_info[i].name, req_info[i].namelen),
4014                                 req_info[i].help);
4015         }
4017         group = "External commands:";
4019         for (i = 0; i < run_requests; i++) {
4020                 struct run_request *req = get_run_request(REQ_NONE + i + 1);
4021                 const char *key;
4022                 int argc;
4024                 if (!req || req->keymap != keymap)
4025                         continue;
4027                 key = get_key_name(req->key);
4028                 if (!*key)
4029                         key = "(no key defined)";
4031                 if (add_title && help_open_keymap_title(view, keymap))
4032                         return;
4033                 if (group) {
4034                         add_line_text(view, group, LINE_HELP_GROUP);
4035                         group = NULL;
4036                 }
4038                 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
4039                         if (!string_format_from(buf, &bufpos, "%s%s",
4040                                                 argc ? " " : "", req->argv[argc]))
4041                                 return;
4043                 add_line_format(view, LINE_DEFAULT, "    %-25s `%s`", key, buf);
4044         }
4047 static bool
4048 help_open(struct view *view)
4050         enum keymap keymap;
4052         reset_view(view);
4053         add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
4054         add_line_text(view, "", LINE_DEFAULT);
4056         for (keymap = 0; keymap < ARRAY_SIZE(keymap_table); keymap++)
4057                 help_open_keymap(view, keymap);
4059         return TRUE;
4062 static enum request
4063 help_request(struct view *view, enum request request, struct line *line)
4065         switch (request) {
4066         case REQ_ENTER:
4067                 if (line->type == LINE_HELP_KEYMAP) {
4068                         help_keymap_hidden[line->other] =
4069                                 !help_keymap_hidden[line->other];
4070                         view->p_restore = TRUE;
4071                         open_view(view, REQ_VIEW_HELP, OPEN_REFRESH);
4072                 }
4074                 return REQ_NONE;
4075         default:
4076                 return pager_request(view, request, line);
4077         }
4080 static struct view_ops help_ops = {
4081         "line",
4082         NULL,
4083         help_open,
4084         NULL,
4085         pager_draw,
4086         help_request,
4087         pager_grep,
4088         pager_select,
4089 };
4092 /*
4093  * Tree backend
4094  */
4096 struct tree_stack_entry {
4097         struct tree_stack_entry *prev;  /* Entry below this in the stack */
4098         unsigned long lineno;           /* Line number to restore */
4099         char *name;                     /* Position of name in opt_path */
4100 };
4102 /* The top of the path stack. */
4103 static struct tree_stack_entry *tree_stack = NULL;
4104 unsigned long tree_lineno = 0;
4106 static void
4107 pop_tree_stack_entry(void)
4109         struct tree_stack_entry *entry = tree_stack;
4111         tree_lineno = entry->lineno;
4112         entry->name[0] = 0;
4113         tree_stack = entry->prev;
4114         free(entry);
4117 static void
4118 push_tree_stack_entry(const char *name, unsigned long lineno)
4120         struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
4121         size_t pathlen = strlen(opt_path);
4123         if (!entry)
4124                 return;
4126         entry->prev = tree_stack;
4127         entry->name = opt_path + pathlen;
4128         tree_stack = entry;
4130         if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
4131                 pop_tree_stack_entry();
4132                 return;
4133         }
4135         /* Move the current line to the first tree entry. */
4136         tree_lineno = 1;
4137         entry->lineno = lineno;
4140 /* Parse output from git-ls-tree(1):
4141  *
4142  * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
4143  */
4145 #define SIZEOF_TREE_ATTR \
4146         STRING_SIZE("100644 blob f931e1d229c3e185caad4449bf5b66ed72462657\t")
4148 #define SIZEOF_TREE_MODE \
4149         STRING_SIZE("100644 ")
4151 #define TREE_ID_OFFSET \
4152         STRING_SIZE("100644 blob ")
4154 struct tree_entry {
4155         char id[SIZEOF_REV];
4156         mode_t mode;
4157         time_t time;                    /* Date from the author ident. */
4158         const char *author;             /* Author of the commit. */
4159         char name[1];
4160 };
4162 static const char *
4163 tree_path(const struct line *line)
4165         return ((struct tree_entry *) line->data)->name;
4168 static int
4169 tree_compare_entry(const struct line *line1, const struct line *line2)
4171         if (line1->type != line2->type)
4172                 return line1->type == LINE_TREE_DIR ? -1 : 1;
4173         return strcmp(tree_path(line1), tree_path(line2));
4176 static const enum sort_field tree_sort_fields[] = {
4177         ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
4178 };
4179 static struct sort_state tree_sort_state = SORT_STATE(tree_sort_fields);
4181 static int
4182 tree_compare(const void *l1, const void *l2)
4184         const struct line *line1 = (const struct line *) l1;
4185         const struct line *line2 = (const struct line *) l2;
4186         const struct tree_entry *entry1 = ((const struct line *) l1)->data;
4187         const struct tree_entry *entry2 = ((const struct line *) l2)->data;
4189         if (line1->type == LINE_TREE_HEAD)
4190                 return -1;
4191         if (line2->type == LINE_TREE_HEAD)
4192                 return 1;
4194         switch (get_sort_field(tree_sort_state)) {
4195         case ORDERBY_DATE:
4196                 return sort_order(tree_sort_state, entry1->time - entry2->time);
4198         case ORDERBY_AUTHOR:
4199                 return sort_order(tree_sort_state, strcmp(entry1->author, entry2->author));
4201         case ORDERBY_NAME:
4202         default:
4203                 return sort_order(tree_sort_state, tree_compare_entry(line1, line2));
4204         }
4208 static struct line *
4209 tree_entry(struct view *view, enum line_type type, const char *path,
4210            const char *mode, const char *id)
4212         struct tree_entry *entry = calloc(1, sizeof(*entry) + strlen(path));
4213         struct line *line = entry ? add_line_data(view, entry, type) : NULL;
4215         if (!entry || !line) {
4216                 free(entry);
4217                 return NULL;
4218         }
4220         strncpy(entry->name, path, strlen(path));
4221         if (mode)
4222                 entry->mode = strtoul(mode, NULL, 8);
4223         if (id)
4224                 string_copy_rev(entry->id, id);
4226         return line;
4229 static bool
4230 tree_read_date(struct view *view, char *text, bool *read_date)
4232         static const char *author_name;
4233         static time_t author_time;
4235         if (!text && *read_date) {
4236                 *read_date = FALSE;
4237                 return TRUE;
4239         } else if (!text) {
4240                 char *path = *opt_path ? opt_path : ".";
4241                 /* Find next entry to process */
4242                 const char *log_file[] = {
4243                         "git", "log", "--no-color", "--pretty=raw",
4244                                 "--cc", "--raw", view->id, "--", path, NULL
4245                 };
4246                 struct io io = {};
4248                 if (!view->lines) {
4249                         tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL);
4250                         report("Tree is empty");
4251                         return TRUE;
4252                 }
4254                 if (!run_io_rd(&io, log_file, opt_cdup, FORMAT_NONE)) {
4255                         report("Failed to load tree data");
4256                         return TRUE;
4257                 }
4259                 done_io(view->pipe);
4260                 view->io = io;
4261                 *read_date = TRUE;
4262                 return FALSE;
4264         } else if (*text == 'a' && get_line_type(text) == LINE_AUTHOR) {
4265                 parse_author_line(text + STRING_SIZE("author "),
4266                                   &author_name, &author_time);
4268         } else if (*text == ':') {
4269                 char *pos;
4270                 size_t annotated = 1;
4271                 size_t i;
4273                 pos = strchr(text, '\t');
4274                 if (!pos)
4275                         return TRUE;
4276                 text = pos + 1;
4277                 if (*opt_path && !strncmp(text, opt_path, strlen(opt_path)))
4278                         text += strlen(opt_path);
4279                 pos = strchr(text, '/');
4280                 if (pos)
4281                         *pos = 0;
4283                 for (i = 1; i < view->lines; i++) {
4284                         struct line *line = &view->line[i];
4285                         struct tree_entry *entry = line->data;
4287                         annotated += !!entry->author;
4288                         if (entry->author || strcmp(entry->name, text))
4289                                 continue;
4291                         entry->author = author_name;
4292                         entry->time = author_time;
4293                         line->dirty = 1;
4294                         break;
4295                 }
4297                 if (annotated == view->lines)
4298                         kill_io(view->pipe);
4299         }
4300         return TRUE;
4303 static bool
4304 tree_read(struct view *view, char *text)
4306         static bool read_date = FALSE;
4307         struct tree_entry *data;
4308         struct line *entry, *line;
4309         enum line_type type;
4310         size_t textlen = text ? strlen(text) : 0;
4311         char *path = text + SIZEOF_TREE_ATTR;
4313         if (read_date || !text)
4314                 return tree_read_date(view, text, &read_date);
4316         if (textlen <= SIZEOF_TREE_ATTR)
4317                 return FALSE;
4318         if (view->lines == 0 &&
4319             !tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL))
4320                 return FALSE;
4322         /* Strip the path part ... */
4323         if (*opt_path) {
4324                 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
4325                 size_t striplen = strlen(opt_path);
4327                 if (pathlen > striplen)
4328                         memmove(path, path + striplen,
4329                                 pathlen - striplen + 1);
4331                 /* Insert "link" to parent directory. */
4332                 if (view->lines == 1 &&
4333                     !tree_entry(view, LINE_TREE_DIR, "..", "040000", view->ref))
4334                         return FALSE;
4335         }
4337         type = text[SIZEOF_TREE_MODE] == 't' ? LINE_TREE_DIR : LINE_TREE_FILE;
4338         entry = tree_entry(view, type, path, text, text + TREE_ID_OFFSET);
4339         if (!entry)
4340                 return FALSE;
4341         data = entry->data;
4343         /* Skip "Directory ..." and ".." line. */
4344         for (line = &view->line[1 + !!*opt_path]; line < entry; line++) {
4345                 if (tree_compare_entry(line, entry) <= 0)
4346                         continue;
4348                 memmove(line + 1, line, (entry - line) * sizeof(*entry));
4350                 line->data = data;
4351                 line->type = type;
4352                 for (; line <= entry; line++)
4353                         line->dirty = line->cleareol = 1;
4354                 return TRUE;
4355         }
4357         if (tree_lineno > view->lineno) {
4358                 view->lineno = tree_lineno;
4359                 tree_lineno = 0;
4360         }
4362         return TRUE;
4365 static bool
4366 tree_draw(struct view *view, struct line *line, unsigned int lineno)
4368         struct tree_entry *entry = line->data;
4370         if (line->type == LINE_TREE_HEAD) {
4371                 if (draw_text(view, line->type, "Directory path /", TRUE))
4372                         return TRUE;
4373         } else {
4374                 if (draw_mode(view, entry->mode))
4375                         return TRUE;
4377                 if (opt_author && draw_author(view, entry->author))
4378                         return TRUE;
4380                 if (opt_date && draw_date(view, entry->author ? &entry->time : NULL))
4381                         return TRUE;
4382         }
4383         if (draw_text(view, line->type, entry->name, TRUE))
4384                 return TRUE;
4385         return TRUE;
4388 static void
4389 open_blob_editor()
4391         char file[SIZEOF_STR] = "/tmp/tigblob.XXXXXX";
4392         int fd = mkstemp(file);
4394         if (fd == -1)
4395                 report("Failed to create temporary file");
4396         else if (!run_io_append(blob_ops.argv, FORMAT_ALL, fd))
4397                 report("Failed to save blob data to file");
4398         else
4399                 open_editor(FALSE, file);
4400         if (fd != -1)
4401                 unlink(file);
4404 static enum request
4405 tree_request(struct view *view, enum request request, struct line *line)
4407         enum open_flags flags;
4409         switch (request) {
4410         case REQ_VIEW_BLAME:
4411                 if (line->type != LINE_TREE_FILE) {
4412                         report("Blame only supported for files");
4413                         return REQ_NONE;
4414                 }
4416                 string_copy(opt_ref, view->vid);
4417                 return request;
4419         case REQ_EDIT:
4420                 if (line->type != LINE_TREE_FILE) {
4421                         report("Edit only supported for files");
4422                 } else if (!is_head_commit(view->vid)) {
4423                         open_blob_editor();
4424                 } else {
4425                         open_editor(TRUE, opt_file);
4426                 }
4427                 return REQ_NONE;
4429         case REQ_TOGGLE_SORT_FIELD:
4430         case REQ_TOGGLE_SORT_ORDER:
4431                 sort_view(view, request, &tree_sort_state, tree_compare);
4432                 return REQ_NONE;
4434         case REQ_PARENT:
4435                 if (!*opt_path) {
4436                         /* quit view if at top of tree */
4437                         return REQ_VIEW_CLOSE;
4438                 }
4439                 /* fake 'cd  ..' */
4440                 line = &view->line[1];
4441                 break;
4443         case REQ_ENTER:
4444                 break;
4446         default:
4447                 return request;
4448         }
4450         /* Cleanup the stack if the tree view is at a different tree. */
4451         while (!*opt_path && tree_stack)
4452                 pop_tree_stack_entry();
4454         switch (line->type) {
4455         case LINE_TREE_DIR:
4456                 /* Depending on whether it is a subdirectory or parent link
4457                  * mangle the path buffer. */
4458                 if (line == &view->line[1] && *opt_path) {
4459                         pop_tree_stack_entry();
4461                 } else {
4462                         const char *basename = tree_path(line);
4464                         push_tree_stack_entry(basename, view->lineno);
4465                 }
4467                 /* Trees and subtrees share the same ID, so they are not not
4468                  * unique like blobs. */
4469                 flags = OPEN_RELOAD;
4470                 request = REQ_VIEW_TREE;
4471                 break;
4473         case LINE_TREE_FILE:
4474                 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4475                 request = REQ_VIEW_BLOB;
4476                 break;
4478         default:
4479                 return REQ_NONE;
4480         }
4482         open_view(view, request, flags);
4483         if (request == REQ_VIEW_TREE)
4484                 view->lineno = tree_lineno;
4486         return REQ_NONE;
4489 static bool
4490 tree_grep(struct view *view, struct line *line)
4492         struct tree_entry *entry = line->data;
4493         const char *text[] = {
4494                 entry->name,
4495                 opt_author ? entry->author : "",
4496                 opt_date ? mkdate(&entry->time) : "",
4497                 NULL
4498         };
4500         return grep_text(view, text);
4503 static void
4504 tree_select(struct view *view, struct line *line)
4506         struct tree_entry *entry = line->data;
4508         if (line->type == LINE_TREE_FILE) {
4509                 string_copy_rev(ref_blob, entry->id);
4510                 string_format(opt_file, "%s%s", opt_path, tree_path(line));
4512         } else if (line->type != LINE_TREE_DIR) {
4513                 return;
4514         }
4516         string_copy_rev(view->ref, entry->id);
4519 static bool
4520 tree_prepare(struct view *view)
4522         if (view->lines == 0 && opt_prefix[0]) {
4523                 char *pos = opt_prefix;
4525                 while (pos && *pos) {
4526                         char *end = strchr(pos, '/');
4528                         if (end)
4529                                 *end = 0;
4530                         push_tree_stack_entry(pos, 0);
4531                         pos = end;
4532                         if (end) {
4533                                 *end = '/';
4534                                 pos++;
4535                         }
4536                 }
4538         } else if (strcmp(view->vid, view->id)) {
4539                 opt_path[0] = 0;
4540         }
4542         return init_io_rd(&view->io, view->ops->argv, opt_cdup, FORMAT_ALL);
4545 static const char *tree_argv[SIZEOF_ARG] = {
4546         "git", "ls-tree", "%(commit)", "%(directory)", NULL
4547 };
4549 static struct view_ops tree_ops = {
4550         "file",
4551         tree_argv,
4552         NULL,
4553         tree_read,
4554         tree_draw,
4555         tree_request,
4556         tree_grep,
4557         tree_select,
4558         tree_prepare,
4559 };
4561 static bool
4562 blob_read(struct view *view, char *line)
4564         if (!line)
4565                 return TRUE;
4566         return add_line_text(view, line, LINE_DEFAULT) != NULL;
4569 static enum request
4570 blob_request(struct view *view, enum request request, struct line *line)
4572         switch (request) {
4573         case REQ_EDIT:
4574                 open_blob_editor();
4575                 return REQ_NONE;
4576         default:
4577                 return pager_request(view, request, line);
4578         }
4581 static const char *blob_argv[SIZEOF_ARG] = {
4582         "git", "cat-file", "blob", "%(blob)", NULL
4583 };
4585 static struct view_ops blob_ops = {
4586         "line",
4587         blob_argv,
4588         NULL,
4589         blob_read,
4590         pager_draw,
4591         blob_request,
4592         pager_grep,
4593         pager_select,
4594 };
4596 /*
4597  * Blame backend
4598  *
4599  * Loading the blame view is a two phase job:
4600  *
4601  *  1. File content is read either using opt_file from the
4602  *     filesystem or using git-cat-file.
4603  *  2. Then blame information is incrementally added by
4604  *     reading output from git-blame.
4605  */
4607 static const char *blame_head_argv[] = {
4608         "git", "blame", "--incremental", "--", "%(file)", NULL
4609 };
4611 static const char *blame_ref_argv[] = {
4612         "git", "blame", "--incremental", "%(ref)", "--", "%(file)", NULL
4613 };
4615 static const char *blame_cat_file_argv[] = {
4616         "git", "cat-file", "blob", "%(ref):%(file)", NULL
4617 };
4619 struct blame_commit {
4620         char id[SIZEOF_REV];            /* SHA1 ID. */
4621         char title[128];                /* First line of the commit message. */
4622         const char *author;             /* Author of the commit. */
4623         time_t time;                    /* Date from the author ident. */
4624         char filename[128];             /* Name of file. */
4625         bool has_previous;              /* Was a "previous" line detected. */
4626 };
4628 struct blame {
4629         struct blame_commit *commit;
4630         unsigned long lineno;
4631         char text[1];
4632 };
4634 static bool
4635 blame_open(struct view *view)
4637         char path[SIZEOF_STR];
4639         if (!view->parent && *opt_prefix) {
4640                 string_copy(path, opt_file);
4641                 if (!string_format(opt_file, "%s%s", opt_prefix, path))
4642                         return FALSE;
4643         }
4645         if (!string_format(path, "%s%s", opt_cdup, opt_file))
4646                 return FALSE;
4648         if (*opt_ref || !io_open(&view->io, path)) {
4649                 if (!run_io_rd(&view->io, blame_cat_file_argv, opt_cdup, FORMAT_ALL))
4650                         return FALSE;
4651         }
4653         setup_update(view, opt_file);
4654         string_format(view->ref, "%s ...", opt_file);
4656         return TRUE;
4659 static struct blame_commit *
4660 get_blame_commit(struct view *view, const char *id)
4662         size_t i;
4664         for (i = 0; i < view->lines; i++) {
4665                 struct blame *blame = view->line[i].data;
4667                 if (!blame->commit)
4668                         continue;
4670                 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
4671                         return blame->commit;
4672         }
4674         {
4675                 struct blame_commit *commit = calloc(1, sizeof(*commit));
4677                 if (commit)
4678                         string_ncopy(commit->id, id, SIZEOF_REV);
4679                 return commit;
4680         }
4683 static bool
4684 parse_number(const char **posref, size_t *number, size_t min, size_t max)
4686         const char *pos = *posref;
4688         *posref = NULL;
4689         pos = strchr(pos + 1, ' ');
4690         if (!pos || !isdigit(pos[1]))
4691                 return FALSE;
4692         *number = atoi(pos + 1);
4693         if (*number < min || *number > max)
4694                 return FALSE;
4696         *posref = pos;
4697         return TRUE;
4700 static struct blame_commit *
4701 parse_blame_commit(struct view *view, const char *text, int *blamed)
4703         struct blame_commit *commit;
4704         struct blame *blame;
4705         const char *pos = text + SIZEOF_REV - 2;
4706         size_t orig_lineno = 0;
4707         size_t lineno;
4708         size_t group;
4710         if (strlen(text) <= SIZEOF_REV || pos[1] != ' ')
4711                 return NULL;
4713         if (!parse_number(&pos, &orig_lineno, 1, 9999999) ||
4714             !parse_number(&pos, &lineno, 1, view->lines) ||
4715             !parse_number(&pos, &group, 1, view->lines - lineno + 1))
4716                 return NULL;
4718         commit = get_blame_commit(view, text);
4719         if (!commit)
4720                 return NULL;
4722         *blamed += group;
4723         while (group--) {
4724                 struct line *line = &view->line[lineno + group - 1];
4726                 blame = line->data;
4727                 blame->commit = commit;
4728                 blame->lineno = orig_lineno + group - 1;
4729                 line->dirty = 1;
4730         }
4732         return commit;
4735 static bool
4736 blame_read_file(struct view *view, const char *line, bool *read_file)
4738         if (!line) {
4739                 const char **argv = *opt_ref ? blame_ref_argv : blame_head_argv;
4740                 struct io io = {};
4742                 if (view->lines == 0 && !view->parent)
4743                         die("No blame exist for %s", view->vid);
4745                 if (view->lines == 0 || !run_io_rd(&io, argv, opt_cdup, FORMAT_ALL)) {
4746                         report("Failed to load blame data");
4747                         return TRUE;
4748                 }
4750                 done_io(view->pipe);
4751                 view->io = io;
4752                 *read_file = FALSE;
4753                 return FALSE;
4755         } else {
4756                 size_t linelen = strlen(line);
4757                 struct blame *blame = malloc(sizeof(*blame) + linelen);
4759                 if (!blame)
4760                         return FALSE;
4762                 blame->commit = NULL;
4763                 strncpy(blame->text, line, linelen);
4764                 blame->text[linelen] = 0;
4765                 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
4766         }
4769 static bool
4770 match_blame_header(const char *name, char **line)
4772         size_t namelen = strlen(name);
4773         bool matched = !strncmp(name, *line, namelen);
4775         if (matched)
4776                 *line += namelen;
4778         return matched;
4781 static bool
4782 blame_read(struct view *view, char *line)
4784         static struct blame_commit *commit = NULL;
4785         static int blamed = 0;
4786         static bool read_file = TRUE;
4788         if (read_file)
4789                 return blame_read_file(view, line, &read_file);
4791         if (!line) {
4792                 /* Reset all! */
4793                 commit = NULL;
4794                 blamed = 0;
4795                 read_file = TRUE;
4796                 string_format(view->ref, "%s", view->vid);
4797                 if (view_is_displayed(view)) {
4798                         update_view_title(view);
4799                         redraw_view_from(view, 0);
4800                 }
4801                 return TRUE;
4802         }
4804         if (!commit) {
4805                 commit = parse_blame_commit(view, line, &blamed);
4806                 string_format(view->ref, "%s %2d%%", view->vid,
4807                               view->lines ? blamed * 100 / view->lines : 0);
4809         } else if (match_blame_header("author ", &line)) {
4810                 commit->author = get_author(line);
4812         } else if (match_blame_header("author-time ", &line)) {
4813                 commit->time = (time_t) atol(line);
4815         } else if (match_blame_header("author-tz ", &line)) {
4816                 parse_timezone(&commit->time, line);
4818         } else if (match_blame_header("summary ", &line)) {
4819                 string_ncopy(commit->title, line, strlen(line));
4821         } else if (match_blame_header("previous ", &line)) {
4822                 commit->has_previous = TRUE;
4824         } else if (match_blame_header("filename ", &line)) {
4825                 string_ncopy(commit->filename, line, strlen(line));
4826                 commit = NULL;
4827         }
4829         return TRUE;
4832 static bool
4833 blame_draw(struct view *view, struct line *line, unsigned int lineno)
4835         struct blame *blame = line->data;
4836         time_t *time = NULL;
4837         const char *id = NULL, *author = NULL;
4838         char text[SIZEOF_STR];
4840         if (blame->commit && *blame->commit->filename) {
4841                 id = blame->commit->id;
4842                 author = blame->commit->author;
4843                 time = &blame->commit->time;
4844         }
4846         if (opt_date && draw_date(view, time))
4847                 return TRUE;
4849         if (opt_author && draw_author(view, author))
4850                 return TRUE;
4852         if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
4853                 return TRUE;
4855         if (draw_lineno(view, lineno))
4856                 return TRUE;
4858         string_expand(text, sizeof(text), blame->text, opt_tab_size);
4859         draw_text(view, LINE_DEFAULT, text, TRUE);
4860         return TRUE;
4863 static bool
4864 check_blame_commit(struct blame *blame, bool check_null_id)
4866         if (!blame->commit)
4867                 report("Commit data not loaded yet");
4868         else if (check_null_id && !strcmp(blame->commit->id, NULL_ID))
4869                 report("No commit exist for the selected line");
4870         else
4871                 return TRUE;
4872         return FALSE;
4875 static void
4876 setup_blame_parent_line(struct view *view, struct blame *blame)
4878         const char *diff_tree_argv[] = {
4879                 "git", "diff-tree", "-U0", blame->commit->id,
4880                         "--", blame->commit->filename, NULL
4881         };
4882         struct io io = {};
4883         int parent_lineno = -1;
4884         int blamed_lineno = -1;
4885         char *line;
4887         if (!run_io(&io, diff_tree_argv, NULL, IO_RD))
4888                 return;
4890         while ((line = io_get(&io, '\n', TRUE))) {
4891                 if (*line == '@') {
4892                         char *pos = strchr(line, '+');
4894                         parent_lineno = atoi(line + 4);
4895                         if (pos)
4896                                 blamed_lineno = atoi(pos + 1);
4898                 } else if (*line == '+' && parent_lineno != -1) {
4899                         if (blame->lineno == blamed_lineno - 1 &&
4900                             !strcmp(blame->text, line + 1)) {
4901                                 view->lineno = parent_lineno ? parent_lineno - 1 : 0;
4902                                 break;
4903                         }
4904                         blamed_lineno++;
4905                 }
4906         }
4908         done_io(&io);
4911 static enum request
4912 blame_request(struct view *view, enum request request, struct line *line)
4914         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4915         struct blame *blame = line->data;
4917         switch (request) {
4918         case REQ_VIEW_BLAME:
4919                 if (check_blame_commit(blame, TRUE)) {
4920                         string_copy(opt_ref, blame->commit->id);
4921                         string_copy(opt_file, blame->commit->filename);
4922                         if (blame->lineno)
4923                                 view->lineno = blame->lineno;
4924                         open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4925                 }
4926                 break;
4928         case REQ_PARENT:
4929                 if (check_blame_commit(blame, TRUE) &&
4930                     select_commit_parent(blame->commit->id, opt_ref,
4931                                          blame->commit->filename)) {
4932                         string_copy(opt_file, blame->commit->filename);
4933                         setup_blame_parent_line(view, blame);
4934                         open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4935                 }
4936                 break;
4938         case REQ_ENTER:
4939                 if (!check_blame_commit(blame, FALSE))
4940                         break;
4942                 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
4943                     !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
4944                         break;
4946                 if (!strcmp(blame->commit->id, NULL_ID)) {
4947                         struct view *diff = VIEW(REQ_VIEW_DIFF);
4948                         const char *diff_index_argv[] = {
4949                                 "git", "diff-index", "--root", "--patch-with-stat",
4950                                         "-C", "-M", "HEAD", "--", view->vid, NULL
4951                         };
4953                         if (!blame->commit->has_previous) {
4954                                 diff_index_argv[1] = "diff";
4955                                 diff_index_argv[2] = "--no-color";
4956                                 diff_index_argv[6] = "--";
4957                                 diff_index_argv[7] = "/dev/null";
4958                         }
4960                         if (!prepare_update(diff, diff_index_argv, NULL, FORMAT_DASH)) {
4961                                 report("Failed to allocate diff command");
4962                                 break;
4963                         }
4964                         flags |= OPEN_PREPARED;
4965                 }
4967                 open_view(view, REQ_VIEW_DIFF, flags);
4968                 if (VIEW(REQ_VIEW_DIFF)->pipe && !strcmp(blame->commit->id, NULL_ID))
4969                         string_copy_rev(VIEW(REQ_VIEW_DIFF)->ref, NULL_ID);
4970                 break;
4972         default:
4973                 return request;
4974         }
4976         return REQ_NONE;
4979 static bool
4980 blame_grep(struct view *view, struct line *line)
4982         struct blame *blame = line->data;
4983         struct blame_commit *commit = blame->commit;
4984         const char *text[] = {
4985                 blame->text,
4986                 commit ? commit->title : "",
4987                 commit ? commit->id : "",
4988                 commit && opt_author ? commit->author : "",
4989                 commit && opt_date ? mkdate(&commit->time) : "",
4990                 NULL
4991         };
4993         return grep_text(view, text);
4996 static void
4997 blame_select(struct view *view, struct line *line)
4999         struct blame *blame = line->data;
5000         struct blame_commit *commit = blame->commit;
5002         if (!commit)
5003                 return;
5005         if (!strcmp(commit->id, NULL_ID))
5006                 string_ncopy(ref_commit, "HEAD", 4);
5007         else
5008                 string_copy_rev(ref_commit, commit->id);
5011 static struct view_ops blame_ops = {
5012         "line",
5013         NULL,
5014         blame_open,
5015         blame_read,
5016         blame_draw,
5017         blame_request,
5018         blame_grep,
5019         blame_select,
5020 };
5022 /*
5023  * Branch backend
5024  */
5026 struct branch {
5027         const char *author;             /* Author of the last commit. */
5028         time_t time;                    /* Date of the last activity. */
5029         struct ref *ref;                /* Name and commit ID information. */
5030 };
5032 static const enum sort_field branch_sort_fields[] = {
5033         ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
5034 };
5035 static struct sort_state branch_sort_state = SORT_STATE(branch_sort_fields);
5037 static int
5038 branch_compare(const void *l1, const void *l2)
5040         const struct branch *branch1 = ((const struct line *) l1)->data;
5041         const struct branch *branch2 = ((const struct line *) l2)->data;
5043         switch (get_sort_field(branch_sort_state)) {
5044         case ORDERBY_DATE:
5045                 return sort_order(branch_sort_state, branch1->time - branch2->time);
5047         case ORDERBY_AUTHOR:
5048                 return sort_order(branch_sort_state, strcmp(branch1->author, branch2->author));
5050         case ORDERBY_NAME:
5051         default:
5052                 return sort_order(branch_sort_state, strcmp(branch1->ref->name, branch2->ref->name));
5053         }
5056 static bool
5057 branch_draw(struct view *view, struct line *line, unsigned int lineno)
5059         struct branch *branch = line->data;
5060         enum line_type type = branch->ref->head ? LINE_MAIN_HEAD : LINE_DEFAULT;
5062         if (opt_date && draw_date(view, branch->author ? &branch->time : NULL))
5063                 return TRUE;
5065         if (opt_author && draw_author(view, branch->author))
5066                 return TRUE;
5068         draw_text(view, type, branch->ref->name, TRUE);
5069         return TRUE;
5072 static enum request
5073 branch_request(struct view *view, enum request request, struct line *line)
5075         switch (request) {
5076         case REQ_REFRESH:
5077                 load_refs();
5078                 open_view(view, REQ_VIEW_BRANCH, OPEN_REFRESH);
5079                 return REQ_NONE;
5081         case REQ_TOGGLE_SORT_FIELD:
5082         case REQ_TOGGLE_SORT_ORDER:
5083                 sort_view(view, request, &branch_sort_state, branch_compare);
5084                 return REQ_NONE;
5086         case REQ_ENTER:
5087                 open_view(view, REQ_VIEW_MAIN, OPEN_SPLIT);
5088                 return REQ_NONE;
5090         default:
5091                 return request;
5092         }
5095 static bool
5096 branch_read(struct view *view, char *line)
5098         static char id[SIZEOF_REV];
5099         struct branch *reference;
5100         size_t i;
5102         if (!line)
5103                 return TRUE;
5105         switch (get_line_type(line)) {
5106         case LINE_COMMIT:
5107                 string_copy_rev(id, line + STRING_SIZE("commit "));
5108                 return TRUE;
5110         case LINE_AUTHOR:
5111                 for (i = 0, reference = NULL; i < view->lines; i++) {
5112                         struct branch *branch = view->line[i].data;
5114                         if (strcmp(branch->ref->id, id))
5115                                 continue;
5117                         view->line[i].dirty = TRUE;
5118                         if (reference) {
5119                                 branch->author = reference->author;
5120                                 branch->time = reference->time;
5121                                 continue;
5122                         }
5124                         parse_author_line(line + STRING_SIZE("author "),
5125                                           &branch->author, &branch->time);
5126                         reference = branch;
5127                 }
5128                 return TRUE;
5130         default:
5131                 return TRUE;
5132         }
5136 static bool
5137 branch_open_visitor(void *data, struct ref *ref)
5139         struct view *view = data;
5140         struct branch *branch;
5142         if (ref->tag || ref->ltag || ref->remote)
5143                 return TRUE;
5145         branch = calloc(1, sizeof(*branch));
5146         if (!branch)
5147                 return FALSE;
5149         branch->ref = ref;
5150         return !!add_line_data(view, branch, LINE_DEFAULT);
5153 static bool
5154 branch_open(struct view *view)
5156         const char *branch_log[] = {
5157                 "git", "log", "--no-color", "--pretty=raw",
5158                         "--simplify-by-decoration", "--all", NULL
5159         };
5161         if (!run_io_rd(&view->io, branch_log, NULL, FORMAT_NONE)) {
5162                 report("Failed to load branch data");
5163                 return TRUE;
5164         }
5166         setup_update(view, view->id);
5167         foreach_ref(branch_open_visitor, view);
5168         view->p_restore = TRUE;
5170         return TRUE;
5173 static bool
5174 branch_grep(struct view *view, struct line *line)
5176         struct branch *branch = line->data;
5177         const char *text[] = {
5178                 branch->ref->name,
5179                 branch->author,
5180                 NULL
5181         };
5183         return grep_text(view, text);
5186 static void
5187 branch_select(struct view *view, struct line *line)
5189         struct branch *branch = line->data;
5191         string_copy_rev(view->ref, branch->ref->id);
5192         string_copy_rev(ref_commit, branch->ref->id);
5193         string_copy_rev(ref_head, branch->ref->id);
5196 static struct view_ops branch_ops = {
5197         "branch",
5198         NULL,
5199         branch_open,
5200         branch_read,
5201         branch_draw,
5202         branch_request,
5203         branch_grep,
5204         branch_select,
5205 };
5207 /*
5208  * Status backend
5209  */
5211 struct status {
5212         char status;
5213         struct {
5214                 mode_t mode;
5215                 char rev[SIZEOF_REV];
5216                 char name[SIZEOF_STR];
5217         } old;
5218         struct {
5219                 mode_t mode;
5220                 char rev[SIZEOF_REV];
5221                 char name[SIZEOF_STR];
5222         } new;
5223 };
5225 static char status_onbranch[SIZEOF_STR];
5226 static struct status stage_status;
5227 static enum line_type stage_line_type;
5228 static size_t stage_chunks;
5229 static int *stage_chunk;
5231 DEFINE_ALLOCATOR(realloc_ints, int, 32)
5233 /* This should work even for the "On branch" line. */
5234 static inline bool
5235 status_has_none(struct view *view, struct line *line)
5237         return line < view->line + view->lines && !line[1].data;
5240 /* Get fields from the diff line:
5241  * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
5242  */
5243 static inline bool
5244 status_get_diff(struct status *file, const char *buf, size_t bufsize)
5246         const char *old_mode = buf +  1;
5247         const char *new_mode = buf +  8;
5248         const char *old_rev  = buf + 15;
5249         const char *new_rev  = buf + 56;
5250         const char *status   = buf + 97;
5252         if (bufsize < 98 ||
5253             old_mode[-1] != ':' ||
5254             new_mode[-1] != ' ' ||
5255             old_rev[-1]  != ' ' ||
5256             new_rev[-1]  != ' ' ||
5257             status[-1]   != ' ')
5258                 return FALSE;
5260         file->status = *status;
5262         string_copy_rev(file->old.rev, old_rev);
5263         string_copy_rev(file->new.rev, new_rev);
5265         file->old.mode = strtoul(old_mode, NULL, 8);
5266         file->new.mode = strtoul(new_mode, NULL, 8);
5268         file->old.name[0] = file->new.name[0] = 0;
5270         return TRUE;
5273 static bool
5274 status_run(struct view *view, const char *argv[], char status, enum line_type type)
5276         struct status *unmerged = NULL;
5277         char *buf;
5278         struct io io = {};
5280         if (!run_io(&io, argv, NULL, IO_RD))
5281                 return FALSE;
5283         add_line_data(view, NULL, type);
5285         while ((buf = io_get(&io, 0, TRUE))) {
5286                 struct status *file = unmerged;
5288                 if (!file) {
5289                         file = calloc(1, sizeof(*file));
5290                         if (!file || !add_line_data(view, file, type))
5291                                 goto error_out;
5292                 }
5294                 /* Parse diff info part. */
5295                 if (status) {
5296                         file->status = status;
5297                         if (status == 'A')
5298                                 string_copy(file->old.rev, NULL_ID);
5300                 } else if (!file->status || file == unmerged) {
5301                         if (!status_get_diff(file, buf, strlen(buf)))
5302                                 goto error_out;
5304                         buf = io_get(&io, 0, TRUE);
5305                         if (!buf)
5306                                 break;
5308                         /* Collapse all modified entries that follow an
5309                          * associated unmerged entry. */
5310                         if (unmerged == file) {
5311                                 unmerged->status = 'U';
5312                                 unmerged = NULL;
5313                         } else if (file->status == 'U') {
5314                                 unmerged = file;
5315                         }
5316                 }
5318                 /* Grab the old name for rename/copy. */
5319                 if (!*file->old.name &&
5320                     (file->status == 'R' || file->status == 'C')) {
5321                         string_ncopy(file->old.name, buf, strlen(buf));
5323                         buf = io_get(&io, 0, TRUE);
5324                         if (!buf)
5325                                 break;
5326                 }
5328                 /* git-ls-files just delivers a NUL separated list of
5329                  * file names similar to the second half of the
5330                  * git-diff-* output. */
5331                 string_ncopy(file->new.name, buf, strlen(buf));
5332                 if (!*file->old.name)
5333                         string_copy(file->old.name, file->new.name);
5334                 file = NULL;
5335         }
5337         if (io_error(&io)) {
5338 error_out:
5339                 done_io(&io);
5340                 return FALSE;
5341         }
5343         if (!view->line[view->lines - 1].data)
5344                 add_line_data(view, NULL, LINE_STAT_NONE);
5346         done_io(&io);
5347         return TRUE;
5350 /* Don't show unmerged entries in the staged section. */
5351 static const char *status_diff_index_argv[] = {
5352         "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
5353                              "--cached", "-M", "HEAD", NULL
5354 };
5356 static const char *status_diff_files_argv[] = {
5357         "git", "diff-files", "-z", NULL
5358 };
5360 static const char *status_list_other_argv[] = {
5361         "git", "ls-files", "-z", "--others", "--exclude-standard", NULL
5362 };
5364 static const char *status_list_no_head_argv[] = {
5365         "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
5366 };
5368 static const char *update_index_argv[] = {
5369         "git", "update-index", "-q", "--unmerged", "--refresh", NULL
5370 };
5372 /* Restore the previous line number to stay in the context or select a
5373  * line with something that can be updated. */
5374 static void
5375 status_restore(struct view *view)
5377         if (view->p_lineno >= view->lines)
5378                 view->p_lineno = view->lines - 1;
5379         while (view->p_lineno < view->lines && !view->line[view->p_lineno].data)
5380                 view->p_lineno++;
5381         while (view->p_lineno > 0 && !view->line[view->p_lineno].data)
5382                 view->p_lineno--;
5384         /* If the above fails, always skip the "On branch" line. */
5385         if (view->p_lineno < view->lines)
5386                 view->lineno = view->p_lineno;
5387         else
5388                 view->lineno = 1;
5390         if (view->lineno < view->offset)
5391                 view->offset = view->lineno;
5392         else if (view->offset + view->height <= view->lineno)
5393                 view->offset = view->lineno - view->height + 1;
5395         view->p_restore = FALSE;
5398 static void
5399 status_update_onbranch(void)
5401         static const char *paths[][2] = {
5402                 { "rebase-apply/rebasing",      "Rebasing" },
5403                 { "rebase-apply/applying",      "Applying mailbox" },
5404                 { "rebase-apply/",              "Rebasing mailbox" },
5405                 { "rebase-merge/interactive",   "Interactive rebase" },
5406                 { "rebase-merge/",              "Rebase merge" },
5407                 { "MERGE_HEAD",                 "Merging" },
5408                 { "BISECT_LOG",                 "Bisecting" },
5409                 { "HEAD",                       "On branch" },
5410         };
5411         char buf[SIZEOF_STR];
5412         struct stat stat;
5413         int i;
5415         if (is_initial_commit()) {
5416                 string_copy(status_onbranch, "Initial commit");
5417                 return;
5418         }
5420         for (i = 0; i < ARRAY_SIZE(paths); i++) {
5421                 char *head = opt_head;
5423                 if (!string_format(buf, "%s/%s", opt_git_dir, paths[i][0]) ||
5424                     lstat(buf, &stat) < 0)
5425                         continue;
5427                 if (!*opt_head) {
5428                         struct io io = {};
5430                         if (string_format(buf, "%s/rebase-merge/head-name", opt_git_dir) &&
5431                             io_open(&io, buf) &&
5432                             io_read_buf(&io, buf, sizeof(buf))) {
5433                                 head = buf;
5434                                 if (!prefixcmp(head, "refs/heads/"))
5435                                         head += STRING_SIZE("refs/heads/");
5436                         }
5437                 }
5439                 if (!string_format(status_onbranch, "%s %s", paths[i][1], head))
5440                         string_copy(status_onbranch, opt_head);
5441                 return;
5442         }
5444         string_copy(status_onbranch, "Not currently on any branch");
5447 /* First parse staged info using git-diff-index(1), then parse unstaged
5448  * info using git-diff-files(1), and finally untracked files using
5449  * git-ls-files(1). */
5450 static bool
5451 status_open(struct view *view)
5453         reset_view(view);
5455         add_line_data(view, NULL, LINE_STAT_HEAD);
5456         status_update_onbranch();
5458         run_io_bg(update_index_argv);
5460         if (is_initial_commit()) {
5461                 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
5462                         return FALSE;
5463         } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
5464                 return FALSE;
5465         }
5467         if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
5468             !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
5469                 return FALSE;
5471         /* Restore the exact position or use the specialized restore
5472          * mode? */
5473         if (!view->p_restore)
5474                 status_restore(view);
5475         return TRUE;
5478 static bool
5479 status_draw(struct view *view, struct line *line, unsigned int lineno)
5481         struct status *status = line->data;
5482         enum line_type type;
5483         const char *text;
5485         if (!status) {
5486                 switch (line->type) {
5487                 case LINE_STAT_STAGED:
5488                         type = LINE_STAT_SECTION;
5489                         text = "Changes to be committed:";
5490                         break;
5492                 case LINE_STAT_UNSTAGED:
5493                         type = LINE_STAT_SECTION;
5494                         text = "Changed but not updated:";
5495                         break;
5497                 case LINE_STAT_UNTRACKED:
5498                         type = LINE_STAT_SECTION;
5499                         text = "Untracked files:";
5500                         break;
5502                 case LINE_STAT_NONE:
5503                         type = LINE_DEFAULT;
5504                         text = "  (no files)";
5505                         break;
5507                 case LINE_STAT_HEAD:
5508                         type = LINE_STAT_HEAD;
5509                         text = status_onbranch;
5510                         break;
5512                 default:
5513                         return FALSE;
5514                 }
5515         } else {
5516                 static char buf[] = { '?', ' ', ' ', ' ', 0 };
5518                 buf[0] = status->status;
5519                 if (draw_text(view, line->type, buf, TRUE))
5520                         return TRUE;
5521                 type = LINE_DEFAULT;
5522                 text = status->new.name;
5523         }
5525         draw_text(view, type, text, TRUE);
5526         return TRUE;
5529 static enum request
5530 status_load_error(struct view *view, struct view *stage, const char *path)
5532         if (displayed_views() == 2 || display[current_view] != view)
5533                 maximize_view(view);
5534         report("Failed to load '%s': %s", path, io_strerror(&stage->io));
5535         return REQ_NONE;
5538 static enum request
5539 status_enter(struct view *view, struct line *line)
5541         struct status *status = line->data;
5542         const char *oldpath = status ? status->old.name : NULL;
5543         /* Diffs for unmerged entries are empty when passing the new
5544          * path, so leave it empty. */
5545         const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
5546         const char *info;
5547         enum open_flags split;
5548         struct view *stage = VIEW(REQ_VIEW_STAGE);
5550         if (line->type == LINE_STAT_NONE ||
5551             (!status && line[1].type == LINE_STAT_NONE)) {
5552                 report("No file to diff");
5553                 return REQ_NONE;
5554         }
5556         switch (line->type) {
5557         case LINE_STAT_STAGED:
5558                 if (is_initial_commit()) {
5559                         const char *no_head_diff_argv[] = {
5560                                 "git", "diff", "--no-color", "--patch-with-stat",
5561                                         "--", "/dev/null", newpath, NULL
5562                         };
5564                         if (!prepare_update(stage, no_head_diff_argv, opt_cdup, FORMAT_DASH))
5565                                 return status_load_error(view, stage, newpath);
5566                 } else {
5567                         const char *index_show_argv[] = {
5568                                 "git", "diff-index", "--root", "--patch-with-stat",
5569                                         "-C", "-M", "--cached", "HEAD", "--",
5570                                         oldpath, newpath, NULL
5571                         };
5573                         if (!prepare_update(stage, index_show_argv, opt_cdup, FORMAT_DASH))
5574                                 return status_load_error(view, stage, newpath);
5575                 }
5577                 if (status)
5578                         info = "Staged changes to %s";
5579                 else
5580                         info = "Staged changes";
5581                 break;
5583         case LINE_STAT_UNSTAGED:
5584         {
5585                 const char *files_show_argv[] = {
5586                         "git", "diff-files", "--root", "--patch-with-stat",
5587                                 "-C", "-M", "--", oldpath, newpath, NULL
5588                 };
5590                 if (!prepare_update(stage, files_show_argv, opt_cdup, FORMAT_DASH))
5591                         return status_load_error(view, stage, newpath);
5592                 if (status)
5593                         info = "Unstaged changes to %s";
5594                 else
5595                         info = "Unstaged changes";
5596                 break;
5597         }
5598         case LINE_STAT_UNTRACKED:
5599                 if (!newpath) {
5600                         report("No file to show");
5601                         return REQ_NONE;
5602                 }
5604                 if (!suffixcmp(status->new.name, -1, "/")) {
5605                         report("Cannot display a directory");
5606                         return REQ_NONE;
5607                 }
5609                 if (!prepare_update_file(stage, newpath))
5610                         return status_load_error(view, stage, newpath);
5611                 info = "Untracked file %s";
5612                 break;
5614         case LINE_STAT_HEAD:
5615                 return REQ_NONE;
5617         default:
5618                 die("line type %d not handled in switch", line->type);
5619         }
5621         split = view_is_displayed(view) ? OPEN_SPLIT : 0;
5622         open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split);
5623         if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
5624                 if (status) {
5625                         stage_status = *status;
5626                 } else {
5627                         memset(&stage_status, 0, sizeof(stage_status));
5628                 }
5630                 stage_line_type = line->type;
5631                 stage_chunks = 0;
5632                 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
5633         }
5635         return REQ_NONE;
5638 static bool
5639 status_exists(struct status *status, enum line_type type)
5641         struct view *view = VIEW(REQ_VIEW_STATUS);
5642         unsigned long lineno;
5644         for (lineno = 0; lineno < view->lines; lineno++) {
5645                 struct line *line = &view->line[lineno];
5646                 struct status *pos = line->data;
5648                 if (line->type != type)
5649                         continue;
5650                 if (!pos && (!status || !status->status) && line[1].data) {
5651                         select_view_line(view, lineno);
5652                         return TRUE;
5653                 }
5654                 if (pos && !strcmp(status->new.name, pos->new.name)) {
5655                         select_view_line(view, lineno);
5656                         return TRUE;
5657                 }
5658         }
5660         return FALSE;
5664 static bool
5665 status_update_prepare(struct io *io, enum line_type type)
5667         const char *staged_argv[] = {
5668                 "git", "update-index", "-z", "--index-info", NULL
5669         };
5670         const char *others_argv[] = {
5671                 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
5672         };
5674         switch (type) {
5675         case LINE_STAT_STAGED:
5676                 return run_io(io, staged_argv, opt_cdup, IO_WR);
5678         case LINE_STAT_UNSTAGED:
5679                 return run_io(io, others_argv, opt_cdup, IO_WR);
5681         case LINE_STAT_UNTRACKED:
5682                 return run_io(io, others_argv, NULL, IO_WR);
5684         default:
5685                 die("line type %d not handled in switch", type);
5686                 return FALSE;
5687         }
5690 static bool
5691 status_update_write(struct io *io, struct status *status, enum line_type type)
5693         char buf[SIZEOF_STR];
5694         size_t bufsize = 0;
5696         switch (type) {
5697         case LINE_STAT_STAGED:
5698                 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
5699                                         status->old.mode,
5700                                         status->old.rev,
5701                                         status->old.name, 0))
5702                         return FALSE;
5703                 break;
5705         case LINE_STAT_UNSTAGED:
5706         case LINE_STAT_UNTRACKED:
5707                 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
5708                         return FALSE;
5709                 break;
5711         default:
5712                 die("line type %d not handled in switch", type);
5713         }
5715         return io_write(io, buf, bufsize);
5718 static bool
5719 status_update_file(struct status *status, enum line_type type)
5721         struct io io = {};
5722         bool result;
5724         if (!status_update_prepare(&io, type))
5725                 return FALSE;
5727         result = status_update_write(&io, status, type);
5728         return done_io(&io) && result;
5731 static bool
5732 status_update_files(struct view *view, struct line *line)
5734         char buf[sizeof(view->ref)];
5735         struct io io = {};
5736         bool result = TRUE;
5737         struct line *pos = view->line + view->lines;
5738         int files = 0;
5739         int file, done;
5740         int cursor_y = -1, cursor_x = -1;
5742         if (!status_update_prepare(&io, line->type))
5743                 return FALSE;
5745         for (pos = line; pos < view->line + view->lines && pos->data; pos++)
5746                 files++;
5748         string_copy(buf, view->ref);
5749         getsyx(cursor_y, cursor_x);
5750         for (file = 0, done = 5; result && file < files; line++, file++) {
5751                 int almost_done = file * 100 / files;
5753                 if (almost_done > done) {
5754                         done = almost_done;
5755                         string_format(view->ref, "updating file %u of %u (%d%% done)",
5756                                       file, files, done);
5757                         update_view_title(view);
5758                         setsyx(cursor_y, cursor_x);
5759                         doupdate();
5760                 }
5761                 result = status_update_write(&io, line->data, line->type);
5762         }
5763         string_copy(view->ref, buf);
5765         return done_io(&io) && result;
5768 static bool
5769 status_update(struct view *view)
5771         struct line *line = &view->line[view->lineno];
5773         assert(view->lines);
5775         if (!line->data) {
5776                 /* This should work even for the "On branch" line. */
5777                 if (line < view->line + view->lines && !line[1].data) {
5778                         report("Nothing to update");
5779                         return FALSE;
5780                 }
5782                 if (!status_update_files(view, line + 1)) {
5783                         report("Failed to update file status");
5784                         return FALSE;
5785                 }
5787         } else if (!status_update_file(line->data, line->type)) {
5788                 report("Failed to update file status");
5789                 return FALSE;
5790         }
5792         return TRUE;
5795 static bool
5796 status_revert(struct status *status, enum line_type type, bool has_none)
5798         if (!status || type != LINE_STAT_UNSTAGED) {
5799                 if (type == LINE_STAT_STAGED) {
5800                         report("Cannot revert changes to staged files");
5801                 } else if (type == LINE_STAT_UNTRACKED) {
5802                         report("Cannot revert changes to untracked files");
5803                 } else if (has_none) {
5804                         report("Nothing to revert");
5805                 } else {
5806                         report("Cannot revert changes to multiple files");
5807                 }
5808                 return FALSE;
5810         } else {
5811                 char mode[10] = "100644";
5812                 const char *reset_argv[] = {
5813                         "git", "update-index", "--cacheinfo", mode,
5814                                 status->old.rev, status->old.name, NULL
5815                 };
5816                 const char *checkout_argv[] = {
5817                         "git", "checkout", "--", status->old.name, NULL
5818                 };
5820                 if (!prompt_yesno("Are you sure you want to overwrite any changes?"))
5821                         return FALSE;
5822                 string_format(mode, "%o", status->old.mode);
5823                 return (status->status != 'U' || run_io_fg(reset_argv, opt_cdup)) &&
5824                         run_io_fg(checkout_argv, opt_cdup);
5825         }
5828 static enum request
5829 status_request(struct view *view, enum request request, struct line *line)
5831         struct status *status = line->data;
5833         switch (request) {
5834         case REQ_STATUS_UPDATE:
5835                 if (!status_update(view))
5836                         return REQ_NONE;
5837                 break;
5839         case REQ_STATUS_REVERT:
5840                 if (!status_revert(status, line->type, status_has_none(view, line)))
5841                         return REQ_NONE;
5842                 break;
5844         case REQ_STATUS_MERGE:
5845                 if (!status || status->status != 'U') {
5846                         report("Merging only possible for files with unmerged status ('U').");
5847                         return REQ_NONE;
5848                 }
5849                 open_mergetool(status->new.name);
5850                 break;
5852         case REQ_EDIT:
5853                 if (!status)
5854                         return request;
5855                 if (status->status == 'D') {
5856                         report("File has been deleted.");
5857                         return REQ_NONE;
5858                 }
5860                 open_editor(status->status != '?', status->new.name);
5861                 break;
5863         case REQ_VIEW_BLAME:
5864                 if (status) {
5865                         string_copy(opt_file, status->new.name);
5866                         opt_ref[0] = 0;
5867                 }
5868                 return request;
5870         case REQ_ENTER:
5871                 /* After returning the status view has been split to
5872                  * show the stage view. No further reloading is
5873                  * necessary. */
5874                 return status_enter(view, line);
5876         case REQ_REFRESH:
5877                 /* Simply reload the view. */
5878                 break;
5880         default:
5881                 return request;
5882         }
5884         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
5886         return REQ_NONE;
5889 static void
5890 status_select(struct view *view, struct line *line)
5892         struct status *status = line->data;
5893         char file[SIZEOF_STR] = "all files";
5894         const char *text;
5895         const char *key;
5897         if (status && !string_format(file, "'%s'", status->new.name))
5898                 return;
5900         if (!status && line[1].type == LINE_STAT_NONE)
5901                 line++;
5903         switch (line->type) {
5904         case LINE_STAT_STAGED:
5905                 text = "Press %s to unstage %s for commit";
5906                 break;
5908         case LINE_STAT_UNSTAGED:
5909                 text = "Press %s to stage %s for commit";
5910                 break;
5912         case LINE_STAT_UNTRACKED:
5913                 text = "Press %s to stage %s for addition";
5914                 break;
5916         case LINE_STAT_HEAD:
5917         case LINE_STAT_NONE:
5918                 text = "Nothing to update";
5919                 break;
5921         default:
5922                 die("line type %d not handled in switch", line->type);
5923         }
5925         if (status && status->status == 'U') {
5926                 text = "Press %s to resolve conflict in %s";
5927                 key = get_key(KEYMAP_STATUS, REQ_STATUS_MERGE);
5929         } else {
5930                 key = get_key(KEYMAP_STATUS, REQ_STATUS_UPDATE);
5931         }
5933         string_format(view->ref, text, key, file);
5936 static bool
5937 status_grep(struct view *view, struct line *line)
5939         struct status *status = line->data;
5941         if (status) {
5942                 const char buf[2] = { status->status, 0 };
5943                 const char *text[] = { status->new.name, buf, NULL };
5945                 return grep_text(view, text);
5946         }
5948         return FALSE;
5951 static struct view_ops status_ops = {
5952         "file",
5953         NULL,
5954         status_open,
5955         NULL,
5956         status_draw,
5957         status_request,
5958         status_grep,
5959         status_select,
5960 };
5963 static bool
5964 stage_diff_write(struct io *io, struct line *line, struct line *end)
5966         while (line < end) {
5967                 if (!io_write(io, line->data, strlen(line->data)) ||
5968                     !io_write(io, "\n", 1))
5969                         return FALSE;
5970                 line++;
5971                 if (line->type == LINE_DIFF_CHUNK ||
5972                     line->type == LINE_DIFF_HEADER)
5973                         break;
5974         }
5976         return TRUE;
5979 static struct line *
5980 stage_diff_find(struct view *view, struct line *line, enum line_type type)
5982         for (; view->line < line; line--)
5983                 if (line->type == type)
5984                         return line;
5986         return NULL;
5989 static bool
5990 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
5992         const char *apply_argv[SIZEOF_ARG] = {
5993                 "git", "apply", "--whitespace=nowarn", NULL
5994         };
5995         struct line *diff_hdr;
5996         struct io io = {};
5997         int argc = 3;
5999         diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
6000         if (!diff_hdr)
6001                 return FALSE;
6003         if (!revert)
6004                 apply_argv[argc++] = "--cached";
6005         if (revert || stage_line_type == LINE_STAT_STAGED)
6006                 apply_argv[argc++] = "-R";
6007         apply_argv[argc++] = "-";
6008         apply_argv[argc++] = NULL;
6009         if (!run_io(&io, apply_argv, opt_cdup, IO_WR))
6010                 return FALSE;
6012         if (!stage_diff_write(&io, diff_hdr, chunk) ||
6013             !stage_diff_write(&io, chunk, view->line + view->lines))
6014                 chunk = NULL;
6016         done_io(&io);
6017         run_io_bg(update_index_argv);
6019         return chunk ? TRUE : FALSE;
6022 static bool
6023 stage_update(struct view *view, struct line *line)
6025         struct line *chunk = NULL;
6027         if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
6028                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6030         if (chunk) {
6031                 if (!stage_apply_chunk(view, chunk, FALSE)) {
6032                         report("Failed to apply chunk");
6033                         return FALSE;
6034                 }
6036         } else if (!stage_status.status) {
6037                 view = VIEW(REQ_VIEW_STATUS);
6039                 for (line = view->line; line < view->line + view->lines; line++)
6040                         if (line->type == stage_line_type)
6041                                 break;
6043                 if (!status_update_files(view, line + 1)) {
6044                         report("Failed to update files");
6045                         return FALSE;
6046                 }
6048         } else if (!status_update_file(&stage_status, stage_line_type)) {
6049                 report("Failed to update file");
6050                 return FALSE;
6051         }
6053         return TRUE;
6056 static bool
6057 stage_revert(struct view *view, struct line *line)
6059         struct line *chunk = NULL;
6061         if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
6062                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6064         if (chunk) {
6065                 if (!prompt_yesno("Are you sure you want to revert changes?"))
6066                         return FALSE;
6068                 if (!stage_apply_chunk(view, chunk, TRUE)) {
6069                         report("Failed to revert chunk");
6070                         return FALSE;
6071                 }
6072                 return TRUE;
6074         } else {
6075                 return status_revert(stage_status.status ? &stage_status : NULL,
6076                                      stage_line_type, FALSE);
6077         }
6081 static void
6082 stage_next(struct view *view, struct line *line)
6084         int i;
6086         if (!stage_chunks) {
6087                 for (line = view->line; line < view->line + view->lines; line++) {
6088                         if (line->type != LINE_DIFF_CHUNK)
6089                                 continue;
6091                         if (!realloc_ints(&stage_chunk, stage_chunks, 1)) {
6092                                 report("Allocation failure");
6093                                 return;
6094                         }
6096                         stage_chunk[stage_chunks++] = line - view->line;
6097                 }
6098         }
6100         for (i = 0; i < stage_chunks; i++) {
6101                 if (stage_chunk[i] > view->lineno) {
6102                         do_scroll_view(view, stage_chunk[i] - view->lineno);
6103                         report("Chunk %d of %d", i + 1, stage_chunks);
6104                         return;
6105                 }
6106         }
6108         report("No next chunk found");
6111 static enum request
6112 stage_request(struct view *view, enum request request, struct line *line)
6114         switch (request) {
6115         case REQ_STATUS_UPDATE:
6116                 if (!stage_update(view, line))
6117                         return REQ_NONE;
6118                 break;
6120         case REQ_STATUS_REVERT:
6121                 if (!stage_revert(view, line))
6122                         return REQ_NONE;
6123                 break;
6125         case REQ_STAGE_NEXT:
6126                 if (stage_line_type == LINE_STAT_UNTRACKED) {
6127                         report("File is untracked; press %s to add",
6128                                get_key(KEYMAP_STAGE, REQ_STATUS_UPDATE));
6129                         return REQ_NONE;
6130                 }
6131                 stage_next(view, line);
6132                 return REQ_NONE;
6134         case REQ_EDIT:
6135                 if (!stage_status.new.name[0])
6136                         return request;
6137                 if (stage_status.status == 'D') {
6138                         report("File has been deleted.");
6139                         return REQ_NONE;
6140                 }
6142                 open_editor(stage_status.status != '?', stage_status.new.name);
6143                 break;
6145         case REQ_REFRESH:
6146                 /* Reload everything ... */
6147                 break;
6149         case REQ_VIEW_BLAME:
6150                 if (stage_status.new.name[0]) {
6151                         string_copy(opt_file, stage_status.new.name);
6152                         opt_ref[0] = 0;
6153                 }
6154                 return request;
6156         case REQ_ENTER:
6157                 return pager_request(view, request, line);
6159         default:
6160                 return request;
6161         }
6163         VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
6164         open_view(view, REQ_VIEW_STATUS, OPEN_REFRESH);
6166         /* Check whether the staged entry still exists, and close the
6167          * stage view if it doesn't. */
6168         if (!status_exists(&stage_status, stage_line_type)) {
6169                 status_restore(VIEW(REQ_VIEW_STATUS));
6170                 return REQ_VIEW_CLOSE;
6171         }
6173         if (stage_line_type == LINE_STAT_UNTRACKED) {
6174                 if (!suffixcmp(stage_status.new.name, -1, "/")) {
6175                         report("Cannot display a directory");
6176                         return REQ_NONE;
6177                 }
6179                 if (!prepare_update_file(view, stage_status.new.name)) {
6180                         report("Failed to open file: %s", strerror(errno));
6181                         return REQ_NONE;
6182                 }
6183         }
6184         open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
6186         return REQ_NONE;
6189 static struct view_ops stage_ops = {
6190         "line",
6191         NULL,
6192         NULL,
6193         pager_read,
6194         pager_draw,
6195         stage_request,
6196         pager_grep,
6197         pager_select,
6198 };
6201 /*
6202  * Revision graph
6203  */
6205 struct commit {
6206         char id[SIZEOF_REV];            /* SHA1 ID. */
6207         char title[128];                /* First line of the commit message. */
6208         const char *author;             /* Author of the commit. */
6209         time_t time;                    /* Date from the author ident. */
6210         struct ref_list *refs;          /* Repository references. */
6211         chtype graph[SIZEOF_REVGRAPH];  /* Ancestry chain graphics. */
6212         size_t graph_size;              /* The width of the graph array. */
6213         bool has_parents;               /* Rewritten --parents seen. */
6214 };
6216 /* Size of rev graph with no  "padding" columns */
6217 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
6219 struct rev_graph {
6220         struct rev_graph *prev, *next, *parents;
6221         char rev[SIZEOF_REVITEMS][SIZEOF_REV];
6222         size_t size;
6223         struct commit *commit;
6224         size_t pos;
6225         unsigned int boundary:1;
6226 };
6228 /* Parents of the commit being visualized. */
6229 static struct rev_graph graph_parents[4];
6231 /* The current stack of revisions on the graph. */
6232 static struct rev_graph graph_stacks[4] = {
6233         { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
6234         { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
6235         { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
6236         { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
6237 };
6239 static inline bool
6240 graph_parent_is_merge(struct rev_graph *graph)
6242         return graph->parents->size > 1;
6245 static inline void
6246 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
6248         struct commit *commit = graph->commit;
6250         if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
6251                 commit->graph[commit->graph_size++] = symbol;
6254 static void
6255 clear_rev_graph(struct rev_graph *graph)
6257         graph->boundary = 0;
6258         graph->size = graph->pos = 0;
6259         graph->commit = NULL;
6260         memset(graph->parents, 0, sizeof(*graph->parents));
6263 static void
6264 done_rev_graph(struct rev_graph *graph)
6266         if (graph_parent_is_merge(graph) &&
6267             graph->pos < graph->size - 1 &&
6268             graph->next->size == graph->size + graph->parents->size - 1) {
6269                 size_t i = graph->pos + graph->parents->size - 1;
6271                 graph->commit->graph_size = i * 2;
6272                 while (i < graph->next->size - 1) {
6273                         append_to_rev_graph(graph, ' ');
6274                         append_to_rev_graph(graph, '\\');
6275                         i++;
6276                 }
6277         }
6279         clear_rev_graph(graph);
6282 static void
6283 push_rev_graph(struct rev_graph *graph, const char *parent)
6285         int i;
6287         /* "Collapse" duplicate parents lines.
6288          *
6289          * FIXME: This needs to also update update the drawn graph but
6290          * for now it just serves as a method for pruning graph lines. */
6291         for (i = 0; i < graph->size; i++)
6292                 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
6293                         return;
6295         if (graph->size < SIZEOF_REVITEMS) {
6296                 string_copy_rev(graph->rev[graph->size++], parent);
6297         }
6300 static chtype
6301 get_rev_graph_symbol(struct rev_graph *graph)
6303         chtype symbol;
6305         if (graph->boundary)
6306                 symbol = REVGRAPH_BOUND;
6307         else if (graph->parents->size == 0)
6308                 symbol = REVGRAPH_INIT;
6309         else if (graph_parent_is_merge(graph))
6310                 symbol = REVGRAPH_MERGE;
6311         else if (graph->pos >= graph->size)
6312                 symbol = REVGRAPH_BRANCH;
6313         else
6314                 symbol = REVGRAPH_COMMIT;
6316         return symbol;
6319 static void
6320 draw_rev_graph(struct rev_graph *graph)
6322         struct rev_filler {
6323                 chtype separator, line;
6324         };
6325         enum { DEFAULT, RSHARP, RDIAG, LDIAG };
6326         static struct rev_filler fillers[] = {
6327                 { ' ',  '|' },
6328                 { '`',  '.' },
6329                 { '\'', ' ' },
6330                 { '/',  ' ' },
6331         };
6332         chtype symbol = get_rev_graph_symbol(graph);
6333         struct rev_filler *filler;
6334         size_t i;
6336         if (opt_line_graphics)
6337                 fillers[DEFAULT].line = line_graphics[LINE_GRAPHIC_VLINE];
6339         filler = &fillers[DEFAULT];
6341         for (i = 0; i < graph->pos; i++) {
6342                 append_to_rev_graph(graph, filler->line);
6343                 if (graph_parent_is_merge(graph->prev) &&
6344                     graph->prev->pos == i)
6345                         filler = &fillers[RSHARP];
6347                 append_to_rev_graph(graph, filler->separator);
6348         }
6350         /* Place the symbol for this revision. */
6351         append_to_rev_graph(graph, symbol);
6353         if (graph->prev->size > graph->size)
6354                 filler = &fillers[RDIAG];
6355         else
6356                 filler = &fillers[DEFAULT];
6358         i++;
6360         for (; i < graph->size; i++) {
6361                 append_to_rev_graph(graph, filler->separator);
6362                 append_to_rev_graph(graph, filler->line);
6363                 if (graph_parent_is_merge(graph->prev) &&
6364                     i < graph->prev->pos + graph->parents->size)
6365                         filler = &fillers[RSHARP];
6366                 if (graph->prev->size > graph->size)
6367                         filler = &fillers[LDIAG];
6368         }
6370         if (graph->prev->size > graph->size) {
6371                 append_to_rev_graph(graph, filler->separator);
6372                 if (filler->line != ' ')
6373                         append_to_rev_graph(graph, filler->line);
6374         }
6377 /* Prepare the next rev graph */
6378 static void
6379 prepare_rev_graph(struct rev_graph *graph)
6381         size_t i;
6383         /* First, traverse all lines of revisions up to the active one. */
6384         for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
6385                 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
6386                         break;
6388                 push_rev_graph(graph->next, graph->rev[graph->pos]);
6389         }
6391         /* Interleave the new revision parent(s). */
6392         for (i = 0; !graph->boundary && i < graph->parents->size; i++)
6393                 push_rev_graph(graph->next, graph->parents->rev[i]);
6395         /* Lastly, put any remaining revisions. */
6396         for (i = graph->pos + 1; i < graph->size; i++)
6397                 push_rev_graph(graph->next, graph->rev[i]);
6400 static void
6401 update_rev_graph(struct view *view, struct rev_graph *graph)
6403         /* If this is the finalizing update ... */
6404         if (graph->commit)
6405                 prepare_rev_graph(graph);
6407         /* Graph visualization needs a one rev look-ahead,
6408          * so the first update doesn't visualize anything. */
6409         if (!graph->prev->commit)
6410                 return;
6412         if (view->lines > 2)
6413                 view->line[view->lines - 3].dirty = 1;
6414         if (view->lines > 1)
6415                 view->line[view->lines - 2].dirty = 1;
6416         draw_rev_graph(graph->prev);
6417         done_rev_graph(graph->prev->prev);
6421 /*
6422  * Main view backend
6423  */
6425 static const char *main_argv[SIZEOF_ARG] = {
6426         "git", "log", "--no-color", "--pretty=raw", "--parents",
6427                       "--topo-order", "%(head)", NULL
6428 };
6430 static bool
6431 main_draw(struct view *view, struct line *line, unsigned int lineno)
6433         struct commit *commit = line->data;
6435         if (!commit->author)
6436                 return FALSE;
6438         if (opt_date && draw_date(view, &commit->time))
6439                 return TRUE;
6441         if (opt_author && draw_author(view, commit->author))
6442                 return TRUE;
6444         if (opt_rev_graph && commit->graph_size &&
6445             draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
6446                 return TRUE;
6448         if (opt_show_refs && commit->refs) {
6449                 size_t i;
6451                 for (i = 0; i < commit->refs->size; i++) {
6452                         struct ref *ref = commit->refs->refs[i];
6453                         enum line_type type;
6455                         if (ref->head)
6456                                 type = LINE_MAIN_HEAD;
6457                         else if (ref->ltag)
6458                                 type = LINE_MAIN_LOCAL_TAG;
6459                         else if (ref->tag)
6460                                 type = LINE_MAIN_TAG;
6461                         else if (ref->tracked)
6462                                 type = LINE_MAIN_TRACKED;
6463                         else if (ref->remote)
6464                                 type = LINE_MAIN_REMOTE;
6465                         else
6466                                 type = LINE_MAIN_REF;
6468                         if (draw_text(view, type, "[", TRUE) ||
6469                             draw_text(view, type, ref->name, TRUE) ||
6470                             draw_text(view, type, "]", TRUE))
6471                                 return TRUE;
6473                         if (draw_text(view, LINE_DEFAULT, " ", TRUE))
6474                                 return TRUE;
6475                 }
6476         }
6478         draw_text(view, LINE_DEFAULT, commit->title, TRUE);
6479         return TRUE;
6482 /* Reads git log --pretty=raw output and parses it into the commit struct. */
6483 static bool
6484 main_read(struct view *view, char *line)
6486         static struct rev_graph *graph = graph_stacks;
6487         enum line_type type;
6488         struct commit *commit;
6490         if (!line) {
6491                 int i;
6493                 if (!view->lines && !view->parent)
6494                         die("No revisions match the given arguments.");
6495                 if (view->lines > 0) {
6496                         commit = view->line[view->lines - 1].data;
6497                         view->line[view->lines - 1].dirty = 1;
6498                         if (!commit->author) {
6499                                 view->lines--;
6500                                 free(commit);
6501                                 graph->commit = NULL;
6502                         }
6503                 }
6504                 update_rev_graph(view, graph);
6506                 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
6507                         clear_rev_graph(&graph_stacks[i]);
6508                 return TRUE;
6509         }
6511         type = get_line_type(line);
6512         if (type == LINE_COMMIT) {
6513                 commit = calloc(1, sizeof(struct commit));
6514                 if (!commit)
6515                         return FALSE;
6517                 line += STRING_SIZE("commit ");
6518                 if (*line == '-') {
6519                         graph->boundary = 1;
6520                         line++;
6521                 }
6523                 string_copy_rev(commit->id, line);
6524                 commit->refs = get_ref_list(commit->id);
6525                 graph->commit = commit;
6526                 add_line_data(view, commit, LINE_MAIN_COMMIT);
6528                 while ((line = strchr(line, ' '))) {
6529                         line++;
6530                         push_rev_graph(graph->parents, line);
6531                         commit->has_parents = TRUE;
6532                 }
6533                 return TRUE;
6534         }
6536         if (!view->lines)
6537                 return TRUE;
6538         commit = view->line[view->lines - 1].data;
6540         switch (type) {
6541         case LINE_PARENT:
6542                 if (commit->has_parents)
6543                         break;
6544                 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
6545                 break;
6547         case LINE_AUTHOR:
6548                 parse_author_line(line + STRING_SIZE("author "),
6549                                   &commit->author, &commit->time);
6550                 update_rev_graph(view, graph);
6551                 graph = graph->next;
6552                 break;
6554         default:
6555                 /* Fill in the commit title if it has not already been set. */
6556                 if (commit->title[0])
6557                         break;
6559                 /* Require titles to start with a non-space character at the
6560                  * offset used by git log. */
6561                 if (strncmp(line, "    ", 4))
6562                         break;
6563                 line += 4;
6564                 /* Well, if the title starts with a whitespace character,
6565                  * try to be forgiving.  Otherwise we end up with no title. */
6566                 while (isspace(*line))
6567                         line++;
6568                 if (*line == '\0')
6569                         break;
6570                 /* FIXME: More graceful handling of titles; append "..." to
6571                  * shortened titles, etc. */
6573                 string_expand(commit->title, sizeof(commit->title), line, 1);
6574                 view->line[view->lines - 1].dirty = 1;
6575         }
6577         return TRUE;
6580 static enum request
6581 main_request(struct view *view, enum request request, struct line *line)
6583         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
6585         switch (request) {
6586         case REQ_ENTER:
6587                 open_view(view, REQ_VIEW_DIFF, flags);
6588                 break;
6589         case REQ_REFRESH:
6590                 load_refs();
6591                 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
6592                 break;
6593         default:
6594                 return request;
6595         }
6597         return REQ_NONE;
6600 static bool
6601 grep_refs(struct ref_list *list, regex_t *regex)
6603         regmatch_t pmatch;
6604         size_t i;
6606         if (!opt_show_refs || !list)
6607                 return FALSE;
6609         for (i = 0; i < list->size; i++) {
6610                 if (regexec(regex, list->refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
6611                         return TRUE;
6612         }
6614         return FALSE;
6617 static bool
6618 main_grep(struct view *view, struct line *line)
6620         struct commit *commit = line->data;
6621         const char *text[] = {
6622                 commit->title,
6623                 opt_author ? commit->author : "",
6624                 opt_date ? mkdate(&commit->time) : "",
6625                 NULL
6626         };
6628         return grep_text(view, text) || grep_refs(commit->refs, view->regex);
6631 static void
6632 main_select(struct view *view, struct line *line)
6634         struct commit *commit = line->data;
6636         string_copy_rev(view->ref, commit->id);
6637         string_copy_rev(ref_commit, view->ref);
6640 static struct view_ops main_ops = {
6641         "commit",
6642         main_argv,
6643         NULL,
6644         main_read,
6645         main_draw,
6646         main_request,
6647         main_grep,
6648         main_select,
6649 };
6652 /*
6653  * Unicode / UTF-8 handling
6654  *
6655  * NOTE: Much of the following code for dealing with Unicode is derived from
6656  * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
6657  * src/intl/charset.c from the UTF-8 branch commit elinks-0.11.0-g31f2c28.
6658  */
6660 static inline int
6661 unicode_width(unsigned long c)
6663         if (c >= 0x1100 &&
6664            (c <= 0x115f                         /* Hangul Jamo */
6665             || c == 0x2329
6666             || c == 0x232a
6667             || (c >= 0x2e80  && c <= 0xa4cf && c != 0x303f)
6668                                                 /* CJK ... Yi */
6669             || (c >= 0xac00  && c <= 0xd7a3)    /* Hangul Syllables */
6670             || (c >= 0xf900  && c <= 0xfaff)    /* CJK Compatibility Ideographs */
6671             || (c >= 0xfe30  && c <= 0xfe6f)    /* CJK Compatibility Forms */
6672             || (c >= 0xff00  && c <= 0xff60)    /* Fullwidth Forms */
6673             || (c >= 0xffe0  && c <= 0xffe6)
6674             || (c >= 0x20000 && c <= 0x2fffd)
6675             || (c >= 0x30000 && c <= 0x3fffd)))
6676                 return 2;
6678         if (c == '\t')
6679                 return opt_tab_size;
6681         return 1;
6684 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
6685  * Illegal bytes are set one. */
6686 static const unsigned char utf8_bytes[256] = {
6687         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,
6688         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,
6689         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,
6690         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,
6691         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,
6692         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,
6693         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,
6694         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,
6695 };
6697 /* Decode UTF-8 multi-byte representation into a Unicode character. */
6698 static inline unsigned long
6699 utf8_to_unicode(const char *string, size_t length)
6701         unsigned long unicode;
6703         switch (length) {
6704         case 1:
6705                 unicode  =   string[0];
6706                 break;
6707         case 2:
6708                 unicode  =  (string[0] & 0x1f) << 6;
6709                 unicode +=  (string[1] & 0x3f);
6710                 break;
6711         case 3:
6712                 unicode  =  (string[0] & 0x0f) << 12;
6713                 unicode += ((string[1] & 0x3f) << 6);
6714                 unicode +=  (string[2] & 0x3f);
6715                 break;
6716         case 4:
6717                 unicode  =  (string[0] & 0x0f) << 18;
6718                 unicode += ((string[1] & 0x3f) << 12);
6719                 unicode += ((string[2] & 0x3f) << 6);
6720                 unicode +=  (string[3] & 0x3f);
6721                 break;
6722         case 5:
6723                 unicode  =  (string[0] & 0x0f) << 24;
6724                 unicode += ((string[1] & 0x3f) << 18);
6725                 unicode += ((string[2] & 0x3f) << 12);
6726                 unicode += ((string[3] & 0x3f) << 6);
6727                 unicode +=  (string[4] & 0x3f);
6728                 break;
6729         case 6:
6730                 unicode  =  (string[0] & 0x01) << 30;
6731                 unicode += ((string[1] & 0x3f) << 24);
6732                 unicode += ((string[2] & 0x3f) << 18);
6733                 unicode += ((string[3] & 0x3f) << 12);
6734                 unicode += ((string[4] & 0x3f) << 6);
6735                 unicode +=  (string[5] & 0x3f);
6736                 break;
6737         default:
6738                 die("Invalid Unicode length");
6739         }
6741         /* Invalid characters could return the special 0xfffd value but NUL
6742          * should be just as good. */
6743         return unicode > 0xffff ? 0 : unicode;
6746 /* Calculates how much of string can be shown within the given maximum width
6747  * and sets trimmed parameter to non-zero value if all of string could not be
6748  * shown. If the reserve flag is TRUE, it will reserve at least one
6749  * trailing character, which can be useful when drawing a delimiter.
6750  *
6751  * Returns the number of bytes to output from string to satisfy max_width. */
6752 static size_t
6753 utf8_length(const char **start, size_t skip, int *width, size_t max_width, int *trimmed, bool reserve)
6755         const char *string = *start;
6756         const char *end = strchr(string, '\0');
6757         unsigned char last_bytes = 0;
6758         size_t last_ucwidth = 0;
6760         *width = 0;
6761         *trimmed = 0;
6763         while (string < end) {
6764                 int c = *(unsigned char *) string;
6765                 unsigned char bytes = utf8_bytes[c];
6766                 size_t ucwidth;
6767                 unsigned long unicode;
6769                 if (string + bytes > end)
6770                         break;
6772                 /* Change representation to figure out whether
6773                  * it is a single- or double-width character. */
6775                 unicode = utf8_to_unicode(string, bytes);
6776                 /* FIXME: Graceful handling of invalid Unicode character. */
6777                 if (!unicode)
6778                         break;
6780                 ucwidth = unicode_width(unicode);
6781                 if (skip > 0) {
6782                         skip -= ucwidth <= skip ? ucwidth : skip;
6783                         *start += bytes;
6784                 }
6785                 *width  += ucwidth;
6786                 if (*width > max_width) {
6787                         *trimmed = 1;
6788                         *width -= ucwidth;
6789                         if (reserve && *width == max_width) {
6790                                 string -= last_bytes;
6791                                 *width -= last_ucwidth;
6792                         }
6793                         break;
6794                 }
6796                 string  += bytes;
6797                 last_bytes = ucwidth ? bytes : 0;
6798                 last_ucwidth = ucwidth;
6799         }
6801         return string - *start;
6805 /*
6806  * Status management
6807  */
6809 /* Whether or not the curses interface has been initialized. */
6810 static bool cursed = FALSE;
6812 /* Terminal hacks and workarounds. */
6813 static bool use_scroll_redrawwin;
6814 static bool use_scroll_status_wclear;
6816 /* The status window is used for polling keystrokes. */
6817 static WINDOW *status_win;
6819 /* Reading from the prompt? */
6820 static bool input_mode = FALSE;
6822 static bool status_empty = FALSE;
6824 /* Update status and title window. */
6825 static void
6826 report(const char *msg, ...)
6828         struct view *view = display[current_view];
6830         if (input_mode)
6831                 return;
6833         if (!view) {
6834                 char buf[SIZEOF_STR];
6835                 va_list args;
6837                 va_start(args, msg);
6838                 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
6839                         buf[sizeof(buf) - 1] = 0;
6840                         buf[sizeof(buf) - 2] = '.';
6841                         buf[sizeof(buf) - 3] = '.';
6842                         buf[sizeof(buf) - 4] = '.';
6843                 }
6844                 va_end(args);
6845                 die("%s", buf);
6846         }
6848         if (!status_empty || *msg) {
6849                 va_list args;
6851                 va_start(args, msg);
6853                 wmove(status_win, 0, 0);
6854                 if (view->has_scrolled && use_scroll_status_wclear)
6855                         wclear(status_win);
6856                 if (*msg) {
6857                         vwprintw(status_win, msg, args);
6858                         status_empty = FALSE;
6859                 } else {
6860                         status_empty = TRUE;
6861                 }
6862                 wclrtoeol(status_win);
6863                 wnoutrefresh(status_win);
6865                 va_end(args);
6866         }
6868         update_view_title(view);
6871 /* Controls when nodelay should be in effect when polling user input. */
6872 static void
6873 set_nonblocking_input(bool loading)
6875         static unsigned int loading_views;
6877         if ((loading == FALSE && loading_views-- == 1) ||
6878             (loading == TRUE  && loading_views++ == 0))
6879                 nodelay(status_win, loading);
6882 static void
6883 init_display(void)
6885         const char *term;
6886         int x, y;
6888         /* Initialize the curses library */
6889         if (isatty(STDIN_FILENO)) {
6890                 cursed = !!initscr();
6891                 opt_tty = stdin;
6892         } else {
6893                 /* Leave stdin and stdout alone when acting as a pager. */
6894                 opt_tty = fopen("/dev/tty", "r+");
6895                 if (!opt_tty)
6896                         die("Failed to open /dev/tty");
6897                 cursed = !!newterm(NULL, opt_tty, opt_tty);
6898         }
6900         if (!cursed)
6901                 die("Failed to initialize curses");
6903         nonl();         /* Disable conversion and detect newlines from input. */
6904         cbreak();       /* Take input chars one at a time, no wait for \n */
6905         noecho();       /* Don't echo input */
6906         leaveok(stdscr, FALSE);
6908         if (has_colors())
6909                 init_colors();
6911         getmaxyx(stdscr, y, x);
6912         status_win = newwin(1, 0, y - 1, 0);
6913         if (!status_win)
6914                 die("Failed to create status window");
6916         /* Enable keyboard mapping */
6917         keypad(status_win, TRUE);
6918         wbkgdset(status_win, get_line_attr(LINE_STATUS));
6920         TABSIZE = opt_tab_size;
6921         if (opt_line_graphics) {
6922                 line_graphics[LINE_GRAPHIC_VLINE] = ACS_VLINE;
6923         }
6925         term = getenv("XTERM_VERSION") ? NULL : getenv("COLORTERM");
6926         if (term && !strcmp(term, "gnome-terminal")) {
6927                 /* In the gnome-terminal-emulator, the message from
6928                  * scrolling up one line when impossible followed by
6929                  * scrolling down one line causes corruption of the
6930                  * status line. This is fixed by calling wclear. */
6931                 use_scroll_status_wclear = TRUE;
6932                 use_scroll_redrawwin = FALSE;
6934         } else if (term && !strcmp(term, "xrvt-xpm")) {
6935                 /* No problems with full optimizations in xrvt-(unicode)
6936                  * and aterm. */
6937                 use_scroll_status_wclear = use_scroll_redrawwin = FALSE;
6939         } else {
6940                 /* When scrolling in (u)xterm the last line in the
6941                  * scrolling direction will update slowly. */
6942                 use_scroll_redrawwin = TRUE;
6943                 use_scroll_status_wclear = FALSE;
6944         }
6947 static int
6948 get_input(int prompt_position)
6950         struct view *view;
6951         int i, key, cursor_y, cursor_x;
6953         if (prompt_position)
6954                 input_mode = TRUE;
6956         while (TRUE) {
6957                 foreach_view (view, i) {
6958                         update_view(view);
6959                         if (view_is_displayed(view) && view->has_scrolled &&
6960                             use_scroll_redrawwin)
6961                                 redrawwin(view->win);
6962                         view->has_scrolled = FALSE;
6963                 }
6965                 /* Update the cursor position. */
6966                 if (prompt_position) {
6967                         getbegyx(status_win, cursor_y, cursor_x);
6968                         cursor_x = prompt_position;
6969                 } else {
6970                         view = display[current_view];
6971                         getbegyx(view->win, cursor_y, cursor_x);
6972                         cursor_x = view->width - 1;
6973                         cursor_y += view->lineno - view->offset;
6974                 }
6975                 setsyx(cursor_y, cursor_x);
6977                 /* Refresh, accept single keystroke of input */
6978                 doupdate();
6979                 key = wgetch(status_win);
6981                 /* wgetch() with nodelay() enabled returns ERR when
6982                  * there's no input. */
6983                 if (key == ERR) {
6985                 } else if (key == KEY_RESIZE) {
6986                         int height, width;
6988                         getmaxyx(stdscr, height, width);
6990                         wresize(status_win, 1, width);
6991                         mvwin(status_win, height - 1, 0);
6992                         wnoutrefresh(status_win);
6993                         resize_display();
6994                         redraw_display(TRUE);
6996                 } else {
6997                         input_mode = FALSE;
6998                         return key;
6999                 }
7000         }
7003 static char *
7004 prompt_input(const char *prompt, input_handler handler, void *data)
7006         enum input_status status = INPUT_OK;
7007         static char buf[SIZEOF_STR];
7008         size_t pos = 0;
7010         buf[pos] = 0;
7012         while (status == INPUT_OK || status == INPUT_SKIP) {
7013                 int key;
7015                 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
7016                 wclrtoeol(status_win);
7018                 key = get_input(pos + 1);
7019                 switch (key) {
7020                 case KEY_RETURN:
7021                 case KEY_ENTER:
7022                 case '\n':
7023                         status = pos ? INPUT_STOP : INPUT_CANCEL;
7024                         break;
7026                 case KEY_BACKSPACE:
7027                         if (pos > 0)
7028                                 buf[--pos] = 0;
7029                         else
7030                                 status = INPUT_CANCEL;
7031                         break;
7033                 case KEY_ESC:
7034                         status = INPUT_CANCEL;
7035                         break;
7037                 default:
7038                         if (pos >= sizeof(buf)) {
7039                                 report("Input string too long");
7040                                 return NULL;
7041                         }
7043                         status = handler(data, buf, key);
7044                         if (status == INPUT_OK)
7045                                 buf[pos++] = (char) key;
7046                 }
7047         }
7049         /* Clear the status window */
7050         status_empty = FALSE;
7051         report("");
7053         if (status == INPUT_CANCEL)
7054                 return NULL;
7056         buf[pos++] = 0;
7058         return buf;
7061 static enum input_status
7062 prompt_yesno_handler(void *data, char *buf, int c)
7064         if (c == 'y' || c == 'Y')
7065                 return INPUT_STOP;
7066         if (c == 'n' || c == 'N')
7067                 return INPUT_CANCEL;
7068         return INPUT_SKIP;
7071 static bool
7072 prompt_yesno(const char *prompt)
7074         char prompt2[SIZEOF_STR];
7076         if (!string_format(prompt2, "%s [Yy/Nn]", prompt))
7077                 return FALSE;
7079         return !!prompt_input(prompt2, prompt_yesno_handler, NULL);
7082 static enum input_status
7083 read_prompt_handler(void *data, char *buf, int c)
7085         return isprint(c) ? INPUT_OK : INPUT_SKIP;
7088 static char *
7089 read_prompt(const char *prompt)
7091         return prompt_input(prompt, read_prompt_handler, NULL);
7094 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected)
7096         enum input_status status = INPUT_OK;
7097         int size = 0;
7099         while (items[size].text)
7100                 size++;
7102         while (status == INPUT_OK) {
7103                 const struct menu_item *item = &items[*selected];
7104                 int key;
7105                 int i;
7107                 mvwprintw(status_win, 0, 0, "%s (%d of %d) ",
7108                           prompt, *selected + 1, size);
7109                 if (item->hotkey)
7110                         wprintw(status_win, "[%c] ", (char) item->hotkey);
7111                 wprintw(status_win, "%s", item->text);
7112                 wclrtoeol(status_win);
7114                 key = get_input(COLS - 1);
7115                 switch (key) {
7116                 case KEY_RETURN:
7117                 case KEY_ENTER:
7118                 case '\n':
7119                         status = INPUT_STOP;
7120                         break;
7122                 case KEY_LEFT:
7123                 case KEY_UP:
7124                         *selected = *selected - 1;
7125                         if (*selected < 0)
7126                                 *selected = size - 1;
7127                         break;
7129                 case KEY_RIGHT:
7130                 case KEY_DOWN:
7131                         *selected = (*selected + 1) % size;
7132                         break;
7134                 case KEY_ESC:
7135                         status = INPUT_CANCEL;
7136                         break;
7138                 default:
7139                         for (i = 0; items[i].text; i++)
7140                                 if (items[i].hotkey == key) {
7141                                         *selected = i;
7142                                         status = INPUT_STOP;
7143                                         break;
7144                                 }
7145                 }
7146         }
7148         /* Clear the status window */
7149         status_empty = FALSE;
7150         report("");
7152         return status != INPUT_CANCEL;
7155 /*
7156  * Repository properties
7157  */
7159 static struct ref **refs = NULL;
7160 static size_t refs_size = 0;
7162 static struct ref_list **ref_lists = NULL;
7163 static size_t ref_lists_size = 0;
7165 DEFINE_ALLOCATOR(realloc_refs, struct ref *, 256)
7166 DEFINE_ALLOCATOR(realloc_refs_list, struct ref *, 8)
7167 DEFINE_ALLOCATOR(realloc_ref_lists, struct ref_list *, 8)
7169 static int
7170 compare_refs(const void *ref1_, const void *ref2_)
7172         const struct ref *ref1 = *(const struct ref **)ref1_;
7173         const struct ref *ref2 = *(const struct ref **)ref2_;
7175         if (ref1->tag != ref2->tag)
7176                 return ref2->tag - ref1->tag;
7177         if (ref1->ltag != ref2->ltag)
7178                 return ref2->ltag - ref2->ltag;
7179         if (ref1->head != ref2->head)
7180                 return ref2->head - ref1->head;
7181         if (ref1->tracked != ref2->tracked)
7182                 return ref2->tracked - ref1->tracked;
7183         if (ref1->remote != ref2->remote)
7184                 return ref2->remote - ref1->remote;
7185         return strcmp(ref1->name, ref2->name);
7188 static void
7189 foreach_ref(bool (*visitor)(void *data, struct ref *ref), void *data)
7191         size_t i;
7193         for (i = 0; i < refs_size; i++)
7194                 if (!visitor(data, refs[i]))
7195                         break;
7198 static struct ref_list *
7199 get_ref_list(const char *id)
7201         struct ref_list *list;
7202         size_t i;
7204         for (i = 0; i < ref_lists_size; i++)
7205                 if (!strcmp(id, ref_lists[i]->id))
7206                         return ref_lists[i];
7208         if (!realloc_ref_lists(&ref_lists, ref_lists_size, 1))
7209                 return NULL;
7210         list = calloc(1, sizeof(*list));
7211         if (!list)
7212                 return NULL;
7214         for (i = 0; i < refs_size; i++) {
7215                 if (!strcmp(id, refs[i]->id) &&
7216                     realloc_refs_list(&list->refs, list->size, 1))
7217                         list->refs[list->size++] = refs[i];
7218         }
7220         if (!list->refs) {
7221                 free(list);
7222                 return NULL;
7223         }
7225         qsort(list->refs, list->size, sizeof(*list->refs), compare_refs);
7226         ref_lists[ref_lists_size++] = list;
7227         return list;
7230 static int
7231 read_ref(char *id, size_t idlen, char *name, size_t namelen)
7233         struct ref *ref = NULL;
7234         bool tag = FALSE;
7235         bool ltag = FALSE;
7236         bool remote = FALSE;
7237         bool tracked = FALSE;
7238         bool head = FALSE;
7239         int from = 0, to = refs_size - 1;
7241         if (!prefixcmp(name, "refs/tags/")) {
7242                 if (!suffixcmp(name, namelen, "^{}")) {
7243                         namelen -= 3;
7244                         name[namelen] = 0;
7245                 } else {
7246                         ltag = TRUE;
7247                 }
7249                 tag = TRUE;
7250                 namelen -= STRING_SIZE("refs/tags/");
7251                 name    += STRING_SIZE("refs/tags/");
7253         } else if (!prefixcmp(name, "refs/remotes/")) {
7254                 remote = TRUE;
7255                 namelen -= STRING_SIZE("refs/remotes/");
7256                 name    += STRING_SIZE("refs/remotes/");
7257                 tracked  = !strcmp(opt_remote, name);
7259         } else if (!prefixcmp(name, "refs/heads/")) {
7260                 namelen -= STRING_SIZE("refs/heads/");
7261                 name    += STRING_SIZE("refs/heads/");
7262                 head     = !strncmp(opt_head, name, namelen);
7264         } else if (!strcmp(name, "HEAD")) {
7265                 string_ncopy(opt_head_rev, id, idlen);
7266                 return OK;
7267         }
7269         /* If we are reloading or it's an annotated tag, replace the
7270          * previous SHA1 with the resolved commit id; relies on the fact
7271          * git-ls-remote lists the commit id of an annotated tag right
7272          * before the commit id it points to. */
7273         while (from <= to) {
7274                 size_t pos = (to + from) / 2;
7275                 int cmp = strcmp(name, refs[pos]->name);
7277                 if (!cmp) {
7278                         ref = refs[pos];
7279                         break;
7280                 }
7282                 if (cmp < 0)
7283                         to = pos - 1;
7284                 else
7285                         from = pos + 1;
7286         }
7288         if (!ref) {
7289                 if (!realloc_refs(&refs, refs_size, 1))
7290                         return ERR;
7291                 ref = calloc(1, sizeof(*ref) + namelen);
7292                 if (!ref)
7293                         return ERR;
7294                 memmove(refs + from + 1, refs + from,
7295                         (refs_size - from) * sizeof(*refs));
7296                 refs[from] = ref;
7297                 strncpy(ref->name, name, namelen);
7298                 refs_size++;
7299         }
7301         ref->head = head;
7302         ref->tag = tag;
7303         ref->ltag = ltag;
7304         ref->remote = remote;
7305         ref->tracked = tracked;
7306         string_copy_rev(ref->id, id);
7308         return OK;
7311 static int
7312 load_refs(void)
7314         const char *head_argv[] = {
7315                 "git", "symbolic-ref", "HEAD", NULL
7316         };
7317         static const char *ls_remote_argv[SIZEOF_ARG] = {
7318                 "git", "ls-remote", opt_git_dir, NULL
7319         };
7320         static bool init = FALSE;
7321         size_t i;
7323         if (!init) {
7324                 argv_from_env(ls_remote_argv, "TIG_LS_REMOTE");
7325                 init = TRUE;
7326         }
7328         if (!*opt_git_dir)
7329                 return OK;
7331         if (run_io_buf(head_argv, opt_head, sizeof(opt_head)) &&
7332             !prefixcmp(opt_head, "refs/heads/")) {
7333                 char *offset = opt_head + STRING_SIZE("refs/heads/");
7335                 memmove(opt_head, offset, strlen(offset) + 1);
7336         }
7338         for (i = 0; i < refs_size; i++)
7339                 refs[i]->id[0] = 0;
7341         if (run_io_load(ls_remote_argv, "\t", read_ref) == ERR)
7342                 return ERR;
7344         /* Update the ref lists to reflect changes. */
7345         for (i = 0; i < ref_lists_size; i++) {
7346                 struct ref_list *list = ref_lists[i];
7347                 size_t old, new;
7349                 for (old = new = 0; old < list->size; old++)
7350                         if (!strcmp(list->id, list->refs[old]->id))
7351                                 list->refs[new++] = list->refs[old];
7352                 list->size = new;
7353         }
7355         return OK;
7358 static void
7359 set_remote_branch(const char *name, const char *value, size_t valuelen)
7361         if (!strcmp(name, ".remote")) {
7362                 string_ncopy(opt_remote, value, valuelen);
7364         } else if (*opt_remote && !strcmp(name, ".merge")) {
7365                 size_t from = strlen(opt_remote);
7367                 if (!prefixcmp(value, "refs/heads/"))
7368                         value += STRING_SIZE("refs/heads/");
7370                 if (!string_format_from(opt_remote, &from, "/%s", value))
7371                         opt_remote[0] = 0;
7372         }
7375 static void
7376 set_repo_config_option(char *name, char *value, int (*cmd)(int, const char **))
7378         const char *argv[SIZEOF_ARG] = { name, "=" };
7379         int argc = 1 + (cmd == option_set_command);
7380         int error = ERR;
7382         if (!argv_from_string(argv, &argc, value))
7383                 config_msg = "Too many option arguments";
7384         else
7385                 error = cmd(argc, argv);
7387         if (error == ERR)
7388                 warn("Option 'tig.%s': %s", name, config_msg);
7391 static bool
7392 set_environment_variable(const char *name, const char *value)
7394         size_t len = strlen(name) + 1 + strlen(value) + 1;
7395         char *env = malloc(len);
7397         if (env &&
7398             string_nformat(env, len, NULL, "%s=%s", name, value) &&
7399             putenv(env) == 0)
7400                 return TRUE;
7401         free(env);
7402         return FALSE;
7405 static void
7406 set_work_tree(const char *value)
7408         char cwd[SIZEOF_STR];
7410         if (!getcwd(cwd, sizeof(cwd)))
7411                 die("Failed to get cwd path: %s", strerror(errno));
7412         if (chdir(opt_git_dir) < 0)
7413                 die("Failed to chdir(%s): %s", strerror(errno));
7414         if (!getcwd(opt_git_dir, sizeof(opt_git_dir)))
7415                 die("Failed to get git path: %s", strerror(errno));
7416         if (chdir(cwd) < 0)
7417                 die("Failed to chdir(%s): %s", cwd, strerror(errno));
7418         if (chdir(value) < 0)
7419                 die("Failed to chdir(%s): %s", value, strerror(errno));
7420         if (!getcwd(cwd, sizeof(cwd)))
7421                 die("Failed to get cwd path: %s", strerror(errno));
7422         if (!set_environment_variable("GIT_WORK_TREE", cwd))
7423                 die("Failed to set GIT_WORK_TREE to '%s'", cwd);
7424         if (!set_environment_variable("GIT_DIR", opt_git_dir))
7425                 die("Failed to set GIT_DIR to '%s'", opt_git_dir);
7426         opt_is_inside_work_tree = TRUE;
7429 static int
7430 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
7432         if (!strcmp(name, "i18n.commitencoding"))
7433                 string_ncopy(opt_encoding, value, valuelen);
7435         else if (!strcmp(name, "core.editor"))
7436                 string_ncopy(opt_editor, value, valuelen);
7438         else if (!strcmp(name, "core.worktree"))
7439                 set_work_tree(value);
7441         else if (!prefixcmp(name, "tig.color."))
7442                 set_repo_config_option(name + 10, value, option_color_command);
7444         else if (!prefixcmp(name, "tig.bind."))
7445                 set_repo_config_option(name + 9, value, option_bind_command);
7447         else if (!prefixcmp(name, "tig."))
7448                 set_repo_config_option(name + 4, value, option_set_command);
7450         else if (*opt_head && !prefixcmp(name, "branch.") &&
7451                  !strncmp(name + 7, opt_head, strlen(opt_head)))
7452                 set_remote_branch(name + 7 + strlen(opt_head), value, valuelen);
7454         return OK;
7457 static int
7458 load_git_config(void)
7460         const char *config_list_argv[] = { "git", "config", "--list", NULL };
7462         return run_io_load(config_list_argv, "=", read_repo_config_option);
7465 static int
7466 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
7468         if (!opt_git_dir[0]) {
7469                 string_ncopy(opt_git_dir, name, namelen);
7471         } else if (opt_is_inside_work_tree == -1) {
7472                 /* This can be 3 different values depending on the
7473                  * version of git being used. If git-rev-parse does not
7474                  * understand --is-inside-work-tree it will simply echo
7475                  * the option else either "true" or "false" is printed.
7476                  * Default to true for the unknown case. */
7477                 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
7479         } else if (*name == '.') {
7480                 string_ncopy(opt_cdup, name, namelen);
7482         } else {
7483                 string_ncopy(opt_prefix, name, namelen);
7484         }
7486         return OK;
7489 static int
7490 load_repo_info(void)
7492         const char *rev_parse_argv[] = {
7493                 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
7494                         "--show-cdup", "--show-prefix", NULL
7495         };
7497         return run_io_load(rev_parse_argv, "=", read_repo_info);
7501 /*
7502  * Main
7503  */
7505 static const char usage[] =
7506 "tig " TIG_VERSION " (" __DATE__ ")\n"
7507 "\n"
7508 "Usage: tig        [options] [revs] [--] [paths]\n"
7509 "   or: tig show   [options] [revs] [--] [paths]\n"
7510 "   or: tig blame  [rev] path\n"
7511 "   or: tig status\n"
7512 "   or: tig <      [git command output]\n"
7513 "\n"
7514 "Options:\n"
7515 "  -v, --version   Show version and exit\n"
7516 "  -h, --help      Show help message and exit";
7518 static void __NORETURN
7519 quit(int sig)
7521         /* XXX: Restore tty modes and let the OS cleanup the rest! */
7522         if (cursed)
7523                 endwin();
7524         exit(0);
7527 static void __NORETURN
7528 die(const char *err, ...)
7530         va_list args;
7532         endwin();
7534         va_start(args, err);
7535         fputs("tig: ", stderr);
7536         vfprintf(stderr, err, args);
7537         fputs("\n", stderr);
7538         va_end(args);
7540         exit(1);
7543 static void
7544 warn(const char *msg, ...)
7546         va_list args;
7548         va_start(args, msg);
7549         fputs("tig warning: ", stderr);
7550         vfprintf(stderr, msg, args);
7551         fputs("\n", stderr);
7552         va_end(args);
7555 static enum request
7556 parse_options(int argc, const char *argv[])
7558         enum request request = REQ_VIEW_MAIN;
7559         const char *subcommand;
7560         bool seen_dashdash = FALSE;
7561         /* XXX: This is vulnerable to the user overriding options
7562          * required for the main view parser. */
7563         const char *custom_argv[SIZEOF_ARG] = {
7564                 "git", "log", "--no-color", "--pretty=raw", "--parents",
7565                         "--topo-order", NULL
7566         };
7567         int i, j = 6;
7569         if (!isatty(STDIN_FILENO)) {
7570                 io_open(&VIEW(REQ_VIEW_PAGER)->io, "");
7571                 return REQ_VIEW_PAGER;
7572         }
7574         if (argc <= 1)
7575                 return REQ_NONE;
7577         subcommand = argv[1];
7578         if (!strcmp(subcommand, "status")) {
7579                 if (argc > 2)
7580                         warn("ignoring arguments after `%s'", subcommand);
7581                 return REQ_VIEW_STATUS;
7583         } else if (!strcmp(subcommand, "blame")) {
7584                 if (argc <= 2 || argc > 4)
7585                         die("invalid number of options to blame\n\n%s", usage);
7587                 i = 2;
7588                 if (argc == 4) {
7589                         string_ncopy(opt_ref, argv[i], strlen(argv[i]));
7590                         i++;
7591                 }
7593                 string_ncopy(opt_file, argv[i], strlen(argv[i]));
7594                 return REQ_VIEW_BLAME;
7596         } else if (!strcmp(subcommand, "show")) {
7597                 request = REQ_VIEW_DIFF;
7599         } else {
7600                 subcommand = NULL;
7601         }
7603         if (subcommand) {
7604                 custom_argv[1] = subcommand;
7605                 j = 2;
7606         }
7608         for (i = 1 + !!subcommand; i < argc; i++) {
7609                 const char *opt = argv[i];
7611                 if (seen_dashdash || !strcmp(opt, "--")) {
7612                         seen_dashdash = TRUE;
7614                 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
7615                         printf("tig version %s\n", TIG_VERSION);
7616                         quit(0);
7618                 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
7619                         printf("%s\n", usage);
7620                         quit(0);
7621                 }
7623                 custom_argv[j++] = opt;
7624                 if (j >= ARRAY_SIZE(custom_argv))
7625                         die("command too long");
7626         }
7628         if (!prepare_update(VIEW(request), custom_argv, NULL, FORMAT_NONE))
7629                 die("Failed to format arguments");
7631         return request;
7634 int
7635 main(int argc, const char *argv[])
7637         enum request request = parse_options(argc, argv);
7638         struct view *view;
7639         size_t i;
7641         signal(SIGINT, quit);
7642         signal(SIGPIPE, SIG_IGN);
7644         if (setlocale(LC_ALL, "")) {
7645                 char *codeset = nl_langinfo(CODESET);
7647                 string_ncopy(opt_codeset, codeset, strlen(codeset));
7648         }
7650         if (load_repo_info() == ERR)
7651                 die("Failed to load repo info.");
7653         if (load_options() == ERR)
7654                 die("Failed to load user config.");
7656         if (load_git_config() == ERR)
7657                 die("Failed to load repo config.");
7659         /* Require a git repository unless when running in pager mode. */
7660         if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
7661                 die("Not a git repository");
7663         if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
7664                 opt_utf8 = FALSE;
7666         if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
7667                 opt_iconv = iconv_open(opt_codeset, opt_encoding);
7668                 if (opt_iconv == ICONV_NONE)
7669                         die("Failed to initialize character set conversion");
7670         }
7672         if (load_refs() == ERR)
7673                 die("Failed to load refs.");
7675         foreach_view (view, i)
7676                 argv_from_env(view->ops->argv, view->cmd_env);
7678         init_display();
7680         if (request != REQ_NONE)
7681                 open_view(NULL, request, OPEN_PREPARED);
7682         request = request == REQ_NONE ? REQ_VIEW_MAIN : REQ_NONE;
7684         while (view_driver(display[current_view], request)) {
7685                 int key = get_input(0);
7687                 view = display[current_view];
7688                 request = get_keybinding(view->keymap, key);
7690                 /* Some low-level request handling. This keeps access to
7691                  * status_win restricted. */
7692                 switch (request) {
7693                 case REQ_PROMPT:
7694                 {
7695                         char *cmd = read_prompt(":");
7697                         if (cmd && isdigit(*cmd)) {
7698                                 int lineno = view->lineno + 1;
7700                                 if (parse_int(&lineno, cmd, 1, view->lines + 1) == OK) {
7701                                         select_view_line(view, lineno - 1);
7702                                         report("");
7703                                 } else {
7704                                         report("Unable to parse '%s' as a line number", cmd);
7705                                 }
7707                         } else if (cmd) {
7708                                 struct view *next = VIEW(REQ_VIEW_PAGER);
7709                                 const char *argv[SIZEOF_ARG] = { "git" };
7710                                 int argc = 1;
7712                                 /* When running random commands, initially show the
7713                                  * command in the title. However, it maybe later be
7714                                  * overwritten if a commit line is selected. */
7715                                 string_ncopy(next->ref, cmd, strlen(cmd));
7717                                 if (!argv_from_string(argv, &argc, cmd)) {
7718                                         report("Too many arguments");
7719                                 } else if (!prepare_update(next, argv, NULL, FORMAT_DASH)) {
7720                                         report("Failed to format command");
7721                                 } else {
7722                                         open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
7723                                 }
7724                         }
7726                         request = REQ_NONE;
7727                         break;
7728                 }
7729                 case REQ_SEARCH:
7730                 case REQ_SEARCH_BACK:
7731                 {
7732                         const char *prompt = request == REQ_SEARCH ? "/" : "?";
7733                         char *search = read_prompt(prompt);
7735                         if (search)
7736                                 string_ncopy(opt_search, search, strlen(search));
7737                         else if (*opt_search)
7738                                 request = request == REQ_SEARCH ?
7739                                         REQ_FIND_NEXT :
7740                                         REQ_FIND_PREV;
7741                         else
7742                                 request = REQ_NONE;
7743                         break;
7744                 }
7745                 default:
7746                         break;
7747                 }
7748         }
7750         quit(0);
7752         return 0;