Code

e931ce246fc7ab59b4976e021c77ff423d47520d
[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 run_io_rd_dir(struct io *io, const char **argv, const char *dir, enum format_flags flags)
656         return init_io_rd(io, argv, dir, flags) && start_io(io);
659 static bool
660 io_eof(struct io *io)
662         return io->eof;
665 static int
666 io_error(struct io *io)
668         return io->error;
671 static char *
672 io_strerror(struct io *io)
674         return strerror(io->error);
677 static bool
678 io_can_read(struct io *io)
680         struct timeval tv = { 0, 500 };
681         fd_set fds;
683         FD_ZERO(&fds);
684         FD_SET(io->pipe, &fds);
686         return select(io->pipe + 1, &fds, NULL, NULL, &tv) > 0;
689 static ssize_t
690 io_read(struct io *io, void *buf, size_t bufsize)
692         do {
693                 ssize_t readsize = read(io->pipe, buf, bufsize);
695                 if (readsize < 0 && (errno == EAGAIN || errno == EINTR))
696                         continue;
697                 else if (readsize == -1)
698                         io->error = errno;
699                 else if (readsize == 0)
700                         io->eof = 1;
701                 return readsize;
702         } while (1);
705 DEFINE_ALLOCATOR(realloc_io_buf, char, BUFSIZ)
707 static char *
708 io_get(struct io *io, int c, bool can_read)
710         char *eol;
711         ssize_t readsize;
713         while (TRUE) {
714                 if (io->bufsize > 0) {
715                         eol = memchr(io->bufpos, c, io->bufsize);
716                         if (eol) {
717                                 char *line = io->bufpos;
719                                 *eol = 0;
720                                 io->bufpos = eol + 1;
721                                 io->bufsize -= io->bufpos - line;
722                                 return line;
723                         }
724                 }
726                 if (io_eof(io)) {
727                         if (io->bufsize) {
728                                 io->bufpos[io->bufsize] = 0;
729                                 io->bufsize = 0;
730                                 return io->bufpos;
731                         }
732                         return NULL;
733                 }
735                 if (!can_read)
736                         return NULL;
738                 if (io->bufsize > 0 && io->bufpos > io->buf)
739                         memmove(io->buf, io->bufpos, io->bufsize);
741                 if (io->bufalloc == io->bufsize) {
742                         if (!realloc_io_buf(&io->buf, io->bufalloc, BUFSIZ))
743                                 return NULL;
744                         io->bufalloc += BUFSIZ;
745                 }
747                 io->bufpos = io->buf;
748                 readsize = io_read(io, io->buf + io->bufsize, io->bufalloc - io->bufsize);
749                 if (io_error(io))
750                         return NULL;
751                 io->bufsize += readsize;
752         }
755 static bool
756 io_write(struct io *io, const void *buf, size_t bufsize)
758         size_t written = 0;
760         while (!io_error(io) && written < bufsize) {
761                 ssize_t size;
763                 size = write(io->pipe, buf + written, bufsize - written);
764                 if (size < 0 && (errno == EAGAIN || errno == EINTR))
765                         continue;
766                 else if (size == -1)
767                         io->error = errno;
768                 else
769                         written += size;
770         }
772         return written == bufsize;
775 static bool
776 io_read_buf(struct io *io, char buf[], size_t bufsize)
778         char *result = io_get(io, '\n', TRUE);
780         if (result) {
781                 result = chomp_string(result);
782                 string_ncopy_do(buf, bufsize, result, strlen(result));
783         }
785         return done_io(io) && result;
788 static bool
789 run_io_buf(const char **argv, char buf[], size_t bufsize)
791         struct io io = {};
793         return run_io_rd(&io, argv, NULL, FORMAT_NONE)
794             && io_read_buf(&io, buf, bufsize);
797 static int
798 io_load(struct io *io, const char *separators,
799         int (*read_property)(char *, size_t, char *, size_t))
801         char *name;
802         int state = OK;
804         if (!start_io(io))
805                 return ERR;
807         while (state == OK && (name = io_get(io, '\n', TRUE))) {
808                 char *value;
809                 size_t namelen;
810                 size_t valuelen;
812                 name = chomp_string(name);
813                 namelen = strcspn(name, separators);
815                 if (name[namelen]) {
816                         name[namelen] = 0;
817                         value = chomp_string(name + namelen + 1);
818                         valuelen = strlen(value);
820                 } else {
821                         value = "";
822                         valuelen = 0;
823                 }
825                 state = read_property(name, namelen, value, valuelen);
826         }
828         if (state != ERR && io_error(io))
829                 state = ERR;
830         done_io(io);
832         return state;
835 static int
836 run_io_load(const char **argv, const char *separators,
837             int (*read_property)(char *, size_t, char *, size_t))
839         struct io io = {};
841         return init_io_rd(&io, argv, NULL, FORMAT_NONE)
842                 ? io_load(&io, separators, read_property) : ERR;
846 /*
847  * User requests
848  */
850 #define REQ_INFO \
851         /* XXX: Keep the view request first and in sync with views[]. */ \
852         REQ_GROUP("View switching") \
853         REQ_(VIEW_MAIN,         "Show main view"), \
854         REQ_(VIEW_DIFF,         "Show diff view"), \
855         REQ_(VIEW_LOG,          "Show log view"), \
856         REQ_(VIEW_TREE,         "Show tree view"), \
857         REQ_(VIEW_BLOB,         "Show blob view"), \
858         REQ_(VIEW_BLAME,        "Show blame view"), \
859         REQ_(VIEW_BRANCH,       "Show branch view"), \
860         REQ_(VIEW_HELP,         "Show help page"), \
861         REQ_(VIEW_PAGER,        "Show pager view"), \
862         REQ_(VIEW_STATUS,       "Show status view"), \
863         REQ_(VIEW_STAGE,        "Show stage view"), \
864         \
865         REQ_GROUP("View manipulation") \
866         REQ_(ENTER,             "Enter current line and scroll"), \
867         REQ_(NEXT,              "Move to next"), \
868         REQ_(PREVIOUS,          "Move to previous"), \
869         REQ_(PARENT,            "Move to parent"), \
870         REQ_(VIEW_NEXT,         "Move focus to next view"), \
871         REQ_(REFRESH,           "Reload and refresh"), \
872         REQ_(MAXIMIZE,          "Maximize the current view"), \
873         REQ_(VIEW_CLOSE,        "Close the current view"), \
874         REQ_(QUIT,              "Close all views and quit"), \
875         \
876         REQ_GROUP("View specific requests") \
877         REQ_(STATUS_UPDATE,     "Update file status"), \
878         REQ_(STATUS_REVERT,     "Revert file changes"), \
879         REQ_(STATUS_MERGE,      "Merge file using external tool"), \
880         REQ_(STAGE_NEXT,        "Find next chunk to stage"), \
881         \
882         REQ_GROUP("Cursor navigation") \
883         REQ_(MOVE_UP,           "Move cursor one line up"), \
884         REQ_(MOVE_DOWN,         "Move cursor one line down"), \
885         REQ_(MOVE_PAGE_DOWN,    "Move cursor one page down"), \
886         REQ_(MOVE_PAGE_UP,      "Move cursor one page up"), \
887         REQ_(MOVE_FIRST_LINE,   "Move cursor to first line"), \
888         REQ_(MOVE_LAST_LINE,    "Move cursor to last line"), \
889         \
890         REQ_GROUP("Scrolling") \
891         REQ_(SCROLL_LEFT,       "Scroll two columns left"), \
892         REQ_(SCROLL_RIGHT,      "Scroll two columns right"), \
893         REQ_(SCROLL_LINE_UP,    "Scroll one line up"), \
894         REQ_(SCROLL_LINE_DOWN,  "Scroll one line down"), \
895         REQ_(SCROLL_PAGE_UP,    "Scroll one page up"), \
896         REQ_(SCROLL_PAGE_DOWN,  "Scroll one page down"), \
897         \
898         REQ_GROUP("Searching") \
899         REQ_(SEARCH,            "Search the view"), \
900         REQ_(SEARCH_BACK,       "Search backwards in the view"), \
901         REQ_(FIND_NEXT,         "Find next search match"), \
902         REQ_(FIND_PREV,         "Find previous search match"), \
903         \
904         REQ_GROUP("Option manipulation") \
905         REQ_(OPTIONS,           "Open option menu"), \
906         REQ_(TOGGLE_LINENO,     "Toggle line numbers"), \
907         REQ_(TOGGLE_DATE,       "Toggle date display"), \
908         REQ_(TOGGLE_DATE_SHORT, "Toggle short (date-only) dates"), \
909         REQ_(TOGGLE_AUTHOR,     "Toggle author display"), \
910         REQ_(TOGGLE_REV_GRAPH,  "Toggle revision graph visualization"), \
911         REQ_(TOGGLE_REFS,       "Toggle reference display (tags/branches)"), \
912         REQ_(TOGGLE_SORT_ORDER, "Toggle ascending/descending sort order"), \
913         REQ_(TOGGLE_SORT_FIELD, "Toggle field to sort by"), \
914         \
915         REQ_GROUP("Misc") \
916         REQ_(PROMPT,            "Bring up the prompt"), \
917         REQ_(SCREEN_REDRAW,     "Redraw the screen"), \
918         REQ_(SHOW_VERSION,      "Show version information"), \
919         REQ_(STOP_LOADING,      "Stop all loading views"), \
920         REQ_(EDIT,              "Open in editor"), \
921         REQ_(NONE,              "Do nothing")
924 /* User action requests. */
925 enum request {
926 #define REQ_GROUP(help)
927 #define REQ_(req, help) REQ_##req
929         /* Offset all requests to avoid conflicts with ncurses getch values. */
930         REQ_OFFSET = KEY_MAX + 1,
931         REQ_INFO
933 #undef  REQ_GROUP
934 #undef  REQ_
935 };
937 struct request_info {
938         enum request request;
939         const char *name;
940         int namelen;
941         const char *help;
942 };
944 static const struct request_info req_info[] = {
945 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
946 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
947         REQ_INFO
948 #undef  REQ_GROUP
949 #undef  REQ_
950 };
952 static enum request
953 get_request(const char *name)
955         int namelen = strlen(name);
956         int i;
958         for (i = 0; i < ARRAY_SIZE(req_info); i++)
959                 if (req_info[i].namelen == namelen &&
960                     !string_enum_compare(req_info[i].name, name, namelen))
961                         return req_info[i].request;
963         return REQ_NONE;
967 /*
968  * Options
969  */
971 /* Option and state variables. */
972 static enum date opt_date               = DATE_DEFAULT;
973 static bool opt_author                  = TRUE;
974 static bool opt_line_number             = FALSE;
975 static bool opt_line_graphics           = TRUE;
976 static bool opt_rev_graph               = FALSE;
977 static bool opt_show_refs               = TRUE;
978 static int opt_num_interval             = 5;
979 static double opt_hscroll               = 0.50;
980 static double opt_scale_split_view      = 2.0 / 3.0;
981 static int opt_tab_size                 = 8;
982 static int opt_author_cols              = 19;
983 static char opt_path[SIZEOF_STR]        = "";
984 static char opt_file[SIZEOF_STR]        = "";
985 static char opt_ref[SIZEOF_REF]         = "";
986 static char opt_head[SIZEOF_REF]        = "";
987 static char opt_head_rev[SIZEOF_REV]    = "";
988 static char opt_remote[SIZEOF_REF]      = "";
989 static char opt_encoding[20]            = "UTF-8";
990 static bool opt_utf8                    = TRUE;
991 static char opt_codeset[20]             = "UTF-8";
992 static iconv_t opt_iconv                = ICONV_NONE;
993 static char opt_search[SIZEOF_STR]      = "";
994 static char opt_cdup[SIZEOF_STR]        = "";
995 static char opt_prefix[SIZEOF_STR]      = "";
996 static char opt_git_dir[SIZEOF_STR]     = "";
997 static signed char opt_is_inside_work_tree      = -1; /* set to TRUE or FALSE */
998 static char opt_editor[SIZEOF_STR]      = "";
999 static FILE *opt_tty                    = NULL;
1001 #define is_initial_commit()     (!*opt_head_rev)
1002 #define is_head_commit(rev)     (!strcmp((rev), "HEAD") || !strcmp(opt_head_rev, (rev)))
1003 #define mkdate(time)            string_date(time, opt_date)
1006 /*
1007  * Line-oriented content detection.
1008  */
1010 #define LINE_INFO \
1011 LINE(DIFF_HEADER,  "diff --git ",       COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1012 LINE(DIFF_CHUNK,   "@@",                COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1013 LINE(DIFF_ADD,     "+",                 COLOR_GREEN,    COLOR_DEFAULT,  0), \
1014 LINE(DIFF_DEL,     "-",                 COLOR_RED,      COLOR_DEFAULT,  0), \
1015 LINE(DIFF_INDEX,        "index ",         COLOR_BLUE,   COLOR_DEFAULT,  0), \
1016 LINE(DIFF_OLDMODE,      "old file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
1017 LINE(DIFF_NEWMODE,      "new file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
1018 LINE(DIFF_COPY_FROM,    "copy from",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
1019 LINE(DIFF_COPY_TO,      "copy to",        COLOR_YELLOW, COLOR_DEFAULT,  0), \
1020 LINE(DIFF_RENAME_FROM,  "rename from",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
1021 LINE(DIFF_RENAME_TO,    "rename to",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
1022 LINE(DIFF_SIMILARITY,   "similarity ",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
1023 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
1024 LINE(DIFF_TREE,         "diff-tree ",     COLOR_BLUE,   COLOR_DEFAULT,  0), \
1025 LINE(PP_AUTHOR,    "Author: ",          COLOR_CYAN,     COLOR_DEFAULT,  0), \
1026 LINE(PP_COMMIT,    "Commit: ",          COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1027 LINE(PP_MERGE,     "Merge: ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
1028 LINE(PP_DATE,      "Date:   ",          COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1029 LINE(PP_ADATE,     "AuthorDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1030 LINE(PP_CDATE,     "CommitDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1031 LINE(PP_REFS,      "Refs: ",            COLOR_RED,      COLOR_DEFAULT,  0), \
1032 LINE(COMMIT,       "commit ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
1033 LINE(PARENT,       "parent ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
1034 LINE(TREE,         "tree ",             COLOR_BLUE,     COLOR_DEFAULT,  0), \
1035 LINE(AUTHOR,       "author ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
1036 LINE(COMMITTER,    "committer ",        COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1037 LINE(SIGNOFF,      "    Signed-off-by", COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1038 LINE(ACKED,        "    Acked-by",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1039 LINE(DEFAULT,      "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
1040 LINE(CURSOR,       "",                  COLOR_WHITE,    COLOR_GREEN,    A_BOLD), \
1041 LINE(STATUS,       "",                  COLOR_GREEN,    COLOR_DEFAULT,  0), \
1042 LINE(DELIMITER,    "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1043 LINE(DATE,         "",                  COLOR_BLUE,     COLOR_DEFAULT,  0), \
1044 LINE(MODE,         "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1045 LINE(LINE_NUMBER,  "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1046 LINE(TITLE_BLUR,   "",                  COLOR_WHITE,    COLOR_BLUE,     0), \
1047 LINE(TITLE_FOCUS,  "",                  COLOR_WHITE,    COLOR_BLUE,     A_BOLD), \
1048 LINE(MAIN_COMMIT,  "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
1049 LINE(MAIN_TAG,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  A_BOLD), \
1050 LINE(MAIN_LOCAL_TAG,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1051 LINE(MAIN_REMOTE,  "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1052 LINE(MAIN_TRACKED, "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_BOLD), \
1053 LINE(MAIN_REF,     "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1054 LINE(MAIN_HEAD,    "",                  COLOR_CYAN,     COLOR_DEFAULT,  A_BOLD), \
1055 LINE(MAIN_REVGRAPH,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1056 LINE(TREE_HEAD,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_BOLD), \
1057 LINE(TREE_DIR,     "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_NORMAL), \
1058 LINE(TREE_FILE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
1059 LINE(STAT_HEAD,    "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1060 LINE(STAT_SECTION, "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1061 LINE(STAT_NONE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
1062 LINE(STAT_STAGED,  "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1063 LINE(STAT_UNSTAGED,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1064 LINE(STAT_UNTRACKED,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1065 LINE(HELP_KEYMAP,  "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1066 LINE(HELP_GROUP,   "",                  COLOR_BLUE,     COLOR_DEFAULT,  0), \
1067 LINE(BLAME_ID,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0)
1069 enum line_type {
1070 #define LINE(type, line, fg, bg, attr) \
1071         LINE_##type
1072         LINE_INFO,
1073         LINE_NONE
1074 #undef  LINE
1075 };
1077 struct line_info {
1078         const char *name;       /* Option name. */
1079         int namelen;            /* Size of option name. */
1080         const char *line;       /* The start of line to match. */
1081         int linelen;            /* Size of string to match. */
1082         int fg, bg, attr;       /* Color and text attributes for the lines. */
1083 };
1085 static struct line_info line_info[] = {
1086 #define LINE(type, line, fg, bg, attr) \
1087         { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
1088         LINE_INFO
1089 #undef  LINE
1090 };
1092 static enum line_type
1093 get_line_type(const char *line)
1095         int linelen = strlen(line);
1096         enum line_type type;
1098         for (type = 0; type < ARRAY_SIZE(line_info); type++)
1099                 /* Case insensitive search matches Signed-off-by lines better. */
1100                 if (linelen >= line_info[type].linelen &&
1101                     !strncasecmp(line_info[type].line, line, line_info[type].linelen))
1102                         return type;
1104         return LINE_DEFAULT;
1107 static inline int
1108 get_line_attr(enum line_type type)
1110         assert(type < ARRAY_SIZE(line_info));
1111         return COLOR_PAIR(type) | line_info[type].attr;
1114 static struct line_info *
1115 get_line_info(const char *name)
1117         size_t namelen = strlen(name);
1118         enum line_type type;
1120         for (type = 0; type < ARRAY_SIZE(line_info); type++)
1121                 if (namelen == line_info[type].namelen &&
1122                     !string_enum_compare(line_info[type].name, name, namelen))
1123                         return &line_info[type];
1125         return NULL;
1128 static void
1129 init_colors(void)
1131         int default_bg = line_info[LINE_DEFAULT].bg;
1132         int default_fg = line_info[LINE_DEFAULT].fg;
1133         enum line_type type;
1135         start_color();
1137         if (assume_default_colors(default_fg, default_bg) == ERR) {
1138                 default_bg = COLOR_BLACK;
1139                 default_fg = COLOR_WHITE;
1140         }
1142         for (type = 0; type < ARRAY_SIZE(line_info); type++) {
1143                 struct line_info *info = &line_info[type];
1144                 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
1145                 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
1147                 init_pair(type, fg, bg);
1148         }
1151 struct line {
1152         enum line_type type;
1154         /* State flags */
1155         unsigned int selected:1;
1156         unsigned int dirty:1;
1157         unsigned int cleareol:1;
1158         unsigned int other:16;
1160         void *data;             /* User data */
1161 };
1164 /*
1165  * Keys
1166  */
1168 struct keybinding {
1169         int alias;
1170         enum request request;
1171 };
1173 static const struct keybinding default_keybindings[] = {
1174         /* View switching */
1175         { 'm',          REQ_VIEW_MAIN },
1176         { 'd',          REQ_VIEW_DIFF },
1177         { 'l',          REQ_VIEW_LOG },
1178         { 't',          REQ_VIEW_TREE },
1179         { 'f',          REQ_VIEW_BLOB },
1180         { 'B',          REQ_VIEW_BLAME },
1181         { 'H',          REQ_VIEW_BRANCH },
1182         { 'p',          REQ_VIEW_PAGER },
1183         { 'h',          REQ_VIEW_HELP },
1184         { 'S',          REQ_VIEW_STATUS },
1185         { 'c',          REQ_VIEW_STAGE },
1187         /* View manipulation */
1188         { 'q',          REQ_VIEW_CLOSE },
1189         { KEY_TAB,      REQ_VIEW_NEXT },
1190         { KEY_RETURN,   REQ_ENTER },
1191         { KEY_UP,       REQ_PREVIOUS },
1192         { KEY_DOWN,     REQ_NEXT },
1193         { 'R',          REQ_REFRESH },
1194         { KEY_F(5),     REQ_REFRESH },
1195         { 'O',          REQ_MAXIMIZE },
1197         /* Cursor navigation */
1198         { 'k',          REQ_MOVE_UP },
1199         { 'j',          REQ_MOVE_DOWN },
1200         { KEY_HOME,     REQ_MOVE_FIRST_LINE },
1201         { KEY_END,      REQ_MOVE_LAST_LINE },
1202         { KEY_NPAGE,    REQ_MOVE_PAGE_DOWN },
1203         { ' ',          REQ_MOVE_PAGE_DOWN },
1204         { KEY_PPAGE,    REQ_MOVE_PAGE_UP },
1205         { 'b',          REQ_MOVE_PAGE_UP },
1206         { '-',          REQ_MOVE_PAGE_UP },
1208         /* Scrolling */
1209         { KEY_LEFT,     REQ_SCROLL_LEFT },
1210         { KEY_RIGHT,    REQ_SCROLL_RIGHT },
1211         { KEY_IC,       REQ_SCROLL_LINE_UP },
1212         { KEY_DC,       REQ_SCROLL_LINE_DOWN },
1213         { 'w',          REQ_SCROLL_PAGE_UP },
1214         { 's',          REQ_SCROLL_PAGE_DOWN },
1216         /* Searching */
1217         { '/',          REQ_SEARCH },
1218         { '?',          REQ_SEARCH_BACK },
1219         { 'n',          REQ_FIND_NEXT },
1220         { 'N',          REQ_FIND_PREV },
1222         /* Misc */
1223         { 'Q',          REQ_QUIT },
1224         { 'z',          REQ_STOP_LOADING },
1225         { 'v',          REQ_SHOW_VERSION },
1226         { 'r',          REQ_SCREEN_REDRAW },
1227         { 'o',          REQ_OPTIONS },
1228         { '.',          REQ_TOGGLE_LINENO },
1229         { 'D',          REQ_TOGGLE_DATE },
1230         { 'A',          REQ_TOGGLE_AUTHOR },
1231         { 'g',          REQ_TOGGLE_REV_GRAPH },
1232         { 'F',          REQ_TOGGLE_REFS },
1233         { 'I',          REQ_TOGGLE_SORT_ORDER },
1234         { 'i',          REQ_TOGGLE_SORT_FIELD },
1235         { ':',          REQ_PROMPT },
1236         { 'u',          REQ_STATUS_UPDATE },
1237         { '!',          REQ_STATUS_REVERT },
1238         { 'M',          REQ_STATUS_MERGE },
1239         { '@',          REQ_STAGE_NEXT },
1240         { ',',          REQ_PARENT },
1241         { 'e',          REQ_EDIT },
1242 };
1244 #define KEYMAP_INFO \
1245         KEYMAP_(GENERIC), \
1246         KEYMAP_(MAIN), \
1247         KEYMAP_(DIFF), \
1248         KEYMAP_(LOG), \
1249         KEYMAP_(TREE), \
1250         KEYMAP_(BLOB), \
1251         KEYMAP_(BLAME), \
1252         KEYMAP_(BRANCH), \
1253         KEYMAP_(PAGER), \
1254         KEYMAP_(HELP), \
1255         KEYMAP_(STATUS), \
1256         KEYMAP_(STAGE)
1258 enum keymap {
1259 #define KEYMAP_(name) KEYMAP_##name
1260         KEYMAP_INFO
1261 #undef  KEYMAP_
1262 };
1264 static const struct enum_map keymap_table[] = {
1265 #define KEYMAP_(name) ENUM_MAP(#name, KEYMAP_##name)
1266         KEYMAP_INFO
1267 #undef  KEYMAP_
1268 };
1270 #define set_keymap(map, name) map_enum(map, keymap_table, name)
1272 struct keybinding_table {
1273         struct keybinding *data;
1274         size_t size;
1275 };
1277 static struct keybinding_table keybindings[ARRAY_SIZE(keymap_table)];
1279 static void
1280 add_keybinding(enum keymap keymap, enum request request, int key)
1282         struct keybinding_table *table = &keybindings[keymap];
1284         table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data));
1285         if (!table->data)
1286                 die("Failed to allocate keybinding");
1287         table->data[table->size].alias = key;
1288         table->data[table->size++].request = request;
1291 /* Looks for a key binding first in the given map, then in the generic map, and
1292  * lastly in the default keybindings. */
1293 static enum request
1294 get_keybinding(enum keymap keymap, int key)
1296         size_t i;
1298         for (i = 0; i < keybindings[keymap].size; i++)
1299                 if (keybindings[keymap].data[i].alias == key)
1300                         return keybindings[keymap].data[i].request;
1302         for (i = 0; i < keybindings[KEYMAP_GENERIC].size; i++)
1303                 if (keybindings[KEYMAP_GENERIC].data[i].alias == key)
1304                         return keybindings[KEYMAP_GENERIC].data[i].request;
1306         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1307                 if (default_keybindings[i].alias == key)
1308                         return default_keybindings[i].request;
1310         return (enum request) key;
1314 struct key {
1315         const char *name;
1316         int value;
1317 };
1319 static const struct key key_table[] = {
1320         { "Enter",      KEY_RETURN },
1321         { "Space",      ' ' },
1322         { "Backspace",  KEY_BACKSPACE },
1323         { "Tab",        KEY_TAB },
1324         { "Escape",     KEY_ESC },
1325         { "Left",       KEY_LEFT },
1326         { "Right",      KEY_RIGHT },
1327         { "Up",         KEY_UP },
1328         { "Down",       KEY_DOWN },
1329         { "Insert",     KEY_IC },
1330         { "Delete",     KEY_DC },
1331         { "Hash",       '#' },
1332         { "Home",       KEY_HOME },
1333         { "End",        KEY_END },
1334         { "PageUp",     KEY_PPAGE },
1335         { "PageDown",   KEY_NPAGE },
1336         { "F1",         KEY_F(1) },
1337         { "F2",         KEY_F(2) },
1338         { "F3",         KEY_F(3) },
1339         { "F4",         KEY_F(4) },
1340         { "F5",         KEY_F(5) },
1341         { "F6",         KEY_F(6) },
1342         { "F7",         KEY_F(7) },
1343         { "F8",         KEY_F(8) },
1344         { "F9",         KEY_F(9) },
1345         { "F10",        KEY_F(10) },
1346         { "F11",        KEY_F(11) },
1347         { "F12",        KEY_F(12) },
1348 };
1350 static int
1351 get_key_value(const char *name)
1353         int i;
1355         for (i = 0; i < ARRAY_SIZE(key_table); i++)
1356                 if (!strcasecmp(key_table[i].name, name))
1357                         return key_table[i].value;
1359         if (strlen(name) == 1 && isprint(*name))
1360                 return (int) *name;
1362         return ERR;
1365 static const char *
1366 get_key_name(int key_value)
1368         static char key_char[] = "'X'";
1369         const char *seq = NULL;
1370         int key;
1372         for (key = 0; key < ARRAY_SIZE(key_table); key++)
1373                 if (key_table[key].value == key_value)
1374                         seq = key_table[key].name;
1376         if (seq == NULL &&
1377             key_value < 127 &&
1378             isprint(key_value)) {
1379                 key_char[1] = (char) key_value;
1380                 seq = key_char;
1381         }
1383         return seq ? seq : "(no key)";
1386 static bool
1387 append_key(char *buf, size_t *pos, const struct keybinding *keybinding)
1389         const char *sep = *pos > 0 ? ", " : "";
1390         const char *keyname = get_key_name(keybinding->alias);
1392         return string_nformat(buf, BUFSIZ, pos, "%s%s", sep, keyname);
1395 static bool
1396 append_keymap_request_keys(char *buf, size_t *pos, enum request request,
1397                            enum keymap keymap, bool all)
1399         int i;
1401         for (i = 0; i < keybindings[keymap].size; i++) {
1402                 if (keybindings[keymap].data[i].request == request) {
1403                         if (!append_key(buf, pos, &keybindings[keymap].data[i]))
1404                                 return FALSE;
1405                         if (!all)
1406                                 break;
1407                 }
1408         }
1410         return TRUE;
1413 #define get_key(keymap, request) get_keys(keymap, request, FALSE)
1415 static const char *
1416 get_keys(enum keymap keymap, enum request request, bool all)
1418         static char buf[BUFSIZ];
1419         size_t pos = 0;
1420         int i;
1422         buf[pos] = 0;
1424         if (!append_keymap_request_keys(buf, &pos, request, keymap, all))
1425                 return "Too many keybindings!";
1426         if (pos > 0 && !all)
1427                 return buf;
1429         if (keymap != KEYMAP_GENERIC) {
1430                 /* Only the generic keymap includes the default keybindings when
1431                  * listing all keys. */
1432                 if (all)
1433                         return buf;
1435                 if (!append_keymap_request_keys(buf, &pos, request, KEYMAP_GENERIC, all))
1436                         return "Too many keybindings!";
1437                 if (pos)
1438                         return buf;
1439         }
1441         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1442                 if (default_keybindings[i].request == request) {
1443                         if (!append_key(buf, &pos, &default_keybindings[i]))
1444                                 return "Too many keybindings!";
1445                         if (!all)
1446                                 return buf;
1447                 }
1448         }
1450         return buf;
1453 struct run_request {
1454         enum keymap keymap;
1455         int key;
1456         const char *argv[SIZEOF_ARG];
1457 };
1459 static struct run_request *run_request;
1460 static size_t run_requests;
1462 DEFINE_ALLOCATOR(realloc_run_requests, struct run_request, 8)
1464 static enum request
1465 add_run_request(enum keymap keymap, int key, int argc, const char **argv)
1467         struct run_request *req;
1469         if (argc >= ARRAY_SIZE(req->argv) - 1)
1470                 return REQ_NONE;
1472         if (!realloc_run_requests(&run_request, run_requests, 1))
1473                 return REQ_NONE;
1475         req = &run_request[run_requests];
1476         req->keymap = keymap;
1477         req->key = key;
1478         req->argv[0] = NULL;
1480         if (!format_argv(req->argv, argv, FORMAT_NONE))
1481                 return REQ_NONE;
1483         return REQ_NONE + ++run_requests;
1486 static struct run_request *
1487 get_run_request(enum request request)
1489         if (request <= REQ_NONE)
1490                 return NULL;
1491         return &run_request[request - REQ_NONE - 1];
1494 static void
1495 add_builtin_run_requests(void)
1497         const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1498         const char *commit[] = { "git", "commit", NULL };
1499         const char *gc[] = { "git", "gc", NULL };
1500         struct {
1501                 enum keymap keymap;
1502                 int key;
1503                 int argc;
1504                 const char **argv;
1505         } reqs[] = {
1506                 { KEYMAP_MAIN,    'C', ARRAY_SIZE(cherry_pick) - 1, cherry_pick },
1507                 { KEYMAP_STATUS,  'C', ARRAY_SIZE(commit) - 1, commit },
1508                 { KEYMAP_GENERIC, 'G', ARRAY_SIZE(gc) - 1, gc },
1509         };
1510         int i;
1512         for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1513                 enum request req;
1515                 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argc, reqs[i].argv);
1516                 if (req != REQ_NONE)
1517                         add_keybinding(reqs[i].keymap, req, reqs[i].key);
1518         }
1521 /*
1522  * User config file handling.
1523  */
1525 static int   config_lineno;
1526 static bool  config_errors;
1527 static const char *config_msg;
1529 static const struct enum_map color_map[] = {
1530 #define COLOR_MAP(name) ENUM_MAP(#name, COLOR_##name)
1531         COLOR_MAP(DEFAULT),
1532         COLOR_MAP(BLACK),
1533         COLOR_MAP(BLUE),
1534         COLOR_MAP(CYAN),
1535         COLOR_MAP(GREEN),
1536         COLOR_MAP(MAGENTA),
1537         COLOR_MAP(RED),
1538         COLOR_MAP(WHITE),
1539         COLOR_MAP(YELLOW),
1540 };
1542 static const struct enum_map attr_map[] = {
1543 #define ATTR_MAP(name) ENUM_MAP(#name, A_##name)
1544         ATTR_MAP(NORMAL),
1545         ATTR_MAP(BLINK),
1546         ATTR_MAP(BOLD),
1547         ATTR_MAP(DIM),
1548         ATTR_MAP(REVERSE),
1549         ATTR_MAP(STANDOUT),
1550         ATTR_MAP(UNDERLINE),
1551 };
1553 #define set_attribute(attr, name)       map_enum(attr, attr_map, name)
1555 static int parse_step(double *opt, const char *arg)
1557         *opt = atoi(arg);
1558         if (!strchr(arg, '%'))
1559                 return OK;
1561         /* "Shift down" so 100% and 1 does not conflict. */
1562         *opt = (*opt - 1) / 100;
1563         if (*opt >= 1.0) {
1564                 *opt = 0.99;
1565                 config_msg = "Step value larger than 100%";
1566                 return ERR;
1567         }
1568         if (*opt < 0.0) {
1569                 *opt = 1;
1570                 config_msg = "Invalid step value";
1571                 return ERR;
1572         }
1573         return OK;
1576 static int
1577 parse_int(int *opt, const char *arg, int min, int max)
1579         int value = atoi(arg);
1581         if (min <= value && value <= max) {
1582                 *opt = value;
1583                 return OK;
1584         }
1586         config_msg = "Integer value out of bound";
1587         return ERR;
1590 static bool
1591 set_color(int *color, const char *name)
1593         if (map_enum(color, color_map, name))
1594                 return TRUE;
1595         if (!prefixcmp(name, "color"))
1596                 return parse_int(color, name + 5, 0, 255) == OK;
1597         return FALSE;
1600 /* Wants: object fgcolor bgcolor [attribute] */
1601 static int
1602 option_color_command(int argc, const char *argv[])
1604         struct line_info *info;
1606         if (argc < 3) {
1607                 config_msg = "Wrong number of arguments given to color command";
1608                 return ERR;
1609         }
1611         info = get_line_info(argv[0]);
1612         if (!info) {
1613                 static const struct enum_map obsolete[] = {
1614                         ENUM_MAP("main-delim",  LINE_DELIMITER),
1615                         ENUM_MAP("main-date",   LINE_DATE),
1616                         ENUM_MAP("main-author", LINE_AUTHOR),
1617                 };
1618                 int index;
1620                 if (!map_enum(&index, obsolete, argv[0])) {
1621                         config_msg = "Unknown color name";
1622                         return ERR;
1623                 }
1624                 info = &line_info[index];
1625         }
1627         if (!set_color(&info->fg, argv[1]) ||
1628             !set_color(&info->bg, argv[2])) {
1629                 config_msg = "Unknown color";
1630                 return ERR;
1631         }
1633         info->attr = 0;
1634         while (argc-- > 3) {
1635                 int attr;
1637                 if (!set_attribute(&attr, argv[argc])) {
1638                         config_msg = "Unknown attribute";
1639                         return ERR;
1640                 }
1641                 info->attr |= attr;
1642         }
1644         return OK;
1647 static int parse_bool(bool *opt, const char *arg)
1649         *opt = (!strcmp(arg, "1") || !strcmp(arg, "true") || !strcmp(arg, "yes"))
1650                 ? TRUE : FALSE;
1651         return OK;
1654 static int
1655 parse_string(char *opt, const char *arg, size_t optsize)
1657         int arglen = strlen(arg);
1659         switch (arg[0]) {
1660         case '\"':
1661         case '\'':
1662                 if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1663                         config_msg = "Unmatched quotation";
1664                         return ERR;
1665                 }
1666                 arg += 1; arglen -= 2;
1667         default:
1668                 string_ncopy_do(opt, optsize, arg, arglen);
1669                 return OK;
1670         }
1673 /* Wants: name = value */
1674 static int
1675 option_set_command(int argc, const char *argv[])
1677         if (argc != 3) {
1678                 config_msg = "Wrong number of arguments given to set command";
1679                 return ERR;
1680         }
1682         if (strcmp(argv[1], "=")) {
1683                 config_msg = "No value assigned";
1684                 return ERR;
1685         }
1687         if (!strcmp(argv[0], "show-author"))
1688                 return parse_bool(&opt_author, argv[2]);
1690         if (!strcmp(argv[0], "show-date")) {
1691                 bool show_date;
1693                 if (!strcmp(argv[2], "relative")) {
1694                         opt_date = DATE_RELATIVE;
1695                         return OK;
1696                 } else if (!strcmp(argv[2], "short")) {
1697                         opt_date = DATE_SHORT;
1698                         return OK;
1699                 } else if (parse_bool(&show_date, argv[2])) {
1700                         opt_date = show_date ? DATE_DEFAULT : DATE_NONE;
1701                 }
1702                 return ERR;
1703         }
1705         if (!strcmp(argv[0], "show-rev-graph"))
1706                 return parse_bool(&opt_rev_graph, argv[2]);
1708         if (!strcmp(argv[0], "show-refs"))
1709                 return parse_bool(&opt_show_refs, argv[2]);
1711         if (!strcmp(argv[0], "show-line-numbers"))
1712                 return parse_bool(&opt_line_number, argv[2]);
1714         if (!strcmp(argv[0], "line-graphics"))
1715                 return parse_bool(&opt_line_graphics, argv[2]);
1717         if (!strcmp(argv[0], "line-number-interval"))
1718                 return parse_int(&opt_num_interval, argv[2], 1, 1024);
1720         if (!strcmp(argv[0], "author-width"))
1721                 return parse_int(&opt_author_cols, argv[2], 0, 1024);
1723         if (!strcmp(argv[0], "horizontal-scroll"))
1724                 return parse_step(&opt_hscroll, argv[2]);
1726         if (!strcmp(argv[0], "split-view-height"))
1727                 return parse_step(&opt_scale_split_view, argv[2]);
1729         if (!strcmp(argv[0], "tab-size"))
1730                 return parse_int(&opt_tab_size, argv[2], 1, 1024);
1732         if (!strcmp(argv[0], "commit-encoding"))
1733                 return parse_string(opt_encoding, argv[2], sizeof(opt_encoding));
1735         config_msg = "Unknown variable name";
1736         return ERR;
1739 /* Wants: mode request key */
1740 static int
1741 option_bind_command(int argc, const char *argv[])
1743         enum request request;
1744         int keymap = -1;
1745         int key;
1747         if (argc < 3) {
1748                 config_msg = "Wrong number of arguments given to bind command";
1749                 return ERR;
1750         }
1752         if (set_keymap(&keymap, argv[0]) == ERR) {
1753                 config_msg = "Unknown key map";
1754                 return ERR;
1755         }
1757         key = get_key_value(argv[1]);
1758         if (key == ERR) {
1759                 config_msg = "Unknown key";
1760                 return ERR;
1761         }
1763         request = get_request(argv[2]);
1764         if (request == REQ_NONE) {
1765                 static const struct enum_map obsolete[] = {
1766                         ENUM_MAP("cherry-pick",         REQ_NONE),
1767                         ENUM_MAP("screen-resize",       REQ_NONE),
1768                         ENUM_MAP("tree-parent",         REQ_PARENT),
1769                 };
1770                 int alias;
1772                 if (map_enum(&alias, obsolete, argv[2])) {
1773                         if (alias != REQ_NONE)
1774                                 add_keybinding(keymap, alias, key);
1775                         config_msg = "Obsolete request name";
1776                         return ERR;
1777                 }
1778         }
1779         if (request == REQ_NONE && *argv[2]++ == '!')
1780                 request = add_run_request(keymap, key, argc - 2, argv + 2);
1781         if (request == REQ_NONE) {
1782                 config_msg = "Unknown request name";
1783                 return ERR;
1784         }
1786         add_keybinding(keymap, request, key);
1788         return OK;
1791 static int
1792 set_option(const char *opt, char *value)
1794         const char *argv[SIZEOF_ARG];
1795         int argc = 0;
1797         if (!argv_from_string(argv, &argc, value)) {
1798                 config_msg = "Too many option arguments";
1799                 return ERR;
1800         }
1802         if (!strcmp(opt, "color"))
1803                 return option_color_command(argc, argv);
1805         if (!strcmp(opt, "set"))
1806                 return option_set_command(argc, argv);
1808         if (!strcmp(opt, "bind"))
1809                 return option_bind_command(argc, argv);
1811         config_msg = "Unknown option command";
1812         return ERR;
1815 static int
1816 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
1818         int status = OK;
1820         config_lineno++;
1821         config_msg = "Internal error";
1823         /* Check for comment markers, since read_properties() will
1824          * only ensure opt and value are split at first " \t". */
1825         optlen = strcspn(opt, "#");
1826         if (optlen == 0)
1827                 return OK;
1829         if (opt[optlen] != 0) {
1830                 config_msg = "No option value";
1831                 status = ERR;
1833         }  else {
1834                 /* Look for comment endings in the value. */
1835                 size_t len = strcspn(value, "#");
1837                 if (len < valuelen) {
1838                         valuelen = len;
1839                         value[valuelen] = 0;
1840                 }
1842                 status = set_option(opt, value);
1843         }
1845         if (status == ERR) {
1846                 warn("Error on line %d, near '%.*s': %s",
1847                      config_lineno, (int) optlen, opt, config_msg);
1848                 config_errors = TRUE;
1849         }
1851         /* Always keep going if errors are encountered. */
1852         return OK;
1855 static void
1856 load_option_file(const char *path)
1858         struct io io = {};
1860         /* It's OK that the file doesn't exist. */
1861         if (!io_open(&io, path))
1862                 return;
1864         config_lineno = 0;
1865         config_errors = FALSE;
1867         if (io_load(&io, " \t", read_option) == ERR ||
1868             config_errors == TRUE)
1869                 warn("Errors while loading %s.", path);
1872 static int
1873 load_options(void)
1875         const char *home = getenv("HOME");
1876         const char *tigrc_user = getenv("TIGRC_USER");
1877         const char *tigrc_system = getenv("TIGRC_SYSTEM");
1878         char buf[SIZEOF_STR];
1880         add_builtin_run_requests();
1882         if (!tigrc_system)
1883                 tigrc_system = SYSCONFDIR "/tigrc";
1884         load_option_file(tigrc_system);
1886         if (!tigrc_user) {
1887                 if (!home || !string_format(buf, "%s/.tigrc", home))
1888                         return ERR;
1889                 tigrc_user = buf;
1890         }
1891         load_option_file(tigrc_user);
1893         return OK;
1897 /*
1898  * The viewer
1899  */
1901 struct view;
1902 struct view_ops;
1904 /* The display array of active views and the index of the current view. */
1905 static struct view *display[2];
1906 static unsigned int current_view;
1908 #define foreach_displayed_view(view, i) \
1909         for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1911 #define displayed_views()       (display[1] != NULL ? 2 : 1)
1913 /* Current head and commit ID */
1914 static char ref_blob[SIZEOF_REF]        = "";
1915 static char ref_commit[SIZEOF_REF]      = "HEAD";
1916 static char ref_head[SIZEOF_REF]        = "HEAD";
1918 struct view {
1919         const char *name;       /* View name */
1920         const char *cmd_env;    /* Command line set via environment */
1921         const char *id;         /* Points to either of ref_{head,commit,blob} */
1923         struct view_ops *ops;   /* View operations */
1925         enum keymap keymap;     /* What keymap does this view have */
1926         bool git_dir;           /* Whether the view requires a git directory. */
1928         char ref[SIZEOF_REF];   /* Hovered commit reference */
1929         char vid[SIZEOF_REF];   /* View ID. Set to id member when updating. */
1931         int height, width;      /* The width and height of the main window */
1932         WINDOW *win;            /* The main window */
1933         WINDOW *title;          /* The title window living below the main window */
1935         /* Navigation */
1936         unsigned long offset;   /* Offset of the window top */
1937         unsigned long yoffset;  /* Offset from the window side. */
1938         unsigned long lineno;   /* Current line number */
1939         unsigned long p_offset; /* Previous offset of the window top */
1940         unsigned long p_yoffset;/* Previous offset from the window side */
1941         unsigned long p_lineno; /* Previous current line number */
1942         bool p_restore;         /* Should the previous position be restored. */
1944         /* Searching */
1945         char grep[SIZEOF_STR];  /* Search string */
1946         regex_t *regex;         /* Pre-compiled regexp */
1948         /* If non-NULL, points to the view that opened this view. If this view
1949          * is closed tig will switch back to the parent view. */
1950         struct view *parent;
1952         /* Buffering */
1953         size_t lines;           /* Total number of lines */
1954         struct line *line;      /* Line index */
1955         unsigned int digits;    /* Number of digits in the lines member. */
1957         /* Drawing */
1958         struct line *curline;   /* Line currently being drawn. */
1959         enum line_type curtype; /* Attribute currently used for drawing. */
1960         unsigned long col;      /* Column when drawing. */
1961         bool has_scrolled;      /* View was scrolled. */
1963         /* Loading */
1964         struct io io;
1965         struct io *pipe;
1966         time_t start_time;
1967         time_t update_secs;
1968 };
1970 struct view_ops {
1971         /* What type of content being displayed. Used in the title bar. */
1972         const char *type;
1973         /* Default command arguments. */
1974         const char **argv;
1975         /* Open and reads in all view content. */
1976         bool (*open)(struct view *view);
1977         /* Read one line; updates view->line. */
1978         bool (*read)(struct view *view, char *data);
1979         /* Draw one line; @lineno must be < view->height. */
1980         bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
1981         /* Depending on view handle a special requests. */
1982         enum request (*request)(struct view *view, enum request request, struct line *line);
1983         /* Search for regexp in a line. */
1984         bool (*grep)(struct view *view, struct line *line);
1985         /* Select line */
1986         void (*select)(struct view *view, struct line *line);
1987         /* Prepare view for loading */
1988         bool (*prepare)(struct view *view);
1989 };
1991 static struct view_ops blame_ops;
1992 static struct view_ops blob_ops;
1993 static struct view_ops diff_ops;
1994 static struct view_ops help_ops;
1995 static struct view_ops log_ops;
1996 static struct view_ops main_ops;
1997 static struct view_ops pager_ops;
1998 static struct view_ops stage_ops;
1999 static struct view_ops status_ops;
2000 static struct view_ops tree_ops;
2001 static struct view_ops branch_ops;
2003 #define VIEW_STR(name, env, ref, ops, map, git) \
2004         { name, #env, ref, ops, map, git }
2006 #define VIEW_(id, name, ops, git, ref) \
2007         VIEW_STR(name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
2010 static struct view views[] = {
2011         VIEW_(MAIN,   "main",   &main_ops,   TRUE,  ref_head),
2012         VIEW_(DIFF,   "diff",   &diff_ops,   TRUE,  ref_commit),
2013         VIEW_(LOG,    "log",    &log_ops,    TRUE,  ref_head),
2014         VIEW_(TREE,   "tree",   &tree_ops,   TRUE,  ref_commit),
2015         VIEW_(BLOB,   "blob",   &blob_ops,   TRUE,  ref_blob),
2016         VIEW_(BLAME,  "blame",  &blame_ops,  TRUE,  ref_commit),
2017         VIEW_(BRANCH, "branch", &branch_ops, TRUE,  ref_head),
2018         VIEW_(HELP,   "help",   &help_ops,   FALSE, ""),
2019         VIEW_(PAGER,  "pager",  &pager_ops,  FALSE, "stdin"),
2020         VIEW_(STATUS, "status", &status_ops, TRUE,  ""),
2021         VIEW_(STAGE,  "stage",  &stage_ops,  TRUE,  ""),
2022 };
2024 #define VIEW(req)       (&views[(req) - REQ_OFFSET - 1])
2025 #define VIEW_REQ(view)  ((view) - views + REQ_OFFSET + 1)
2027 #define foreach_view(view, i) \
2028         for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
2030 #define view_is_displayed(view) \
2031         (view == display[0] || view == display[1])
2034 enum line_graphic {
2035         LINE_GRAPHIC_VLINE
2036 };
2038 static chtype line_graphics[] = {
2039         /* LINE_GRAPHIC_VLINE: */ '|'
2040 };
2042 static inline void
2043 set_view_attr(struct view *view, enum line_type type)
2045         if (!view->curline->selected && view->curtype != type) {
2046                 wattrset(view->win, get_line_attr(type));
2047                 wchgat(view->win, -1, 0, type, NULL);
2048                 view->curtype = type;
2049         }
2052 static int
2053 draw_chars(struct view *view, enum line_type type, const char *string,
2054            int max_len, bool use_tilde)
2056         int len = 0;
2057         int col = 0;
2058         int trimmed = FALSE;
2059         size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2061         if (max_len <= 0)
2062                 return 0;
2064         if (opt_utf8) {
2065                 len = utf8_length(&string, skip, &col, max_len, &trimmed, use_tilde);
2066         } else {
2067                 col = len = strlen(string);
2068                 if (len > max_len) {
2069                         if (use_tilde) {
2070                                 max_len -= 1;
2071                         }
2072                         col = len = max_len;
2073                         trimmed = TRUE;
2074                 }
2075         }
2077         set_view_attr(view, type);
2078         if (len > 0)
2079                 waddnstr(view->win, string, len);
2080         if (trimmed && use_tilde) {
2081                 set_view_attr(view, LINE_DELIMITER);
2082                 waddch(view->win, '~');
2083                 col++;
2084         }
2086         return col;
2089 static int
2090 draw_space(struct view *view, enum line_type type, int max, int spaces)
2092         static char space[] = "                    ";
2093         int col = 0;
2095         spaces = MIN(max, spaces);
2097         while (spaces > 0) {
2098                 int len = MIN(spaces, sizeof(space) - 1);
2100                 col += draw_chars(view, type, space, len, FALSE);
2101                 spaces -= len;
2102         }
2104         return col;
2107 static bool
2108 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
2110         view->col += draw_chars(view, type, string, view->width + view->yoffset - view->col, trim);
2111         return view->width + view->yoffset <= view->col;
2114 static bool
2115 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
2117         size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2118         int max = view->width + view->yoffset - view->col;
2119         int i;
2121         if (max < size)
2122                 size = max;
2124         set_view_attr(view, type);
2125         /* Using waddch() instead of waddnstr() ensures that
2126          * they'll be rendered correctly for the cursor line. */
2127         for (i = skip; i < size; i++)
2128                 waddch(view->win, graphic[i]);
2130         view->col += size;
2131         if (size < max && skip <= size)
2132                 waddch(view->win, ' ');
2133         view->col++;
2135         return view->width + view->yoffset <= view->col;
2138 static bool
2139 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
2141         int max = MIN(view->width + view->yoffset - view->col, len);
2142         int col;
2144         if (text)
2145                 col = draw_chars(view, type, text, max - 1, trim);
2146         else
2147                 col = draw_space(view, type, max - 1, max - 1);
2149         view->col += col;
2150         view->col += draw_space(view, LINE_DEFAULT, max - col, max - col);
2151         return view->width + view->yoffset <= view->col;
2154 static bool
2155 draw_date(struct view *view, time_t *time)
2157         const char *date = time ? mkdate(time) : "";
2158         int cols = opt_date == DATE_SHORT ? DATE_SHORT_COLS : DATE_COLS;
2160         return draw_field(view, LINE_DATE, date, cols, FALSE);
2163 static bool
2164 draw_author(struct view *view, const char *author)
2166         bool trim = opt_author_cols == 0 || opt_author_cols > 5 || !author;
2168         if (!trim) {
2169                 static char initials[10];
2170                 size_t pos;
2172 #define is_initial_sep(c) (isspace(c) || ispunct(c) || (c) == '@')
2174                 memset(initials, 0, sizeof(initials));
2175                 for (pos = 0; *author && pos < opt_author_cols - 1; author++, pos++) {
2176                         while (is_initial_sep(*author))
2177                                 author++;
2178                         strncpy(&initials[pos], author, sizeof(initials) - 1 - pos);
2179                         while (*author && !is_initial_sep(author[1]))
2180                                 author++;
2181                 }
2183                 author = initials;
2184         }
2186         return draw_field(view, LINE_AUTHOR, author, opt_author_cols, trim);
2189 static bool
2190 draw_mode(struct view *view, mode_t mode)
2192         const char *str;
2194         if (S_ISDIR(mode))
2195                 str = "drwxr-xr-x";
2196         else if (S_ISLNK(mode))
2197                 str = "lrwxrwxrwx";
2198         else if (S_ISGITLINK(mode))
2199                 str = "m---------";
2200         else if (S_ISREG(mode) && mode & S_IXUSR)
2201                 str = "-rwxr-xr-x";
2202         else if (S_ISREG(mode))
2203                 str = "-rw-r--r--";
2204         else
2205                 str = "----------";
2207         return draw_field(view, LINE_MODE, str, STRING_SIZE("-rw-r--r-- "), FALSE);
2210 static bool
2211 draw_lineno(struct view *view, unsigned int lineno)
2213         char number[10];
2214         int digits3 = view->digits < 3 ? 3 : view->digits;
2215         int max = MIN(view->width + view->yoffset - view->col, digits3);
2216         char *text = NULL;
2218         lineno += view->offset + 1;
2219         if (lineno == 1 || (lineno % opt_num_interval) == 0) {
2220                 static char fmt[] = "%1ld";
2222                 fmt[1] = '0' + (view->digits <= 9 ? digits3 : 1);
2223                 if (string_format(number, fmt, lineno))
2224                         text = number;
2225         }
2226         if (text)
2227                 view->col += draw_chars(view, LINE_LINE_NUMBER, text, max, TRUE);
2228         else
2229                 view->col += draw_space(view, LINE_LINE_NUMBER, max, digits3);
2230         return draw_graphic(view, LINE_DEFAULT, &line_graphics[LINE_GRAPHIC_VLINE], 1);
2233 static bool
2234 draw_view_line(struct view *view, unsigned int lineno)
2236         struct line *line;
2237         bool selected = (view->offset + lineno == view->lineno);
2239         assert(view_is_displayed(view));
2241         if (view->offset + lineno >= view->lines)
2242                 return FALSE;
2244         line = &view->line[view->offset + lineno];
2246         wmove(view->win, lineno, 0);
2247         if (line->cleareol)
2248                 wclrtoeol(view->win);
2249         view->col = 0;
2250         view->curline = line;
2251         view->curtype = LINE_NONE;
2252         line->selected = FALSE;
2253         line->dirty = line->cleareol = 0;
2255         if (selected) {
2256                 set_view_attr(view, LINE_CURSOR);
2257                 line->selected = TRUE;
2258                 view->ops->select(view, line);
2259         }
2261         return view->ops->draw(view, line, lineno);
2264 static void
2265 redraw_view_dirty(struct view *view)
2267         bool dirty = FALSE;
2268         int lineno;
2270         for (lineno = 0; lineno < view->height; lineno++) {
2271                 if (view->offset + lineno >= view->lines)
2272                         break;
2273                 if (!view->line[view->offset + lineno].dirty)
2274                         continue;
2275                 dirty = TRUE;
2276                 if (!draw_view_line(view, lineno))
2277                         break;
2278         }
2280         if (!dirty)
2281                 return;
2282         wnoutrefresh(view->win);
2285 static void
2286 redraw_view_from(struct view *view, int lineno)
2288         assert(0 <= lineno && lineno < view->height);
2290         for (; lineno < view->height; lineno++) {
2291                 if (!draw_view_line(view, lineno))
2292                         break;
2293         }
2295         wnoutrefresh(view->win);
2298 static void
2299 redraw_view(struct view *view)
2301         werase(view->win);
2302         redraw_view_from(view, 0);
2306 static void
2307 update_view_title(struct view *view)
2309         char buf[SIZEOF_STR];
2310         char state[SIZEOF_STR];
2311         size_t bufpos = 0, statelen = 0;
2313         assert(view_is_displayed(view));
2315         if (view != VIEW(REQ_VIEW_STATUS) && view->lines) {
2316                 unsigned int view_lines = view->offset + view->height;
2317                 unsigned int lines = view->lines
2318                                    ? MIN(view_lines, view->lines) * 100 / view->lines
2319                                    : 0;
2321                 string_format_from(state, &statelen, " - %s %d of %d (%d%%)",
2322                                    view->ops->type,
2323                                    view->lineno + 1,
2324                                    view->lines,
2325                                    lines);
2327         }
2329         if (view->pipe) {
2330                 time_t secs = time(NULL) - view->start_time;
2332                 /* Three git seconds are a long time ... */
2333                 if (secs > 2)
2334                         string_format_from(state, &statelen, " loading %lds", secs);
2335         }
2337         string_format_from(buf, &bufpos, "[%s]", view->name);
2338         if (*view->ref && bufpos < view->width) {
2339                 size_t refsize = strlen(view->ref);
2340                 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2342                 if (minsize < view->width)
2343                         refsize = view->width - minsize + 7;
2344                 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2345         }
2347         if (statelen && bufpos < view->width) {
2348                 string_format_from(buf, &bufpos, "%s", state);
2349         }
2351         if (view == display[current_view])
2352                 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2353         else
2354                 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2356         mvwaddnstr(view->title, 0, 0, buf, bufpos);
2357         wclrtoeol(view->title);
2358         wnoutrefresh(view->title);
2361 static int
2362 apply_step(double step, int value)
2364         if (step >= 1)
2365                 return (int) step;
2366         value *= step + 0.01;
2367         return value ? value : 1;
2370 static void
2371 resize_display(void)
2373         int offset, i;
2374         struct view *base = display[0];
2375         struct view *view = display[1] ? display[1] : display[0];
2377         /* Setup window dimensions */
2379         getmaxyx(stdscr, base->height, base->width);
2381         /* Make room for the status window. */
2382         base->height -= 1;
2384         if (view != base) {
2385                 /* Horizontal split. */
2386                 view->width   = base->width;
2387                 view->height  = apply_step(opt_scale_split_view, base->height);
2388                 view->height  = MAX(view->height, MIN_VIEW_HEIGHT);
2389                 view->height  = MIN(view->height, base->height - MIN_VIEW_HEIGHT);
2390                 base->height -= view->height;
2392                 /* Make room for the title bar. */
2393                 view->height -= 1;
2394         }
2396         /* Make room for the title bar. */
2397         base->height -= 1;
2399         offset = 0;
2401         foreach_displayed_view (view, i) {
2402                 if (!view->win) {
2403                         view->win = newwin(view->height, 0, offset, 0);
2404                         if (!view->win)
2405                                 die("Failed to create %s view", view->name);
2407                         scrollok(view->win, FALSE);
2409                         view->title = newwin(1, 0, offset + view->height, 0);
2410                         if (!view->title)
2411                                 die("Failed to create title window");
2413                 } else {
2414                         wresize(view->win, view->height, view->width);
2415                         mvwin(view->win,   offset, 0);
2416                         mvwin(view->title, offset + view->height, 0);
2417                 }
2419                 offset += view->height + 1;
2420         }
2423 static void
2424 redraw_display(bool clear)
2426         struct view *view;
2427         int i;
2429         foreach_displayed_view (view, i) {
2430                 if (clear)
2431                         wclear(view->win);
2432                 redraw_view(view);
2433                 update_view_title(view);
2434         }
2437 static void
2438 toggle_date_option(enum date *date)
2440         static const char *help[] = {
2441                 "no",
2442                 "default",
2443                 "relative",
2444                 "short"
2445         };
2447         opt_date = (opt_date + 1) % ARRAY_SIZE(help);
2448         redraw_display(FALSE);
2449         report("Displaying %s dates", help[opt_date]);
2452 static void
2453 toggle_view_option(bool *option, const char *help)
2455         *option = !*option;
2456         redraw_display(FALSE);
2457         report("%sabling %s", *option ? "En" : "Dis", help);
2460 static void
2461 open_option_menu(void)
2463         const struct menu_item menu[] = {
2464                 { '.', "line numbers", &opt_line_number },
2465                 { 'D', "date display", &opt_date },
2466                 { 'A', "author display", &opt_author },
2467                 { 'g', "revision graph display", &opt_rev_graph },
2468                 { 'F', "reference display", &opt_show_refs },
2469                 { 0 }
2470         };
2471         int selected = 0;
2473         if (prompt_menu("Toggle option", menu, &selected)) {
2474                 if (menu[selected].data == &opt_date)
2475                         toggle_date_option(menu[selected].data);
2476                 else
2477                         toggle_view_option(menu[selected].data, menu[selected].text);
2478         }
2481 static void
2482 maximize_view(struct view *view)
2484         memset(display, 0, sizeof(display));
2485         current_view = 0;
2486         display[current_view] = view;
2487         resize_display();
2488         redraw_display(FALSE);
2489         report("");
2493 /*
2494  * Navigation
2495  */
2497 static bool
2498 goto_view_line(struct view *view, unsigned long offset, unsigned long lineno)
2500         if (lineno >= view->lines)
2501                 lineno = view->lines > 0 ? view->lines - 1 : 0;
2503         if (offset > lineno || offset + view->height <= lineno) {
2504                 unsigned long half = view->height / 2;
2506                 if (lineno > half)
2507                         offset = lineno - half;
2508                 else
2509                         offset = 0;
2510         }
2512         if (offset != view->offset || lineno != view->lineno) {
2513                 view->offset = offset;
2514                 view->lineno = lineno;
2515                 return TRUE;
2516         }
2518         return FALSE;
2521 /* Scrolling backend */
2522 static void
2523 do_scroll_view(struct view *view, int lines)
2525         bool redraw_current_line = FALSE;
2527         /* The rendering expects the new offset. */
2528         view->offset += lines;
2530         assert(0 <= view->offset && view->offset < view->lines);
2531         assert(lines);
2533         /* Move current line into the view. */
2534         if (view->lineno < view->offset) {
2535                 view->lineno = view->offset;
2536                 redraw_current_line = TRUE;
2537         } else if (view->lineno >= view->offset + view->height) {
2538                 view->lineno = view->offset + view->height - 1;
2539                 redraw_current_line = TRUE;
2540         }
2542         assert(view->offset <= view->lineno && view->lineno < view->lines);
2544         /* Redraw the whole screen if scrolling is pointless. */
2545         if (view->height < ABS(lines)) {
2546                 redraw_view(view);
2548         } else {
2549                 int line = lines > 0 ? view->height - lines : 0;
2550                 int end = line + ABS(lines);
2552                 scrollok(view->win, TRUE);
2553                 wscrl(view->win, lines);
2554                 scrollok(view->win, FALSE);
2556                 while (line < end && draw_view_line(view, line))
2557                         line++;
2559                 if (redraw_current_line)
2560                         draw_view_line(view, view->lineno - view->offset);
2561                 wnoutrefresh(view->win);
2562         }
2564         view->has_scrolled = TRUE;
2565         report("");
2568 /* Scroll frontend */
2569 static void
2570 scroll_view(struct view *view, enum request request)
2572         int lines = 1;
2574         assert(view_is_displayed(view));
2576         switch (request) {
2577         case REQ_SCROLL_LEFT:
2578                 if (view->yoffset == 0) {
2579                         report("Cannot scroll beyond the first column");
2580                         return;
2581                 }
2582                 if (view->yoffset <= apply_step(opt_hscroll, view->width))
2583                         view->yoffset = 0;
2584                 else
2585                         view->yoffset -= apply_step(opt_hscroll, view->width);
2586                 redraw_view_from(view, 0);
2587                 report("");
2588                 return;
2589         case REQ_SCROLL_RIGHT:
2590                 view->yoffset += apply_step(opt_hscroll, view->width);
2591                 redraw_view(view);
2592                 report("");
2593                 return;
2594         case REQ_SCROLL_PAGE_DOWN:
2595                 lines = view->height;
2596         case REQ_SCROLL_LINE_DOWN:
2597                 if (view->offset + lines > view->lines)
2598                         lines = view->lines - view->offset;
2600                 if (lines == 0 || view->offset + view->height >= view->lines) {
2601                         report("Cannot scroll beyond the last line");
2602                         return;
2603                 }
2604                 break;
2606         case REQ_SCROLL_PAGE_UP:
2607                 lines = view->height;
2608         case REQ_SCROLL_LINE_UP:
2609                 if (lines > view->offset)
2610                         lines = view->offset;
2612                 if (lines == 0) {
2613                         report("Cannot scroll beyond the first line");
2614                         return;
2615                 }
2617                 lines = -lines;
2618                 break;
2620         default:
2621                 die("request %d not handled in switch", request);
2622         }
2624         do_scroll_view(view, lines);
2627 /* Cursor moving */
2628 static void
2629 move_view(struct view *view, enum request request)
2631         int scroll_steps = 0;
2632         int steps;
2634         switch (request) {
2635         case REQ_MOVE_FIRST_LINE:
2636                 steps = -view->lineno;
2637                 break;
2639         case REQ_MOVE_LAST_LINE:
2640                 steps = view->lines - view->lineno - 1;
2641                 break;
2643         case REQ_MOVE_PAGE_UP:
2644                 steps = view->height > view->lineno
2645                       ? -view->lineno : -view->height;
2646                 break;
2648         case REQ_MOVE_PAGE_DOWN:
2649                 steps = view->lineno + view->height >= view->lines
2650                       ? view->lines - view->lineno - 1 : view->height;
2651                 break;
2653         case REQ_MOVE_UP:
2654                 steps = -1;
2655                 break;
2657         case REQ_MOVE_DOWN:
2658                 steps = 1;
2659                 break;
2661         default:
2662                 die("request %d not handled in switch", request);
2663         }
2665         if (steps <= 0 && view->lineno == 0) {
2666                 report("Cannot move beyond the first line");
2667                 return;
2669         } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2670                 report("Cannot move beyond the last line");
2671                 return;
2672         }
2674         /* Move the current line */
2675         view->lineno += steps;
2676         assert(0 <= view->lineno && view->lineno < view->lines);
2678         /* Check whether the view needs to be scrolled */
2679         if (view->lineno < view->offset ||
2680             view->lineno >= view->offset + view->height) {
2681                 scroll_steps = steps;
2682                 if (steps < 0 && -steps > view->offset) {
2683                         scroll_steps = -view->offset;
2685                 } else if (steps > 0) {
2686                         if (view->lineno == view->lines - 1 &&
2687                             view->lines > view->height) {
2688                                 scroll_steps = view->lines - view->offset - 1;
2689                                 if (scroll_steps >= view->height)
2690                                         scroll_steps -= view->height - 1;
2691                         }
2692                 }
2693         }
2695         if (!view_is_displayed(view)) {
2696                 view->offset += scroll_steps;
2697                 assert(0 <= view->offset && view->offset < view->lines);
2698                 view->ops->select(view, &view->line[view->lineno]);
2699                 return;
2700         }
2702         /* Repaint the old "current" line if we be scrolling */
2703         if (ABS(steps) < view->height)
2704                 draw_view_line(view, view->lineno - steps - view->offset);
2706         if (scroll_steps) {
2707                 do_scroll_view(view, scroll_steps);
2708                 return;
2709         }
2711         /* Draw the current line */
2712         draw_view_line(view, view->lineno - view->offset);
2714         wnoutrefresh(view->win);
2715         report("");
2719 /*
2720  * Searching
2721  */
2723 static void search_view(struct view *view, enum request request);
2725 static bool
2726 grep_text(struct view *view, const char *text[])
2728         regmatch_t pmatch;
2729         size_t i;
2731         for (i = 0; text[i]; i++)
2732                 if (*text[i] &&
2733                     regexec(view->regex, text[i], 1, &pmatch, 0) != REG_NOMATCH)
2734                         return TRUE;
2735         return FALSE;
2738 static void
2739 select_view_line(struct view *view, unsigned long lineno)
2741         unsigned long old_lineno = view->lineno;
2742         unsigned long old_offset = view->offset;
2744         if (goto_view_line(view, view->offset, lineno)) {
2745                 if (view_is_displayed(view)) {
2746                         if (old_offset != view->offset) {
2747                                 redraw_view(view);
2748                         } else {
2749                                 draw_view_line(view, old_lineno - view->offset);
2750                                 draw_view_line(view, view->lineno - view->offset);
2751                                 wnoutrefresh(view->win);
2752                         }
2753                 } else {
2754                         view->ops->select(view, &view->line[view->lineno]);
2755                 }
2756         }
2759 static void
2760 find_next(struct view *view, enum request request)
2762         unsigned long lineno = view->lineno;
2763         int direction;
2765         if (!*view->grep) {
2766                 if (!*opt_search)
2767                         report("No previous search");
2768                 else
2769                         search_view(view, request);
2770                 return;
2771         }
2773         switch (request) {
2774         case REQ_SEARCH:
2775         case REQ_FIND_NEXT:
2776                 direction = 1;
2777                 break;
2779         case REQ_SEARCH_BACK:
2780         case REQ_FIND_PREV:
2781                 direction = -1;
2782                 break;
2784         default:
2785                 return;
2786         }
2788         if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
2789                 lineno += direction;
2791         /* Note, lineno is unsigned long so will wrap around in which case it
2792          * will become bigger than view->lines. */
2793         for (; lineno < view->lines; lineno += direction) {
2794                 if (view->ops->grep(view, &view->line[lineno])) {
2795                         select_view_line(view, lineno);
2796                         report("Line %ld matches '%s'", lineno + 1, view->grep);
2797                         return;
2798                 }
2799         }
2801         report("No match found for '%s'", view->grep);
2804 static void
2805 search_view(struct view *view, enum request request)
2807         int regex_err;
2809         if (view->regex) {
2810                 regfree(view->regex);
2811                 *view->grep = 0;
2812         } else {
2813                 view->regex = calloc(1, sizeof(*view->regex));
2814                 if (!view->regex)
2815                         return;
2816         }
2818         regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
2819         if (regex_err != 0) {
2820                 char buf[SIZEOF_STR] = "unknown error";
2822                 regerror(regex_err, view->regex, buf, sizeof(buf));
2823                 report("Search failed: %s", buf);
2824                 return;
2825         }
2827         string_copy(view->grep, opt_search);
2829         find_next(view, request);
2832 /*
2833  * Incremental updating
2834  */
2836 static void
2837 reset_view(struct view *view)
2839         int i;
2841         for (i = 0; i < view->lines; i++)
2842                 free(view->line[i].data);
2843         free(view->line);
2845         view->p_offset = view->offset;
2846         view->p_yoffset = view->yoffset;
2847         view->p_lineno = view->lineno;
2849         view->line = NULL;
2850         view->offset = 0;
2851         view->yoffset = 0;
2852         view->lines  = 0;
2853         view->lineno = 0;
2854         view->vid[0] = 0;
2855         view->update_secs = 0;
2858 static void
2859 free_argv(const char *argv[])
2861         int argc;
2863         for (argc = 0; argv[argc]; argc++)
2864                 free((void *) argv[argc]);
2867 static bool
2868 format_argv(const char *dst_argv[], const char *src_argv[], enum format_flags flags)
2870         char buf[SIZEOF_STR];
2871         int argc;
2872         bool noreplace = flags == FORMAT_NONE;
2874         free_argv(dst_argv);
2876         for (argc = 0; src_argv[argc]; argc++) {
2877                 const char *arg = src_argv[argc];
2878                 size_t bufpos = 0;
2880                 while (arg) {
2881                         char *next = strstr(arg, "%(");
2882                         int len = next - arg;
2883                         const char *value;
2885                         if (!next || noreplace) {
2886                                 if (flags == FORMAT_DASH && !strcmp(arg, "--"))
2887                                         noreplace = TRUE;
2888                                 len = strlen(arg);
2889                                 value = "";
2891                         } else if (!prefixcmp(next, "%(directory)")) {
2892                                 value = opt_path;
2894                         } else if (!prefixcmp(next, "%(file)")) {
2895                                 value = opt_file;
2897                         } else if (!prefixcmp(next, "%(ref)")) {
2898                                 value = *opt_ref ? opt_ref : "HEAD";
2900                         } else if (!prefixcmp(next, "%(head)")) {
2901                                 value = ref_head;
2903                         } else if (!prefixcmp(next, "%(commit)")) {
2904                                 value = ref_commit;
2906                         } else if (!prefixcmp(next, "%(blob)")) {
2907                                 value = ref_blob;
2909                         } else {
2910                                 report("Unknown replacement: `%s`", next);
2911                                 return FALSE;
2912                         }
2914                         if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
2915                                 return FALSE;
2917                         arg = next && !noreplace ? strchr(next, ')') + 1 : NULL;
2918                 }
2920                 dst_argv[argc] = strdup(buf);
2921                 if (!dst_argv[argc])
2922                         break;
2923         }
2925         dst_argv[argc] = NULL;
2927         return src_argv[argc] == NULL;
2930 static bool
2931 restore_view_position(struct view *view)
2933         if (!view->p_restore || (view->pipe && view->lines <= view->p_lineno))
2934                 return FALSE;
2936         /* Changing the view position cancels the restoring. */
2937         /* FIXME: Changing back to the first line is not detected. */
2938         if (view->offset != 0 || view->lineno != 0) {
2939                 view->p_restore = FALSE;
2940                 return FALSE;
2941         }
2943         if (goto_view_line(view, view->p_offset, view->p_lineno) &&
2944             view_is_displayed(view))
2945                 werase(view->win);
2947         view->yoffset = view->p_yoffset;
2948         view->p_restore = FALSE;
2950         return TRUE;
2953 static void
2954 end_update(struct view *view, bool force)
2956         if (!view->pipe)
2957                 return;
2958         while (!view->ops->read(view, NULL))
2959                 if (!force)
2960                         return;
2961         set_nonblocking_input(FALSE);
2962         if (force)
2963                 kill_io(view->pipe);
2964         done_io(view->pipe);
2965         view->pipe = NULL;
2968 static void
2969 setup_update(struct view *view, const char *vid)
2971         set_nonblocking_input(TRUE);
2972         reset_view(view);
2973         string_copy_rev(view->vid, vid);
2974         view->pipe = &view->io;
2975         view->start_time = time(NULL);
2978 static bool
2979 prepare_update(struct view *view, const char *argv[], const char *dir,
2980                enum format_flags flags)
2982         if (view->pipe)
2983                 end_update(view, TRUE);
2984         return init_io_rd(&view->io, argv, dir, flags);
2987 static bool
2988 prepare_update_file(struct view *view, const char *name)
2990         if (view->pipe)
2991                 end_update(view, TRUE);
2992         return io_open(&view->io, name);
2995 static bool
2996 begin_update(struct view *view, bool refresh)
2998         if (view->pipe)
2999                 end_update(view, TRUE);
3001         if (!refresh) {
3002                 if (view->ops->prepare) {
3003                         if (!view->ops->prepare(view))
3004                                 return FALSE;
3005                 } else if (!init_io_rd(&view->io, view->ops->argv, NULL, FORMAT_ALL)) {
3006                         return FALSE;
3007                 }
3009                 /* Put the current ref_* value to the view title ref
3010                  * member. This is needed by the blob view. Most other
3011                  * views sets it automatically after loading because the
3012                  * first line is a commit line. */
3013                 string_copy_rev(view->ref, view->id);
3014         }
3016         if (!start_io(&view->io))
3017                 return FALSE;
3019         setup_update(view, view->id);
3021         return TRUE;
3024 static bool
3025 update_view(struct view *view)
3027         char out_buffer[BUFSIZ * 2];
3028         char *line;
3029         /* Clear the view and redraw everything since the tree sorting
3030          * might have rearranged things. */
3031         bool redraw = view->lines == 0;
3032         bool can_read = TRUE;
3034         if (!view->pipe)
3035                 return TRUE;
3037         if (!io_can_read(view->pipe)) {
3038                 if (view->lines == 0 && view_is_displayed(view)) {
3039                         time_t secs = time(NULL) - view->start_time;
3041                         if (secs > 1 && secs > view->update_secs) {
3042                                 if (view->update_secs == 0)
3043                                         redraw_view(view);
3044                                 update_view_title(view);
3045                                 view->update_secs = secs;
3046                         }
3047                 }
3048                 return TRUE;
3049         }
3051         for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
3052                 if (opt_iconv != ICONV_NONE) {
3053                         ICONV_CONST char *inbuf = line;
3054                         size_t inlen = strlen(line) + 1;
3056                         char *outbuf = out_buffer;
3057                         size_t outlen = sizeof(out_buffer);
3059                         size_t ret;
3061                         ret = iconv(opt_iconv, &inbuf, &inlen, &outbuf, &outlen);
3062                         if (ret != (size_t) -1)
3063                                 line = out_buffer;
3064                 }
3066                 if (!view->ops->read(view, line)) {
3067                         report("Allocation failure");
3068                         end_update(view, TRUE);
3069                         return FALSE;
3070                 }
3071         }
3073         {
3074                 unsigned long lines = view->lines;
3075                 int digits;
3077                 for (digits = 0; lines; digits++)
3078                         lines /= 10;
3080                 /* Keep the displayed view in sync with line number scaling. */
3081                 if (digits != view->digits) {
3082                         view->digits = digits;
3083                         if (opt_line_number || view == VIEW(REQ_VIEW_BLAME))
3084                                 redraw = TRUE;
3085                 }
3086         }
3088         if (io_error(view->pipe)) {
3089                 report("Failed to read: %s", io_strerror(view->pipe));
3090                 end_update(view, TRUE);
3092         } else if (io_eof(view->pipe)) {
3093                 report("");
3094                 end_update(view, FALSE);
3095         }
3097         if (restore_view_position(view))
3098                 redraw = TRUE;
3100         if (!view_is_displayed(view))
3101                 return TRUE;
3103         if (redraw)
3104                 redraw_view_from(view, 0);
3105         else
3106                 redraw_view_dirty(view);
3108         /* Update the title _after_ the redraw so that if the redraw picks up a
3109          * commit reference in view->ref it'll be available here. */
3110         update_view_title(view);
3111         return TRUE;
3114 DEFINE_ALLOCATOR(realloc_lines, struct line, 256)
3116 static struct line *
3117 add_line_data(struct view *view, void *data, enum line_type type)
3119         struct line *line;
3121         if (!realloc_lines(&view->line, view->lines, 1))
3122                 return NULL;
3124         line = &view->line[view->lines++];
3125         memset(line, 0, sizeof(*line));
3126         line->type = type;
3127         line->data = data;
3128         line->dirty = 1;
3130         return line;
3133 static struct line *
3134 add_line_text(struct view *view, const char *text, enum line_type type)
3136         char *data = text ? strdup(text) : NULL;
3138         return data ? add_line_data(view, data, type) : NULL;
3141 static struct line *
3142 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
3144         char buf[SIZEOF_STR];
3145         va_list args;
3147         va_start(args, fmt);
3148         if (vsnprintf(buf, sizeof(buf), fmt, args) >= sizeof(buf))
3149                 buf[0] = 0;
3150         va_end(args);
3152         return buf[0] ? add_line_text(view, buf, type) : NULL;
3155 /*
3156  * View opening
3157  */
3159 enum open_flags {
3160         OPEN_DEFAULT = 0,       /* Use default view switching. */
3161         OPEN_SPLIT = 1,         /* Split current view. */
3162         OPEN_RELOAD = 4,        /* Reload view even if it is the current. */
3163         OPEN_REFRESH = 16,      /* Refresh view using previous command. */
3164         OPEN_PREPARED = 32,     /* Open already prepared command. */
3165 };
3167 static void
3168 open_view(struct view *prev, enum request request, enum open_flags flags)
3170         bool split = !!(flags & OPEN_SPLIT);
3171         bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
3172         bool nomaximize = !!(flags & OPEN_REFRESH);
3173         struct view *view = VIEW(request);
3174         int nviews = displayed_views();
3175         struct view *base_view = display[0];
3177         if (view == prev && nviews == 1 && !reload) {
3178                 report("Already in %s view", view->name);
3179                 return;
3180         }
3182         if (view->git_dir && !opt_git_dir[0]) {
3183                 report("The %s view is disabled in pager view", view->name);
3184                 return;
3185         }
3187         if (split) {
3188                 display[1] = view;
3189                 current_view = 1;
3190         } else if (!nomaximize) {
3191                 /* Maximize the current view. */
3192                 memset(display, 0, sizeof(display));
3193                 current_view = 0;
3194                 display[current_view] = view;
3195         }
3197         /* No parent signals that this is the first loaded view. */
3198         if (prev && view != prev) {
3199                 view->parent = prev;
3200         }
3202         /* Resize the view when switching between split- and full-screen,
3203          * or when switching between two different full-screen views. */
3204         if (nviews != displayed_views() ||
3205             (nviews == 1 && base_view != display[0]))
3206                 resize_display();
3208         if (view->ops->open) {
3209                 if (view->pipe)
3210                         end_update(view, TRUE);
3211                 if (!view->ops->open(view)) {
3212                         report("Failed to load %s view", view->name);
3213                         return;
3214                 }
3215                 restore_view_position(view);
3217         } else if ((reload || strcmp(view->vid, view->id)) &&
3218                    !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
3219                 report("Failed to load %s view", view->name);
3220                 return;
3221         }
3223         if (split && prev->lineno - prev->offset >= prev->height) {
3224                 /* Take the title line into account. */
3225                 int lines = prev->lineno - prev->offset - prev->height + 1;
3227                 /* Scroll the view that was split if the current line is
3228                  * outside the new limited view. */
3229                 do_scroll_view(prev, lines);
3230         }
3232         if (prev && view != prev) {
3233                 if (split) {
3234                         /* "Blur" the previous view. */
3235                         update_view_title(prev);
3236                 }
3237         }
3239         if (view->pipe && view->lines == 0) {
3240                 /* Clear the old view and let the incremental updating refill
3241                  * the screen. */
3242                 werase(view->win);
3243                 view->p_restore = flags & (OPEN_RELOAD | OPEN_REFRESH);
3244                 report("");
3245         } else if (view_is_displayed(view)) {
3246                 redraw_view(view);
3247                 report("");
3248         }
3251 static void
3252 open_external_viewer(const char *argv[], const char *dir)
3254         def_prog_mode();           /* save current tty modes */
3255         endwin();                  /* restore original tty modes */
3256         run_io_fg(argv, dir);
3257         fprintf(stderr, "Press Enter to continue");
3258         getc(opt_tty);
3259         reset_prog_mode();
3260         redraw_display(TRUE);
3263 static void
3264 open_mergetool(const char *file)
3266         const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
3268         open_external_viewer(mergetool_argv, opt_cdup);
3271 static void
3272 open_editor(bool from_root, const char *file)
3274         const char *editor_argv[] = { "vi", file, NULL };
3275         const char *editor;
3277         editor = getenv("GIT_EDITOR");
3278         if (!editor && *opt_editor)
3279                 editor = opt_editor;
3280         if (!editor)
3281                 editor = getenv("VISUAL");
3282         if (!editor)
3283                 editor = getenv("EDITOR");
3284         if (!editor)
3285                 editor = "vi";
3287         editor_argv[0] = editor;
3288         open_external_viewer(editor_argv, from_root ? opt_cdup : NULL);
3291 static void
3292 open_run_request(enum request request)
3294         struct run_request *req = get_run_request(request);
3295         const char *argv[ARRAY_SIZE(req->argv)] = { NULL };
3297         if (!req) {
3298                 report("Unknown run request");
3299                 return;
3300         }
3302         if (format_argv(argv, req->argv, FORMAT_ALL))
3303                 open_external_viewer(argv, NULL);
3304         free_argv(argv);
3307 /*
3308  * User request switch noodle
3309  */
3311 static int
3312 view_driver(struct view *view, enum request request)
3314         int i;
3316         if (request == REQ_NONE)
3317                 return TRUE;
3319         if (request > REQ_NONE) {
3320                 open_run_request(request);
3321                 /* FIXME: When all views can refresh always do this. */
3322                 if (view == VIEW(REQ_VIEW_STATUS) ||
3323                     view == VIEW(REQ_VIEW_MAIN) ||
3324                     view == VIEW(REQ_VIEW_LOG) ||
3325                     view == VIEW(REQ_VIEW_BRANCH) ||
3326                     view == VIEW(REQ_VIEW_STAGE))
3327                         request = REQ_REFRESH;
3328                 else
3329                         return TRUE;
3330         }
3332         if (view && view->lines) {
3333                 request = view->ops->request(view, request, &view->line[view->lineno]);
3334                 if (request == REQ_NONE)
3335                         return TRUE;
3336         }
3338         switch (request) {
3339         case REQ_MOVE_UP:
3340         case REQ_MOVE_DOWN:
3341         case REQ_MOVE_PAGE_UP:
3342         case REQ_MOVE_PAGE_DOWN:
3343         case REQ_MOVE_FIRST_LINE:
3344         case REQ_MOVE_LAST_LINE:
3345                 move_view(view, request);
3346                 break;
3348         case REQ_SCROLL_LEFT:
3349         case REQ_SCROLL_RIGHT:
3350         case REQ_SCROLL_LINE_DOWN:
3351         case REQ_SCROLL_LINE_UP:
3352         case REQ_SCROLL_PAGE_DOWN:
3353         case REQ_SCROLL_PAGE_UP:
3354                 scroll_view(view, request);
3355                 break;
3357         case REQ_VIEW_BLAME:
3358                 if (!opt_file[0]) {
3359                         report("No file chosen, press %s to open tree view",
3360                                get_key(view->keymap, REQ_VIEW_TREE));
3361                         break;
3362                 }
3363                 open_view(view, request, OPEN_DEFAULT);
3364                 break;
3366         case REQ_VIEW_BLOB:
3367                 if (!ref_blob[0]) {
3368                         report("No file chosen, press %s to open tree view",
3369                                get_key(view->keymap, REQ_VIEW_TREE));
3370                         break;
3371                 }
3372                 open_view(view, request, OPEN_DEFAULT);
3373                 break;
3375         case REQ_VIEW_PAGER:
3376                 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3377                         report("No pager content, press %s to run command from prompt",
3378                                get_key(view->keymap, REQ_PROMPT));
3379                         break;
3380                 }
3381                 open_view(view, request, OPEN_DEFAULT);
3382                 break;
3384         case REQ_VIEW_STAGE:
3385                 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3386                         report("No stage content, press %s to open the status view and choose file",
3387                                get_key(view->keymap, REQ_VIEW_STATUS));
3388                         break;
3389                 }
3390                 open_view(view, request, OPEN_DEFAULT);
3391                 break;
3393         case REQ_VIEW_STATUS:
3394                 if (opt_is_inside_work_tree == FALSE) {
3395                         report("The status view requires a working tree");
3396                         break;
3397                 }
3398                 open_view(view, request, OPEN_DEFAULT);
3399                 break;
3401         case REQ_VIEW_MAIN:
3402         case REQ_VIEW_DIFF:
3403         case REQ_VIEW_LOG:
3404         case REQ_VIEW_TREE:
3405         case REQ_VIEW_HELP:
3406         case REQ_VIEW_BRANCH:
3407                 open_view(view, request, OPEN_DEFAULT);
3408                 break;
3410         case REQ_NEXT:
3411         case REQ_PREVIOUS:
3412                 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3414                 if ((view == VIEW(REQ_VIEW_DIFF) &&
3415                      view->parent == VIEW(REQ_VIEW_MAIN)) ||
3416                    (view == VIEW(REQ_VIEW_DIFF) &&
3417                      view->parent == VIEW(REQ_VIEW_BLAME)) ||
3418                    (view == VIEW(REQ_VIEW_STAGE) &&
3419                      view->parent == VIEW(REQ_VIEW_STATUS)) ||
3420                    (view == VIEW(REQ_VIEW_BLOB) &&
3421                      view->parent == VIEW(REQ_VIEW_TREE)) ||
3422                    (view == VIEW(REQ_VIEW_MAIN) &&
3423                      view->parent == VIEW(REQ_VIEW_BRANCH))) {
3424                         int line;
3426                         view = view->parent;
3427                         line = view->lineno;
3428                         move_view(view, request);
3429                         if (view_is_displayed(view))
3430                                 update_view_title(view);
3431                         if (line != view->lineno)
3432                                 view->ops->request(view, REQ_ENTER,
3433                                                    &view->line[view->lineno]);
3435                 } else {
3436                         move_view(view, request);
3437                 }
3438                 break;
3440         case REQ_VIEW_NEXT:
3441         {
3442                 int nviews = displayed_views();
3443                 int next_view = (current_view + 1) % nviews;
3445                 if (next_view == current_view) {
3446                         report("Only one view is displayed");
3447                         break;
3448                 }
3450                 current_view = next_view;
3451                 /* Blur out the title of the previous view. */
3452                 update_view_title(view);
3453                 report("");
3454                 break;
3455         }
3456         case REQ_REFRESH:
3457                 report("Refreshing is not yet supported for the %s view", view->name);
3458                 break;
3460         case REQ_MAXIMIZE:
3461                 if (displayed_views() == 2)
3462                         maximize_view(view);
3463                 break;
3465         case REQ_OPTIONS:
3466                 open_option_menu();
3467                 break;
3469         case REQ_TOGGLE_LINENO:
3470                 toggle_view_option(&opt_line_number, "line numbers");
3471                 break;
3473         case REQ_TOGGLE_DATE:
3474                 toggle_date_option(&opt_date);
3475                 break;
3477         case REQ_TOGGLE_AUTHOR:
3478                 toggle_view_option(&opt_author, "author display");
3479                 break;
3481         case REQ_TOGGLE_REV_GRAPH:
3482                 toggle_view_option(&opt_rev_graph, "revision graph display");
3483                 break;
3485         case REQ_TOGGLE_REFS:
3486                 toggle_view_option(&opt_show_refs, "reference display");
3487                 break;
3489         case REQ_TOGGLE_SORT_FIELD:
3490         case REQ_TOGGLE_SORT_ORDER:
3491                 report("Sorting is not yet supported for the %s view", view->name);
3492                 break;
3494         case REQ_SEARCH:
3495         case REQ_SEARCH_BACK:
3496                 search_view(view, request);
3497                 break;
3499         case REQ_FIND_NEXT:
3500         case REQ_FIND_PREV:
3501                 find_next(view, request);
3502                 break;
3504         case REQ_STOP_LOADING:
3505                 for (i = 0; i < ARRAY_SIZE(views); i++) {
3506                         view = &views[i];
3507                         if (view->pipe)
3508                                 report("Stopped loading the %s view", view->name),
3509                         end_update(view, TRUE);
3510                 }
3511                 break;
3513         case REQ_SHOW_VERSION:
3514                 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3515                 return TRUE;
3517         case REQ_SCREEN_REDRAW:
3518                 redraw_display(TRUE);
3519                 break;
3521         case REQ_EDIT:
3522                 report("Nothing to edit");
3523                 break;
3525         case REQ_ENTER:
3526                 report("Nothing to enter");
3527                 break;
3529         case REQ_VIEW_CLOSE:
3530                 /* XXX: Mark closed views by letting view->parent point to the
3531                  * view itself. Parents to closed view should never be
3532                  * followed. */
3533                 if (view->parent &&
3534                     view->parent->parent != view->parent) {
3535                         maximize_view(view->parent);
3536                         view->parent = view;
3537                         break;
3538                 }
3539                 /* Fall-through */
3540         case REQ_QUIT:
3541                 return FALSE;
3543         default:
3544                 report("Unknown key, press %s for help",
3545                        get_key(view->keymap, REQ_VIEW_HELP));
3546                 return TRUE;
3547         }
3549         return TRUE;
3553 /*
3554  * View backend utilities
3555  */
3557 enum sort_field {
3558         ORDERBY_NAME,
3559         ORDERBY_DATE,
3560         ORDERBY_AUTHOR,
3561 };
3563 struct sort_state {
3564         const enum sort_field *fields;
3565         size_t size, current;
3566         bool reverse;
3567 };
3569 #define SORT_STATE(fields) { fields, ARRAY_SIZE(fields), 0 }
3570 #define get_sort_field(state) ((state).fields[(state).current])
3571 #define sort_order(state, result) ((state).reverse ? -(result) : (result))
3573 static void
3574 sort_view(struct view *view, enum request request, struct sort_state *state,
3575           int (*compare)(const void *, const void *))
3577         switch (request) {
3578         case REQ_TOGGLE_SORT_FIELD:
3579                 state->current = (state->current + 1) % state->size;
3580                 break;
3582         case REQ_TOGGLE_SORT_ORDER:
3583                 state->reverse = !state->reverse;
3584                 break;
3585         default:
3586                 die("Not a sort request");
3587         }
3589         qsort(view->line, view->lines, sizeof(*view->line), compare);
3590         redraw_view(view);
3593 DEFINE_ALLOCATOR(realloc_authors, const char *, 256)
3595 /* Small author cache to reduce memory consumption. It uses binary
3596  * search to lookup or find place to position new entries. No entries
3597  * are ever freed. */
3598 static const char *
3599 get_author(const char *name)
3601         static const char **authors;
3602         static size_t authors_size;
3603         int from = 0, to = authors_size - 1;
3605         while (from <= to) {
3606                 size_t pos = (to + from) / 2;
3607                 int cmp = strcmp(name, authors[pos]);
3609                 if (!cmp)
3610                         return authors[pos];
3612                 if (cmp < 0)
3613                         to = pos - 1;
3614                 else
3615                         from = pos + 1;
3616         }
3618         if (!realloc_authors(&authors, authors_size, 1))
3619                 return NULL;
3620         name = strdup(name);
3621         if (!name)
3622                 return NULL;
3624         memmove(authors + from + 1, authors + from, (authors_size - from) * sizeof(*authors));
3625         authors[from] = name;
3626         authors_size++;
3628         return name;
3631 static void
3632 parse_timezone(time_t *time, const char *zone)
3634         long tz;
3636         tz  = ('0' - zone[1]) * 60 * 60 * 10;
3637         tz += ('0' - zone[2]) * 60 * 60;
3638         tz += ('0' - zone[3]) * 60;
3639         tz += ('0' - zone[4]);
3641         if (zone[0] == '-')
3642                 tz = -tz;
3644         *time -= tz;
3647 /* Parse author lines where the name may be empty:
3648  *      author  <email@address.tld> 1138474660 +0100
3649  */
3650 static void
3651 parse_author_line(char *ident, const char **author, time_t *time)
3653         char *nameend = strchr(ident, '<');
3654         char *emailend = strchr(ident, '>');
3656         if (nameend && emailend)
3657                 *nameend = *emailend = 0;
3658         ident = chomp_string(ident);
3659         if (!*ident) {
3660                 if (nameend)
3661                         ident = chomp_string(nameend + 1);
3662                 if (!*ident)
3663                         ident = "Unknown";
3664         }
3666         *author = get_author(ident);
3668         /* Parse epoch and timezone */
3669         if (emailend && emailend[1] == ' ') {
3670                 char *secs = emailend + 2;
3671                 char *zone = strchr(secs, ' ');
3673                 *time = (time_t) atol(secs);
3675                 if (zone && strlen(zone) == STRING_SIZE(" +0700"))
3676                         parse_timezone(time, zone + 1);
3677         }
3680 static bool
3681 open_commit_parent_menu(char buf[SIZEOF_STR], int *parents)
3683         char rev[SIZEOF_REV];
3684         const char *revlist_argv[] = {
3685                 "git", "log", "--no-color", "-1", "--pretty=format:%s", rev, NULL
3686         };
3687         struct menu_item *items;
3688         char text[SIZEOF_STR];
3689         bool ok = TRUE;
3690         int i;
3692         items = calloc(*parents + 1, sizeof(*items));
3693         if (!items)
3694                 return FALSE;
3696         for (i = 0; i < *parents; i++) {
3697                 string_copy_rev(rev, &buf[SIZEOF_REV * i]);
3698                 if (!run_io_buf(revlist_argv, text, sizeof(text)) ||
3699                     !(items[i].text = strdup(text))) {
3700                         ok = FALSE;
3701                         break;
3702                 }
3703         }
3705         if (ok) {
3706                 *parents = 0;
3707                 ok = prompt_menu("Select parent", items, parents);
3708         }
3709         for (i = 0; items[i].text; i++)
3710                 free((char *) items[i].text);
3711         free(items);
3712         return ok;
3715 static bool
3716 select_commit_parent(const char *id, char rev[SIZEOF_REV], const char *path)
3718         char buf[SIZEOF_STR * 4];
3719         const char *revlist_argv[] = {
3720                 "git", "log", "--no-color", "-1",
3721                         "--pretty=format:%P", id, "--", path, NULL
3722         };
3723         int parents;
3725         if (!run_io_buf(revlist_argv, buf, sizeof(buf)) ||
3726             (parents = strlen(buf) / 40) < 0) {
3727                 report("Failed to get parent information");
3728                 return FALSE;
3730         } else if (parents == 0) {
3731                 if (path)
3732                         report("Path '%s' does not exist in the parent", path);
3733                 else
3734                         report("The selected commit has no parents");
3735                 return FALSE;
3736         }
3738         if (parents > 1 && !open_commit_parent_menu(buf, &parents))
3739                 return FALSE;
3741         string_copy_rev(rev, &buf[41 * parents]);
3742         return TRUE;
3745 /*
3746  * Pager backend
3747  */
3749 static bool
3750 pager_draw(struct view *view, struct line *line, unsigned int lineno)
3752         char text[SIZEOF_STR];
3754         if (opt_line_number && draw_lineno(view, lineno))
3755                 return TRUE;
3757         string_expand(text, sizeof(text), line->data, opt_tab_size);
3758         draw_text(view, line->type, text, TRUE);
3759         return TRUE;
3762 static bool
3763 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
3765         const char *describe_argv[] = { "git", "describe", commit_id, NULL };
3766         char ref[SIZEOF_STR];
3768         if (!run_io_buf(describe_argv, ref, sizeof(ref)) || !*ref)
3769                 return TRUE;
3771         /* This is the only fatal call, since it can "corrupt" the buffer. */
3772         if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
3773                 return FALSE;
3775         return TRUE;
3778 static void
3779 add_pager_refs(struct view *view, struct line *line)
3781         char buf[SIZEOF_STR];
3782         char *commit_id = (char *)line->data + STRING_SIZE("commit ");
3783         struct ref_list *list;
3784         size_t bufpos = 0, i;
3785         const char *sep = "Refs: ";
3786         bool is_tag = FALSE;
3788         assert(line->type == LINE_COMMIT);
3790         list = get_ref_list(commit_id);
3791         if (!list) {
3792                 if (view == VIEW(REQ_VIEW_DIFF))
3793                         goto try_add_describe_ref;
3794                 return;
3795         }
3797         for (i = 0; i < list->size; i++) {
3798                 struct ref *ref = list->refs[i];
3799                 const char *fmt = ref->tag    ? "%s[%s]" :
3800                                   ref->remote ? "%s<%s>" : "%s%s";
3802                 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
3803                         return;
3804                 sep = ", ";
3805                 if (ref->tag)
3806                         is_tag = TRUE;
3807         }
3809         if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
3810 try_add_describe_ref:
3811                 /* Add <tag>-g<commit_id> "fake" reference. */
3812                 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
3813                         return;
3814         }
3816         if (bufpos == 0)
3817                 return;
3819         add_line_text(view, buf, LINE_PP_REFS);
3822 static bool
3823 pager_read(struct view *view, char *data)
3825         struct line *line;
3827         if (!data)
3828                 return TRUE;
3830         line = add_line_text(view, data, get_line_type(data));
3831         if (!line)
3832                 return FALSE;
3834         if (line->type == LINE_COMMIT &&
3835             (view == VIEW(REQ_VIEW_DIFF) ||
3836              view == VIEW(REQ_VIEW_LOG)))
3837                 add_pager_refs(view, line);
3839         return TRUE;
3842 static enum request
3843 pager_request(struct view *view, enum request request, struct line *line)
3845         int split = 0;
3847         if (request != REQ_ENTER)
3848                 return request;
3850         if (line->type == LINE_COMMIT &&
3851            (view == VIEW(REQ_VIEW_LOG) ||
3852             view == VIEW(REQ_VIEW_PAGER))) {
3853                 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
3854                 split = 1;
3855         }
3857         /* Always scroll the view even if it was split. That way
3858          * you can use Enter to scroll through the log view and
3859          * split open each commit diff. */
3860         scroll_view(view, REQ_SCROLL_LINE_DOWN);
3862         /* FIXME: A minor workaround. Scrolling the view will call report("")
3863          * but if we are scrolling a non-current view this won't properly
3864          * update the view title. */
3865         if (split)
3866                 update_view_title(view);
3868         return REQ_NONE;
3871 static bool
3872 pager_grep(struct view *view, struct line *line)
3874         const char *text[] = { line->data, NULL };
3876         return grep_text(view, text);
3879 static void
3880 pager_select(struct view *view, struct line *line)
3882         if (line->type == LINE_COMMIT) {
3883                 char *text = (char *)line->data + STRING_SIZE("commit ");
3885                 if (view != VIEW(REQ_VIEW_PAGER))
3886                         string_copy_rev(view->ref, text);
3887                 string_copy_rev(ref_commit, text);
3888         }
3891 static struct view_ops pager_ops = {
3892         "line",
3893         NULL,
3894         NULL,
3895         pager_read,
3896         pager_draw,
3897         pager_request,
3898         pager_grep,
3899         pager_select,
3900 };
3902 static const char *log_argv[SIZEOF_ARG] = {
3903         "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
3904 };
3906 static enum request
3907 log_request(struct view *view, enum request request, struct line *line)
3909         switch (request) {
3910         case REQ_REFRESH:
3911                 load_refs();
3912                 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
3913                 return REQ_NONE;
3914         default:
3915                 return pager_request(view, request, line);
3916         }
3919 static struct view_ops log_ops = {
3920         "line",
3921         log_argv,
3922         NULL,
3923         pager_read,
3924         pager_draw,
3925         log_request,
3926         pager_grep,
3927         pager_select,
3928 };
3930 static const char *diff_argv[SIZEOF_ARG] = {
3931         "git", "show", "--pretty=fuller", "--no-color", "--root",
3932                 "--patch-with-stat", "--find-copies-harder", "-C", "%(commit)", NULL
3933 };
3935 static struct view_ops diff_ops = {
3936         "line",
3937         diff_argv,
3938         NULL,
3939         pager_read,
3940         pager_draw,
3941         pager_request,
3942         pager_grep,
3943         pager_select,
3944 };
3946 /*
3947  * Help backend
3948  */
3950 static bool help_keymap_hidden[ARRAY_SIZE(keymap_table)];
3952 static char *
3953 help_name(char buf[SIZEOF_STR], const char *name, size_t namelen)
3955         int bufpos;
3957         for (bufpos = 0; bufpos <= namelen; bufpos++) {
3958                 buf[bufpos] = tolower(name[bufpos]);
3959                 if (buf[bufpos] == '_')
3960                         buf[bufpos] = '-';
3961         }
3963         buf[bufpos] = 0;
3964         return buf;
3967 #define help_keymap_name(buf, keymap) \
3968         help_name(buf, keymap_table[keymap].name, keymap_table[keymap].namelen)
3970 static bool
3971 help_open_keymap_title(struct view *view, enum keymap keymap)
3973         char buf[SIZEOF_STR];
3974         struct line *line;
3976         line = add_line_format(view, LINE_HELP_KEYMAP, "[%c] %s bindings",
3977                                help_keymap_hidden[keymap] ? '+' : '-',
3978                                help_keymap_name(buf, keymap));
3979         if (line)
3980                 line->other = keymap;
3982         return help_keymap_hidden[keymap];
3985 static void
3986 help_open_keymap(struct view *view, enum keymap keymap)
3988         const char *group = NULL;
3989         char buf[SIZEOF_STR];
3990         size_t bufpos;
3991         bool add_title = TRUE;
3992         int i;
3994         for (i = 0; i < ARRAY_SIZE(req_info); i++) {
3995                 const char *key = NULL;
3997                 if (req_info[i].request == REQ_NONE)
3998                         continue;
4000                 if (!req_info[i].request) {
4001                         group = req_info[i].help;
4002                         continue;
4003                 }
4005                 key = get_keys(keymap, req_info[i].request, TRUE);
4006                 if (!key || !*key)
4007                         continue;
4009                 if (add_title && help_open_keymap_title(view, keymap))
4010                         return;
4011                 add_title = false;
4013                 if (group) {
4014                         add_line_text(view, group, LINE_HELP_GROUP);
4015                         group = NULL;
4016                 }
4018                 add_line_format(view, LINE_DEFAULT, "    %-25s %-20s %s", key,
4019                                 help_name(buf, req_info[i].name, req_info[i].namelen),
4020                                 req_info[i].help);
4021         }
4023         group = "External commands:";
4025         for (i = 0; i < run_requests; i++) {
4026                 struct run_request *req = get_run_request(REQ_NONE + i + 1);
4027                 const char *key;
4028                 int argc;
4030                 if (!req || req->keymap != keymap)
4031                         continue;
4033                 key = get_key_name(req->key);
4034                 if (!*key)
4035                         key = "(no key defined)";
4037                 if (add_title && help_open_keymap_title(view, keymap))
4038                         return;
4039                 if (group) {
4040                         add_line_text(view, group, LINE_HELP_GROUP);
4041                         group = NULL;
4042                 }
4044                 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
4045                         if (!string_format_from(buf, &bufpos, "%s%s",
4046                                                 argc ? " " : "", req->argv[argc]))
4047                                 return;
4049                 add_line_format(view, LINE_DEFAULT, "    %-25s `%s`", key, buf);
4050         }
4053 static bool
4054 help_open(struct view *view)
4056         enum keymap keymap;
4058         reset_view(view);
4059         add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
4060         add_line_text(view, "", LINE_DEFAULT);
4062         for (keymap = 0; keymap < ARRAY_SIZE(keymap_table); keymap++)
4063                 help_open_keymap(view, keymap);
4065         return TRUE;
4068 static enum request
4069 help_request(struct view *view, enum request request, struct line *line)
4071         switch (request) {
4072         case REQ_ENTER:
4073                 if (line->type == LINE_HELP_KEYMAP) {
4074                         help_keymap_hidden[line->other] =
4075                                 !help_keymap_hidden[line->other];
4076                         view->p_restore = TRUE;
4077                         open_view(view, REQ_VIEW_HELP, OPEN_REFRESH);
4078                 }
4080                 return REQ_NONE;
4081         default:
4082                 return pager_request(view, request, line);
4083         }
4086 static struct view_ops help_ops = {
4087         "line",
4088         NULL,
4089         help_open,
4090         NULL,
4091         pager_draw,
4092         help_request,
4093         pager_grep,
4094         pager_select,
4095 };
4098 /*
4099  * Tree backend
4100  */
4102 struct tree_stack_entry {
4103         struct tree_stack_entry *prev;  /* Entry below this in the stack */
4104         unsigned long lineno;           /* Line number to restore */
4105         char *name;                     /* Position of name in opt_path */
4106 };
4108 /* The top of the path stack. */
4109 static struct tree_stack_entry *tree_stack = NULL;
4110 unsigned long tree_lineno = 0;
4112 static void
4113 pop_tree_stack_entry(void)
4115         struct tree_stack_entry *entry = tree_stack;
4117         tree_lineno = entry->lineno;
4118         entry->name[0] = 0;
4119         tree_stack = entry->prev;
4120         free(entry);
4123 static void
4124 push_tree_stack_entry(const char *name, unsigned long lineno)
4126         struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
4127         size_t pathlen = strlen(opt_path);
4129         if (!entry)
4130                 return;
4132         entry->prev = tree_stack;
4133         entry->name = opt_path + pathlen;
4134         tree_stack = entry;
4136         if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
4137                 pop_tree_stack_entry();
4138                 return;
4139         }
4141         /* Move the current line to the first tree entry. */
4142         tree_lineno = 1;
4143         entry->lineno = lineno;
4146 /* Parse output from git-ls-tree(1):
4147  *
4148  * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
4149  */
4151 #define SIZEOF_TREE_ATTR \
4152         STRING_SIZE("100644 blob f931e1d229c3e185caad4449bf5b66ed72462657\t")
4154 #define SIZEOF_TREE_MODE \
4155         STRING_SIZE("100644 ")
4157 #define TREE_ID_OFFSET \
4158         STRING_SIZE("100644 blob ")
4160 struct tree_entry {
4161         char id[SIZEOF_REV];
4162         mode_t mode;
4163         time_t time;                    /* Date from the author ident. */
4164         const char *author;             /* Author of the commit. */
4165         char name[1];
4166 };
4168 static const char *
4169 tree_path(const struct line *line)
4171         return ((struct tree_entry *) line->data)->name;
4174 static int
4175 tree_compare_entry(const struct line *line1, const struct line *line2)
4177         if (line1->type != line2->type)
4178                 return line1->type == LINE_TREE_DIR ? -1 : 1;
4179         return strcmp(tree_path(line1), tree_path(line2));
4182 static const enum sort_field tree_sort_fields[] = {
4183         ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
4184 };
4185 static struct sort_state tree_sort_state = SORT_STATE(tree_sort_fields);
4187 static int
4188 tree_compare(const void *l1, const void *l2)
4190         const struct line *line1 = (const struct line *) l1;
4191         const struct line *line2 = (const struct line *) l2;
4192         const struct tree_entry *entry1 = ((const struct line *) l1)->data;
4193         const struct tree_entry *entry2 = ((const struct line *) l2)->data;
4195         if (line1->type == LINE_TREE_HEAD)
4196                 return -1;
4197         if (line2->type == LINE_TREE_HEAD)
4198                 return 1;
4200         switch (get_sort_field(tree_sort_state)) {
4201         case ORDERBY_DATE:
4202                 return sort_order(tree_sort_state, entry1->time - entry2->time);
4204         case ORDERBY_AUTHOR:
4205                 return sort_order(tree_sort_state, strcmp(entry1->author, entry2->author));
4207         case ORDERBY_NAME:
4208         default:
4209                 return sort_order(tree_sort_state, tree_compare_entry(line1, line2));
4210         }
4214 static struct line *
4215 tree_entry(struct view *view, enum line_type type, const char *path,
4216            const char *mode, const char *id)
4218         struct tree_entry *entry = calloc(1, sizeof(*entry) + strlen(path));
4219         struct line *line = entry ? add_line_data(view, entry, type) : NULL;
4221         if (!entry || !line) {
4222                 free(entry);
4223                 return NULL;
4224         }
4226         strncpy(entry->name, path, strlen(path));
4227         if (mode)
4228                 entry->mode = strtoul(mode, NULL, 8);
4229         if (id)
4230                 string_copy_rev(entry->id, id);
4232         return line;
4235 static bool
4236 tree_read_date(struct view *view, char *text, bool *read_date)
4238         static const char *author_name;
4239         static time_t author_time;
4241         if (!text && *read_date) {
4242                 *read_date = FALSE;
4243                 return TRUE;
4245         } else if (!text) {
4246                 char *path = *opt_path ? opt_path : ".";
4247                 /* Find next entry to process */
4248                 const char *log_file[] = {
4249                         "git", "log", "--no-color", "--pretty=raw",
4250                                 "--cc", "--raw", view->id, "--", path, NULL
4251                 };
4252                 struct io io = {};
4254                 if (!view->lines) {
4255                         tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL);
4256                         report("Tree is empty");
4257                         return TRUE;
4258                 }
4260                 if (!run_io_rd_dir(&io, log_file, opt_cdup, FORMAT_NONE)) {
4261                         report("Failed to load tree data");
4262                         return TRUE;
4263                 }
4265                 done_io(view->pipe);
4266                 view->io = io;
4267                 *read_date = TRUE;
4268                 return FALSE;
4270         } else if (*text == 'a' && get_line_type(text) == LINE_AUTHOR) {
4271                 parse_author_line(text + STRING_SIZE("author "),
4272                                   &author_name, &author_time);
4274         } else if (*text == ':') {
4275                 char *pos;
4276                 size_t annotated = 1;
4277                 size_t i;
4279                 pos = strchr(text, '\t');
4280                 if (!pos)
4281                         return TRUE;
4282                 text = pos + 1;
4283                 if (*opt_path && !strncmp(text, opt_path, strlen(opt_path)))
4284                         text += strlen(opt_path);
4285                 pos = strchr(text, '/');
4286                 if (pos)
4287                         *pos = 0;
4289                 for (i = 1; i < view->lines; i++) {
4290                         struct line *line = &view->line[i];
4291                         struct tree_entry *entry = line->data;
4293                         annotated += !!entry->author;
4294                         if (entry->author || strcmp(entry->name, text))
4295                                 continue;
4297                         entry->author = author_name;
4298                         entry->time = author_time;
4299                         line->dirty = 1;
4300                         break;
4301                 }
4303                 if (annotated == view->lines)
4304                         kill_io(view->pipe);
4305         }
4306         return TRUE;
4309 static bool
4310 tree_read(struct view *view, char *text)
4312         static bool read_date = FALSE;
4313         struct tree_entry *data;
4314         struct line *entry, *line;
4315         enum line_type type;
4316         size_t textlen = text ? strlen(text) : 0;
4317         char *path = text + SIZEOF_TREE_ATTR;
4319         if (read_date || !text)
4320                 return tree_read_date(view, text, &read_date);
4322         if (textlen <= SIZEOF_TREE_ATTR)
4323                 return FALSE;
4324         if (view->lines == 0 &&
4325             !tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL))
4326                 return FALSE;
4328         /* Strip the path part ... */
4329         if (*opt_path) {
4330                 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
4331                 size_t striplen = strlen(opt_path);
4333                 if (pathlen > striplen)
4334                         memmove(path, path + striplen,
4335                                 pathlen - striplen + 1);
4337                 /* Insert "link" to parent directory. */
4338                 if (view->lines == 1 &&
4339                     !tree_entry(view, LINE_TREE_DIR, "..", "040000", view->ref))
4340                         return FALSE;
4341         }
4343         type = text[SIZEOF_TREE_MODE] == 't' ? LINE_TREE_DIR : LINE_TREE_FILE;
4344         entry = tree_entry(view, type, path, text, text + TREE_ID_OFFSET);
4345         if (!entry)
4346                 return FALSE;
4347         data = entry->data;
4349         /* Skip "Directory ..." and ".." line. */
4350         for (line = &view->line[1 + !!*opt_path]; line < entry; line++) {
4351                 if (tree_compare_entry(line, entry) <= 0)
4352                         continue;
4354                 memmove(line + 1, line, (entry - line) * sizeof(*entry));
4356                 line->data = data;
4357                 line->type = type;
4358                 for (; line <= entry; line++)
4359                         line->dirty = line->cleareol = 1;
4360                 return TRUE;
4361         }
4363         if (tree_lineno > view->lineno) {
4364                 view->lineno = tree_lineno;
4365                 tree_lineno = 0;
4366         }
4368         return TRUE;
4371 static bool
4372 tree_draw(struct view *view, struct line *line, unsigned int lineno)
4374         struct tree_entry *entry = line->data;
4376         if (line->type == LINE_TREE_HEAD) {
4377                 if (draw_text(view, line->type, "Directory path /", TRUE))
4378                         return TRUE;
4379         } else {
4380                 if (draw_mode(view, entry->mode))
4381                         return TRUE;
4383                 if (opt_author && draw_author(view, entry->author))
4384                         return TRUE;
4386                 if (opt_date && draw_date(view, entry->author ? &entry->time : NULL))
4387                         return TRUE;
4388         }
4389         if (draw_text(view, line->type, entry->name, TRUE))
4390                 return TRUE;
4391         return TRUE;
4394 static void
4395 open_blob_editor()
4397         char file[SIZEOF_STR] = "/tmp/tigblob.XXXXXX";
4398         int fd = mkstemp(file);
4400         if (fd == -1)
4401                 report("Failed to create temporary file");
4402         else if (!run_io_append(blob_ops.argv, FORMAT_ALL, fd))
4403                 report("Failed to save blob data to file");
4404         else
4405                 open_editor(FALSE, file);
4406         if (fd != -1)
4407                 unlink(file);
4410 static enum request
4411 tree_request(struct view *view, enum request request, struct line *line)
4413         enum open_flags flags;
4415         switch (request) {
4416         case REQ_VIEW_BLAME:
4417                 if (line->type != LINE_TREE_FILE) {
4418                         report("Blame only supported for files");
4419                         return REQ_NONE;
4420                 }
4422                 string_copy(opt_ref, view->vid);
4423                 return request;
4425         case REQ_EDIT:
4426                 if (line->type != LINE_TREE_FILE) {
4427                         report("Edit only supported for files");
4428                 } else if (!is_head_commit(view->vid)) {
4429                         open_blob_editor();
4430                 } else {
4431                         open_editor(TRUE, opt_file);
4432                 }
4433                 return REQ_NONE;
4435         case REQ_TOGGLE_SORT_FIELD:
4436         case REQ_TOGGLE_SORT_ORDER:
4437                 sort_view(view, request, &tree_sort_state, tree_compare);
4438                 return REQ_NONE;
4440         case REQ_PARENT:
4441                 if (!*opt_path) {
4442                         /* quit view if at top of tree */
4443                         return REQ_VIEW_CLOSE;
4444                 }
4445                 /* fake 'cd  ..' */
4446                 line = &view->line[1];
4447                 break;
4449         case REQ_ENTER:
4450                 break;
4452         default:
4453                 return request;
4454         }
4456         /* Cleanup the stack if the tree view is at a different tree. */
4457         while (!*opt_path && tree_stack)
4458                 pop_tree_stack_entry();
4460         switch (line->type) {
4461         case LINE_TREE_DIR:
4462                 /* Depending on whether it is a subdirectory or parent link
4463                  * mangle the path buffer. */
4464                 if (line == &view->line[1] && *opt_path) {
4465                         pop_tree_stack_entry();
4467                 } else {
4468                         const char *basename = tree_path(line);
4470                         push_tree_stack_entry(basename, view->lineno);
4471                 }
4473                 /* Trees and subtrees share the same ID, so they are not not
4474                  * unique like blobs. */
4475                 flags = OPEN_RELOAD;
4476                 request = REQ_VIEW_TREE;
4477                 break;
4479         case LINE_TREE_FILE:
4480                 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4481                 request = REQ_VIEW_BLOB;
4482                 break;
4484         default:
4485                 return REQ_NONE;
4486         }
4488         open_view(view, request, flags);
4489         if (request == REQ_VIEW_TREE)
4490                 view->lineno = tree_lineno;
4492         return REQ_NONE;
4495 static bool
4496 tree_grep(struct view *view, struct line *line)
4498         struct tree_entry *entry = line->data;
4499         const char *text[] = {
4500                 entry->name,
4501                 opt_author ? entry->author : "",
4502                 opt_date ? mkdate(&entry->time) : "",
4503                 NULL
4504         };
4506         return grep_text(view, text);
4509 static void
4510 tree_select(struct view *view, struct line *line)
4512         struct tree_entry *entry = line->data;
4514         if (line->type == LINE_TREE_FILE) {
4515                 string_copy_rev(ref_blob, entry->id);
4516                 string_format(opt_file, "%s%s", opt_path, tree_path(line));
4518         } else if (line->type != LINE_TREE_DIR) {
4519                 return;
4520         }
4522         string_copy_rev(view->ref, entry->id);
4525 static bool
4526 tree_prepare(struct view *view)
4528         if (view->lines == 0 && opt_prefix[0]) {
4529                 char *pos = opt_prefix;
4531                 while (pos && *pos) {
4532                         char *end = strchr(pos, '/');
4534                         if (end)
4535                                 *end = 0;
4536                         push_tree_stack_entry(pos, 0);
4537                         pos = end;
4538                         if (end) {
4539                                 *end = '/';
4540                                 pos++;
4541                         }
4542                 }
4544         } else if (strcmp(view->vid, view->id)) {
4545                 opt_path[0] = 0;
4546         }
4548         return init_io_rd(&view->io, view->ops->argv, opt_cdup, FORMAT_ALL);
4551 static const char *tree_argv[SIZEOF_ARG] = {
4552         "git", "ls-tree", "%(commit)", "%(directory)", NULL
4553 };
4555 static struct view_ops tree_ops = {
4556         "file",
4557         tree_argv,
4558         NULL,
4559         tree_read,
4560         tree_draw,
4561         tree_request,
4562         tree_grep,
4563         tree_select,
4564         tree_prepare,
4565 };
4567 static bool
4568 blob_read(struct view *view, char *line)
4570         if (!line)
4571                 return TRUE;
4572         return add_line_text(view, line, LINE_DEFAULT) != NULL;
4575 static enum request
4576 blob_request(struct view *view, enum request request, struct line *line)
4578         switch (request) {
4579         case REQ_EDIT:
4580                 open_blob_editor();
4581                 return REQ_NONE;
4582         default:
4583                 return pager_request(view, request, line);
4584         }
4587 static const char *blob_argv[SIZEOF_ARG] = {
4588         "git", "cat-file", "blob", "%(blob)", NULL
4589 };
4591 static struct view_ops blob_ops = {
4592         "line",
4593         blob_argv,
4594         NULL,
4595         blob_read,
4596         pager_draw,
4597         blob_request,
4598         pager_grep,
4599         pager_select,
4600 };
4602 /*
4603  * Blame backend
4604  *
4605  * Loading the blame view is a two phase job:
4606  *
4607  *  1. File content is read either using opt_file from the
4608  *     filesystem or using git-cat-file.
4609  *  2. Then blame information is incrementally added by
4610  *     reading output from git-blame.
4611  */
4613 static const char *blame_head_argv[] = {
4614         "git", "blame", "--incremental", "--", "%(file)", NULL
4615 };
4617 static const char *blame_ref_argv[] = {
4618         "git", "blame", "--incremental", "%(ref)", "--", "%(file)", NULL
4619 };
4621 static const char *blame_cat_file_argv[] = {
4622         "git", "cat-file", "blob", "%(ref):%(file)", NULL
4623 };
4625 struct blame_commit {
4626         char id[SIZEOF_REV];            /* SHA1 ID. */
4627         char title[128];                /* First line of the commit message. */
4628         const char *author;             /* Author of the commit. */
4629         time_t time;                    /* Date from the author ident. */
4630         char filename[128];             /* Name of file. */
4631         bool has_previous;              /* Was a "previous" line detected. */
4632 };
4634 struct blame {
4635         struct blame_commit *commit;
4636         unsigned long lineno;
4637         char text[1];
4638 };
4640 static bool
4641 blame_open(struct view *view)
4643         char path[SIZEOF_STR];
4645         if (!view->parent && *opt_prefix) {
4646                 string_copy(path, opt_file);
4647                 if (!string_format(opt_file, "%s%s", opt_prefix, path))
4648                         return FALSE;
4649         }
4651         if (!string_format(path, "%s%s", opt_cdup, opt_file))
4652                 return FALSE;
4654         if (*opt_ref || !io_open(&view->io, path)) {
4655                 if (!run_io_rd_dir(&view->io, blame_cat_file_argv, opt_cdup, FORMAT_ALL))
4656                         return FALSE;
4657         }
4659         setup_update(view, opt_file);
4660         string_format(view->ref, "%s ...", opt_file);
4662         return TRUE;
4665 static struct blame_commit *
4666 get_blame_commit(struct view *view, const char *id)
4668         size_t i;
4670         for (i = 0; i < view->lines; i++) {
4671                 struct blame *blame = view->line[i].data;
4673                 if (!blame->commit)
4674                         continue;
4676                 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
4677                         return blame->commit;
4678         }
4680         {
4681                 struct blame_commit *commit = calloc(1, sizeof(*commit));
4683                 if (commit)
4684                         string_ncopy(commit->id, id, SIZEOF_REV);
4685                 return commit;
4686         }
4689 static bool
4690 parse_number(const char **posref, size_t *number, size_t min, size_t max)
4692         const char *pos = *posref;
4694         *posref = NULL;
4695         pos = strchr(pos + 1, ' ');
4696         if (!pos || !isdigit(pos[1]))
4697                 return FALSE;
4698         *number = atoi(pos + 1);
4699         if (*number < min || *number > max)
4700                 return FALSE;
4702         *posref = pos;
4703         return TRUE;
4706 static struct blame_commit *
4707 parse_blame_commit(struct view *view, const char *text, int *blamed)
4709         struct blame_commit *commit;
4710         struct blame *blame;
4711         const char *pos = text + SIZEOF_REV - 2;
4712         size_t orig_lineno = 0;
4713         size_t lineno;
4714         size_t group;
4716         if (strlen(text) <= SIZEOF_REV || pos[1] != ' ')
4717                 return NULL;
4719         if (!parse_number(&pos, &orig_lineno, 1, 9999999) ||
4720             !parse_number(&pos, &lineno, 1, view->lines) ||
4721             !parse_number(&pos, &group, 1, view->lines - lineno + 1))
4722                 return NULL;
4724         commit = get_blame_commit(view, text);
4725         if (!commit)
4726                 return NULL;
4728         *blamed += group;
4729         while (group--) {
4730                 struct line *line = &view->line[lineno + group - 1];
4732                 blame = line->data;
4733                 blame->commit = commit;
4734                 blame->lineno = orig_lineno + group - 1;
4735                 line->dirty = 1;
4736         }
4738         return commit;
4741 static bool
4742 blame_read_file(struct view *view, const char *line, bool *read_file)
4744         if (!line) {
4745                 const char **argv = *opt_ref ? blame_ref_argv : blame_head_argv;
4746                 struct io io = {};
4748                 if (view->lines == 0 && !view->parent)
4749                         die("No blame exist for %s", view->vid);
4751                 if (view->lines == 0 || !run_io_rd_dir(&io, argv, opt_cdup, FORMAT_ALL)) {
4752                         report("Failed to load blame data");
4753                         return TRUE;
4754                 }
4756                 done_io(view->pipe);
4757                 view->io = io;
4758                 *read_file = FALSE;
4759                 return FALSE;
4761         } else {
4762                 size_t linelen = strlen(line);
4763                 struct blame *blame = malloc(sizeof(*blame) + linelen);
4765                 if (!blame)
4766                         return FALSE;
4768                 blame->commit = NULL;
4769                 strncpy(blame->text, line, linelen);
4770                 blame->text[linelen] = 0;
4771                 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
4772         }
4775 static bool
4776 match_blame_header(const char *name, char **line)
4778         size_t namelen = strlen(name);
4779         bool matched = !strncmp(name, *line, namelen);
4781         if (matched)
4782                 *line += namelen;
4784         return matched;
4787 static bool
4788 blame_read(struct view *view, char *line)
4790         static struct blame_commit *commit = NULL;
4791         static int blamed = 0;
4792         static bool read_file = TRUE;
4794         if (read_file)
4795                 return blame_read_file(view, line, &read_file);
4797         if (!line) {
4798                 /* Reset all! */
4799                 commit = NULL;
4800                 blamed = 0;
4801                 read_file = TRUE;
4802                 string_format(view->ref, "%s", view->vid);
4803                 if (view_is_displayed(view)) {
4804                         update_view_title(view);
4805                         redraw_view_from(view, 0);
4806                 }
4807                 return TRUE;
4808         }
4810         if (!commit) {
4811                 commit = parse_blame_commit(view, line, &blamed);
4812                 string_format(view->ref, "%s %2d%%", view->vid,
4813                               view->lines ? blamed * 100 / view->lines : 0);
4815         } else if (match_blame_header("author ", &line)) {
4816                 commit->author = get_author(line);
4818         } else if (match_blame_header("author-time ", &line)) {
4819                 commit->time = (time_t) atol(line);
4821         } else if (match_blame_header("author-tz ", &line)) {
4822                 parse_timezone(&commit->time, line);
4824         } else if (match_blame_header("summary ", &line)) {
4825                 string_ncopy(commit->title, line, strlen(line));
4827         } else if (match_blame_header("previous ", &line)) {
4828                 commit->has_previous = TRUE;
4830         } else if (match_blame_header("filename ", &line)) {
4831                 string_ncopy(commit->filename, line, strlen(line));
4832                 commit = NULL;
4833         }
4835         return TRUE;
4838 static bool
4839 blame_draw(struct view *view, struct line *line, unsigned int lineno)
4841         struct blame *blame = line->data;
4842         time_t *time = NULL;
4843         const char *id = NULL, *author = NULL;
4844         char text[SIZEOF_STR];
4846         if (blame->commit && *blame->commit->filename) {
4847                 id = blame->commit->id;
4848                 author = blame->commit->author;
4849                 time = &blame->commit->time;
4850         }
4852         if (opt_date && draw_date(view, time))
4853                 return TRUE;
4855         if (opt_author && draw_author(view, author))
4856                 return TRUE;
4858         if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
4859                 return TRUE;
4861         if (draw_lineno(view, lineno))
4862                 return TRUE;
4864         string_expand(text, sizeof(text), blame->text, opt_tab_size);
4865         draw_text(view, LINE_DEFAULT, text, TRUE);
4866         return TRUE;
4869 static bool
4870 check_blame_commit(struct blame *blame, bool check_null_id)
4872         if (!blame->commit)
4873                 report("Commit data not loaded yet");
4874         else if (check_null_id && !strcmp(blame->commit->id, NULL_ID))
4875                 report("No commit exist for the selected line");
4876         else
4877                 return TRUE;
4878         return FALSE;
4881 static void
4882 setup_blame_parent_line(struct view *view, struct blame *blame)
4884         const char *diff_tree_argv[] = {
4885                 "git", "diff-tree", "-U0", blame->commit->id,
4886                         "--", blame->commit->filename, NULL
4887         };
4888         struct io io = {};
4889         int parent_lineno = -1;
4890         int blamed_lineno = -1;
4891         char *line;
4893         if (!run_io(&io, diff_tree_argv, NULL, IO_RD))
4894                 return;
4896         while ((line = io_get(&io, '\n', TRUE))) {
4897                 if (*line == '@') {
4898                         char *pos = strchr(line, '+');
4900                         parent_lineno = atoi(line + 4);
4901                         if (pos)
4902                                 blamed_lineno = atoi(pos + 1);
4904                 } else if (*line == '+' && parent_lineno != -1) {
4905                         if (blame->lineno == blamed_lineno - 1 &&
4906                             !strcmp(blame->text, line + 1)) {
4907                                 view->lineno = parent_lineno ? parent_lineno - 1 : 0;
4908                                 break;
4909                         }
4910                         blamed_lineno++;
4911                 }
4912         }
4914         done_io(&io);
4917 static enum request
4918 blame_request(struct view *view, enum request request, struct line *line)
4920         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4921         struct blame *blame = line->data;
4923         switch (request) {
4924         case REQ_VIEW_BLAME:
4925                 if (check_blame_commit(blame, TRUE)) {
4926                         string_copy(opt_ref, blame->commit->id);
4927                         string_copy(opt_file, blame->commit->filename);
4928                         if (blame->lineno)
4929                                 view->lineno = blame->lineno;
4930                         open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4931                 }
4932                 break;
4934         case REQ_PARENT:
4935                 if (check_blame_commit(blame, TRUE) &&
4936                     select_commit_parent(blame->commit->id, opt_ref,
4937                                          blame->commit->filename)) {
4938                         string_copy(opt_file, blame->commit->filename);
4939                         setup_blame_parent_line(view, blame);
4940                         open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4941                 }
4942                 break;
4944         case REQ_ENTER:
4945                 if (!check_blame_commit(blame, FALSE))
4946                         break;
4948                 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
4949                     !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
4950                         break;
4952                 if (!strcmp(blame->commit->id, NULL_ID)) {
4953                         struct view *diff = VIEW(REQ_VIEW_DIFF);
4954                         const char *diff_index_argv[] = {
4955                                 "git", "diff-index", "--root", "--patch-with-stat",
4956                                         "-C", "-M", "HEAD", "--", view->vid, NULL
4957                         };
4959                         if (!blame->commit->has_previous) {
4960                                 diff_index_argv[1] = "diff";
4961                                 diff_index_argv[2] = "--no-color";
4962                                 diff_index_argv[6] = "--";
4963                                 diff_index_argv[7] = "/dev/null";
4964                         }
4966                         if (!prepare_update(diff, diff_index_argv, NULL, FORMAT_DASH)) {
4967                                 report("Failed to allocate diff command");
4968                                 break;
4969                         }
4970                         flags |= OPEN_PREPARED;
4971                 }
4973                 open_view(view, REQ_VIEW_DIFF, flags);
4974                 if (VIEW(REQ_VIEW_DIFF)->pipe && !strcmp(blame->commit->id, NULL_ID))
4975                         string_copy_rev(VIEW(REQ_VIEW_DIFF)->ref, NULL_ID);
4976                 break;
4978         default:
4979                 return request;
4980         }
4982         return REQ_NONE;
4985 static bool
4986 blame_grep(struct view *view, struct line *line)
4988         struct blame *blame = line->data;
4989         struct blame_commit *commit = blame->commit;
4990         const char *text[] = {
4991                 blame->text,
4992                 commit ? commit->title : "",
4993                 commit ? commit->id : "",
4994                 commit && opt_author ? commit->author : "",
4995                 commit && opt_date ? mkdate(&commit->time) : "",
4996                 NULL
4997         };
4999         return grep_text(view, text);
5002 static void
5003 blame_select(struct view *view, struct line *line)
5005         struct blame *blame = line->data;
5006         struct blame_commit *commit = blame->commit;
5008         if (!commit)
5009                 return;
5011         if (!strcmp(commit->id, NULL_ID))
5012                 string_ncopy(ref_commit, "HEAD", 4);
5013         else
5014                 string_copy_rev(ref_commit, commit->id);
5017 static struct view_ops blame_ops = {
5018         "line",
5019         NULL,
5020         blame_open,
5021         blame_read,
5022         blame_draw,
5023         blame_request,
5024         blame_grep,
5025         blame_select,
5026 };
5028 /*
5029  * Branch backend
5030  */
5032 struct branch {
5033         const char *author;             /* Author of the last commit. */
5034         time_t time;                    /* Date of the last activity. */
5035         struct ref *ref;                /* Name and commit ID information. */
5036 };
5038 static const enum sort_field branch_sort_fields[] = {
5039         ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
5040 };
5041 static struct sort_state branch_sort_state = SORT_STATE(branch_sort_fields);
5043 static int
5044 branch_compare(const void *l1, const void *l2)
5046         const struct branch *branch1 = ((const struct line *) l1)->data;
5047         const struct branch *branch2 = ((const struct line *) l2)->data;
5049         switch (get_sort_field(branch_sort_state)) {
5050         case ORDERBY_DATE:
5051                 return sort_order(branch_sort_state, branch1->time - branch2->time);
5053         case ORDERBY_AUTHOR:
5054                 return sort_order(branch_sort_state, strcmp(branch1->author, branch2->author));
5056         case ORDERBY_NAME:
5057         default:
5058                 return sort_order(branch_sort_state, strcmp(branch1->ref->name, branch2->ref->name));
5059         }
5062 static bool
5063 branch_draw(struct view *view, struct line *line, unsigned int lineno)
5065         struct branch *branch = line->data;
5066         enum line_type type = branch->ref->head ? LINE_MAIN_HEAD : LINE_DEFAULT;
5068         if (opt_date && draw_date(view, branch->author ? &branch->time : NULL))
5069                 return TRUE;
5071         if (opt_author && draw_author(view, branch->author))
5072                 return TRUE;
5074         draw_text(view, type, branch->ref->name, TRUE);
5075         return TRUE;
5078 static enum request
5079 branch_request(struct view *view, enum request request, struct line *line)
5081         switch (request) {
5082         case REQ_REFRESH:
5083                 load_refs();
5084                 open_view(view, REQ_VIEW_BRANCH, OPEN_REFRESH);
5085                 return REQ_NONE;
5087         case REQ_TOGGLE_SORT_FIELD:
5088         case REQ_TOGGLE_SORT_ORDER:
5089                 sort_view(view, request, &branch_sort_state, branch_compare);
5090                 return REQ_NONE;
5092         case REQ_ENTER:
5093                 open_view(view, REQ_VIEW_MAIN, OPEN_SPLIT);
5094                 return REQ_NONE;
5096         default:
5097                 return request;
5098         }
5101 static bool
5102 branch_read(struct view *view, char *line)
5104         static char id[SIZEOF_REV];
5105         struct branch *reference;
5106         size_t i;
5108         if (!line)
5109                 return TRUE;
5111         switch (get_line_type(line)) {
5112         case LINE_COMMIT:
5113                 string_copy_rev(id, line + STRING_SIZE("commit "));
5114                 return TRUE;
5116         case LINE_AUTHOR:
5117                 for (i = 0, reference = NULL; i < view->lines; i++) {
5118                         struct branch *branch = view->line[i].data;
5120                         if (strcmp(branch->ref->id, id))
5121                                 continue;
5123                         view->line[i].dirty = TRUE;
5124                         if (reference) {
5125                                 branch->author = reference->author;
5126                                 branch->time = reference->time;
5127                                 continue;
5128                         }
5130                         parse_author_line(line + STRING_SIZE("author "),
5131                                           &branch->author, &branch->time);
5132                         reference = branch;
5133                 }
5134                 return TRUE;
5136         default:
5137                 return TRUE;
5138         }
5142 static bool
5143 branch_open_visitor(void *data, struct ref *ref)
5145         struct view *view = data;
5146         struct branch *branch;
5148         if (ref->tag || ref->ltag || ref->remote)
5149                 return TRUE;
5151         branch = calloc(1, sizeof(*branch));
5152         if (!branch)
5153                 return FALSE;
5155         branch->ref = ref;
5156         return !!add_line_data(view, branch, LINE_DEFAULT);
5159 static bool
5160 branch_open(struct view *view)
5162         const char *branch_log[] = {
5163                 "git", "log", "--no-color", "--pretty=raw",
5164                         "--simplify-by-decoration", "--all", NULL
5165         };
5167         if (!run_io_rd(&view->io, branch_log, NULL, FORMAT_NONE)) {
5168                 report("Failed to load branch data");
5169                 return TRUE;
5170         }
5172         setup_update(view, view->id);
5173         foreach_ref(branch_open_visitor, view);
5174         view->p_restore = TRUE;
5176         return TRUE;
5179 static bool
5180 branch_grep(struct view *view, struct line *line)
5182         struct branch *branch = line->data;
5183         const char *text[] = {
5184                 branch->ref->name,
5185                 branch->author,
5186                 NULL
5187         };
5189         return grep_text(view, text);
5192 static void
5193 branch_select(struct view *view, struct line *line)
5195         struct branch *branch = line->data;
5197         string_copy_rev(view->ref, branch->ref->id);
5198         string_copy_rev(ref_commit, branch->ref->id);
5199         string_copy_rev(ref_head, branch->ref->id);
5202 static struct view_ops branch_ops = {
5203         "branch",
5204         NULL,
5205         branch_open,
5206         branch_read,
5207         branch_draw,
5208         branch_request,
5209         branch_grep,
5210         branch_select,
5211 };
5213 /*
5214  * Status backend
5215  */
5217 struct status {
5218         char status;
5219         struct {
5220                 mode_t mode;
5221                 char rev[SIZEOF_REV];
5222                 char name[SIZEOF_STR];
5223         } old;
5224         struct {
5225                 mode_t mode;
5226                 char rev[SIZEOF_REV];
5227                 char name[SIZEOF_STR];
5228         } new;
5229 };
5231 static char status_onbranch[SIZEOF_STR];
5232 static struct status stage_status;
5233 static enum line_type stage_line_type;
5234 static size_t stage_chunks;
5235 static int *stage_chunk;
5237 DEFINE_ALLOCATOR(realloc_ints, int, 32)
5239 /* This should work even for the "On branch" line. */
5240 static inline bool
5241 status_has_none(struct view *view, struct line *line)
5243         return line < view->line + view->lines && !line[1].data;
5246 /* Get fields from the diff line:
5247  * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
5248  */
5249 static inline bool
5250 status_get_diff(struct status *file, const char *buf, size_t bufsize)
5252         const char *old_mode = buf +  1;
5253         const char *new_mode = buf +  8;
5254         const char *old_rev  = buf + 15;
5255         const char *new_rev  = buf + 56;
5256         const char *status   = buf + 97;
5258         if (bufsize < 98 ||
5259             old_mode[-1] != ':' ||
5260             new_mode[-1] != ' ' ||
5261             old_rev[-1]  != ' ' ||
5262             new_rev[-1]  != ' ' ||
5263             status[-1]   != ' ')
5264                 return FALSE;
5266         file->status = *status;
5268         string_copy_rev(file->old.rev, old_rev);
5269         string_copy_rev(file->new.rev, new_rev);
5271         file->old.mode = strtoul(old_mode, NULL, 8);
5272         file->new.mode = strtoul(new_mode, NULL, 8);
5274         file->old.name[0] = file->new.name[0] = 0;
5276         return TRUE;
5279 static bool
5280 status_run(struct view *view, const char *argv[], char status, enum line_type type)
5282         struct status *unmerged = NULL;
5283         char *buf;
5284         struct io io = {};
5286         if (!run_io(&io, argv, NULL, IO_RD))
5287                 return FALSE;
5289         add_line_data(view, NULL, type);
5291         while ((buf = io_get(&io, 0, TRUE))) {
5292                 struct status *file = unmerged;
5294                 if (!file) {
5295                         file = calloc(1, sizeof(*file));
5296                         if (!file || !add_line_data(view, file, type))
5297                                 goto error_out;
5298                 }
5300                 /* Parse diff info part. */
5301                 if (status) {
5302                         file->status = status;
5303                         if (status == 'A')
5304                                 string_copy(file->old.rev, NULL_ID);
5306                 } else if (!file->status || file == unmerged) {
5307                         if (!status_get_diff(file, buf, strlen(buf)))
5308                                 goto error_out;
5310                         buf = io_get(&io, 0, TRUE);
5311                         if (!buf)
5312                                 break;
5314                         /* Collapse all modified entries that follow an
5315                          * associated unmerged entry. */
5316                         if (unmerged == file) {
5317                                 unmerged->status = 'U';
5318                                 unmerged = NULL;
5319                         } else if (file->status == 'U') {
5320                                 unmerged = file;
5321                         }
5322                 }
5324                 /* Grab the old name for rename/copy. */
5325                 if (!*file->old.name &&
5326                     (file->status == 'R' || file->status == 'C')) {
5327                         string_ncopy(file->old.name, buf, strlen(buf));
5329                         buf = io_get(&io, 0, TRUE);
5330                         if (!buf)
5331                                 break;
5332                 }
5334                 /* git-ls-files just delivers a NUL separated list of
5335                  * file names similar to the second half of the
5336                  * git-diff-* output. */
5337                 string_ncopy(file->new.name, buf, strlen(buf));
5338                 if (!*file->old.name)
5339                         string_copy(file->old.name, file->new.name);
5340                 file = NULL;
5341         }
5343         if (io_error(&io)) {
5344 error_out:
5345                 done_io(&io);
5346                 return FALSE;
5347         }
5349         if (!view->line[view->lines - 1].data)
5350                 add_line_data(view, NULL, LINE_STAT_NONE);
5352         done_io(&io);
5353         return TRUE;
5356 /* Don't show unmerged entries in the staged section. */
5357 static const char *status_diff_index_argv[] = {
5358         "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
5359                              "--cached", "-M", "HEAD", NULL
5360 };
5362 static const char *status_diff_files_argv[] = {
5363         "git", "diff-files", "-z", NULL
5364 };
5366 static const char *status_list_other_argv[] = {
5367         "git", "ls-files", "-z", "--others", "--exclude-standard", NULL
5368 };
5370 static const char *status_list_no_head_argv[] = {
5371         "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
5372 };
5374 static const char *update_index_argv[] = {
5375         "git", "update-index", "-q", "--unmerged", "--refresh", NULL
5376 };
5378 /* Restore the previous line number to stay in the context or select a
5379  * line with something that can be updated. */
5380 static void
5381 status_restore(struct view *view)
5383         if (view->p_lineno >= view->lines)
5384                 view->p_lineno = view->lines - 1;
5385         while (view->p_lineno < view->lines && !view->line[view->p_lineno].data)
5386                 view->p_lineno++;
5387         while (view->p_lineno > 0 && !view->line[view->p_lineno].data)
5388                 view->p_lineno--;
5390         /* If the above fails, always skip the "On branch" line. */
5391         if (view->p_lineno < view->lines)
5392                 view->lineno = view->p_lineno;
5393         else
5394                 view->lineno = 1;
5396         if (view->lineno < view->offset)
5397                 view->offset = view->lineno;
5398         else if (view->offset + view->height <= view->lineno)
5399                 view->offset = view->lineno - view->height + 1;
5401         view->p_restore = FALSE;
5404 static void
5405 status_update_onbranch(void)
5407         static const char *paths[][2] = {
5408                 { "rebase-apply/rebasing",      "Rebasing" },
5409                 { "rebase-apply/applying",      "Applying mailbox" },
5410                 { "rebase-apply/",              "Rebasing mailbox" },
5411                 { "rebase-merge/interactive",   "Interactive rebase" },
5412                 { "rebase-merge/",              "Rebase merge" },
5413                 { "MERGE_HEAD",                 "Merging" },
5414                 { "BISECT_LOG",                 "Bisecting" },
5415                 { "HEAD",                       "On branch" },
5416         };
5417         char buf[SIZEOF_STR];
5418         struct stat stat;
5419         int i;
5421         if (is_initial_commit()) {
5422                 string_copy(status_onbranch, "Initial commit");
5423                 return;
5424         }
5426         for (i = 0; i < ARRAY_SIZE(paths); i++) {
5427                 char *head = opt_head;
5429                 if (!string_format(buf, "%s/%s", opt_git_dir, paths[i][0]) ||
5430                     lstat(buf, &stat) < 0)
5431                         continue;
5433                 if (!*opt_head) {
5434                         struct io io = {};
5436                         if (string_format(buf, "%s/rebase-merge/head-name", opt_git_dir) &&
5437                             io_open(&io, buf) &&
5438                             io_read_buf(&io, buf, sizeof(buf))) {
5439                                 head = buf;
5440                                 if (!prefixcmp(head, "refs/heads/"))
5441                                         head += STRING_SIZE("refs/heads/");
5442                         }
5443                 }
5445                 if (!string_format(status_onbranch, "%s %s", paths[i][1], head))
5446                         string_copy(status_onbranch, opt_head);
5447                 return;
5448         }
5450         string_copy(status_onbranch, "Not currently on any branch");
5453 /* First parse staged info using git-diff-index(1), then parse unstaged
5454  * info using git-diff-files(1), and finally untracked files using
5455  * git-ls-files(1). */
5456 static bool
5457 status_open(struct view *view)
5459         reset_view(view);
5461         add_line_data(view, NULL, LINE_STAT_HEAD);
5462         status_update_onbranch();
5464         run_io_bg(update_index_argv);
5466         if (is_initial_commit()) {
5467                 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
5468                         return FALSE;
5469         } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
5470                 return FALSE;
5471         }
5473         if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
5474             !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
5475                 return FALSE;
5477         /* Restore the exact position or use the specialized restore
5478          * mode? */
5479         if (!view->p_restore)
5480                 status_restore(view);
5481         return TRUE;
5484 static bool
5485 status_draw(struct view *view, struct line *line, unsigned int lineno)
5487         struct status *status = line->data;
5488         enum line_type type;
5489         const char *text;
5491         if (!status) {
5492                 switch (line->type) {
5493                 case LINE_STAT_STAGED:
5494                         type = LINE_STAT_SECTION;
5495                         text = "Changes to be committed:";
5496                         break;
5498                 case LINE_STAT_UNSTAGED:
5499                         type = LINE_STAT_SECTION;
5500                         text = "Changed but not updated:";
5501                         break;
5503                 case LINE_STAT_UNTRACKED:
5504                         type = LINE_STAT_SECTION;
5505                         text = "Untracked files:";
5506                         break;
5508                 case LINE_STAT_NONE:
5509                         type = LINE_DEFAULT;
5510                         text = "  (no files)";
5511                         break;
5513                 case LINE_STAT_HEAD:
5514                         type = LINE_STAT_HEAD;
5515                         text = status_onbranch;
5516                         break;
5518                 default:
5519                         return FALSE;
5520                 }
5521         } else {
5522                 static char buf[] = { '?', ' ', ' ', ' ', 0 };
5524                 buf[0] = status->status;
5525                 if (draw_text(view, line->type, buf, TRUE))
5526                         return TRUE;
5527                 type = LINE_DEFAULT;
5528                 text = status->new.name;
5529         }
5531         draw_text(view, type, text, TRUE);
5532         return TRUE;
5535 static enum request
5536 status_load_error(struct view *view, struct view *stage, const char *path)
5538         if (displayed_views() == 2 || display[current_view] != view)
5539                 maximize_view(view);
5540         report("Failed to load '%s': %s", path, io_strerror(&stage->io));
5541         return REQ_NONE;
5544 static enum request
5545 status_enter(struct view *view, struct line *line)
5547         struct status *status = line->data;
5548         const char *oldpath = status ? status->old.name : NULL;
5549         /* Diffs for unmerged entries are empty when passing the new
5550          * path, so leave it empty. */
5551         const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
5552         const char *info;
5553         enum open_flags split;
5554         struct view *stage = VIEW(REQ_VIEW_STAGE);
5556         if (line->type == LINE_STAT_NONE ||
5557             (!status && line[1].type == LINE_STAT_NONE)) {
5558                 report("No file to diff");
5559                 return REQ_NONE;
5560         }
5562         switch (line->type) {
5563         case LINE_STAT_STAGED:
5564                 if (is_initial_commit()) {
5565                         const char *no_head_diff_argv[] = {
5566                                 "git", "diff", "--no-color", "--patch-with-stat",
5567                                         "--", "/dev/null", newpath, NULL
5568                         };
5570                         if (!prepare_update(stage, no_head_diff_argv, opt_cdup, FORMAT_DASH))
5571                                 return status_load_error(view, stage, newpath);
5572                 } else {
5573                         const char *index_show_argv[] = {
5574                                 "git", "diff-index", "--root", "--patch-with-stat",
5575                                         "-C", "-M", "--cached", "HEAD", "--",
5576                                         oldpath, newpath, NULL
5577                         };
5579                         if (!prepare_update(stage, index_show_argv, opt_cdup, FORMAT_DASH))
5580                                 return status_load_error(view, stage, newpath);
5581                 }
5583                 if (status)
5584                         info = "Staged changes to %s";
5585                 else
5586                         info = "Staged changes";
5587                 break;
5589         case LINE_STAT_UNSTAGED:
5590         {
5591                 const char *files_show_argv[] = {
5592                         "git", "diff-files", "--root", "--patch-with-stat",
5593                                 "-C", "-M", "--", oldpath, newpath, NULL
5594                 };
5596                 if (!prepare_update(stage, files_show_argv, opt_cdup, FORMAT_DASH))
5597                         return status_load_error(view, stage, newpath);
5598                 if (status)
5599                         info = "Unstaged changes to %s";
5600                 else
5601                         info = "Unstaged changes";
5602                 break;
5603         }
5604         case LINE_STAT_UNTRACKED:
5605                 if (!newpath) {
5606                         report("No file to show");
5607                         return REQ_NONE;
5608                 }
5610                 if (!suffixcmp(status->new.name, -1, "/")) {
5611                         report("Cannot display a directory");
5612                         return REQ_NONE;
5613                 }
5615                 if (!prepare_update_file(stage, newpath))
5616                         return status_load_error(view, stage, newpath);
5617                 info = "Untracked file %s";
5618                 break;
5620         case LINE_STAT_HEAD:
5621                 return REQ_NONE;
5623         default:
5624                 die("line type %d not handled in switch", line->type);
5625         }
5627         split = view_is_displayed(view) ? OPEN_SPLIT : 0;
5628         open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split);
5629         if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
5630                 if (status) {
5631                         stage_status = *status;
5632                 } else {
5633                         memset(&stage_status, 0, sizeof(stage_status));
5634                 }
5636                 stage_line_type = line->type;
5637                 stage_chunks = 0;
5638                 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
5639         }
5641         return REQ_NONE;
5644 static bool
5645 status_exists(struct status *status, enum line_type type)
5647         struct view *view = VIEW(REQ_VIEW_STATUS);
5648         unsigned long lineno;
5650         for (lineno = 0; lineno < view->lines; lineno++) {
5651                 struct line *line = &view->line[lineno];
5652                 struct status *pos = line->data;
5654                 if (line->type != type)
5655                         continue;
5656                 if (!pos && (!status || !status->status) && line[1].data) {
5657                         select_view_line(view, lineno);
5658                         return TRUE;
5659                 }
5660                 if (pos && !strcmp(status->new.name, pos->new.name)) {
5661                         select_view_line(view, lineno);
5662                         return TRUE;
5663                 }
5664         }
5666         return FALSE;
5670 static bool
5671 status_update_prepare(struct io *io, enum line_type type)
5673         const char *staged_argv[] = {
5674                 "git", "update-index", "-z", "--index-info", NULL
5675         };
5676         const char *others_argv[] = {
5677                 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
5678         };
5680         switch (type) {
5681         case LINE_STAT_STAGED:
5682                 return run_io(io, staged_argv, opt_cdup, IO_WR);
5684         case LINE_STAT_UNSTAGED:
5685                 return run_io(io, others_argv, opt_cdup, IO_WR);
5687         case LINE_STAT_UNTRACKED:
5688                 return run_io(io, others_argv, NULL, IO_WR);
5690         default:
5691                 die("line type %d not handled in switch", type);
5692                 return FALSE;
5693         }
5696 static bool
5697 status_update_write(struct io *io, struct status *status, enum line_type type)
5699         char buf[SIZEOF_STR];
5700         size_t bufsize = 0;
5702         switch (type) {
5703         case LINE_STAT_STAGED:
5704                 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
5705                                         status->old.mode,
5706                                         status->old.rev,
5707                                         status->old.name, 0))
5708                         return FALSE;
5709                 break;
5711         case LINE_STAT_UNSTAGED:
5712         case LINE_STAT_UNTRACKED:
5713                 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
5714                         return FALSE;
5715                 break;
5717         default:
5718                 die("line type %d not handled in switch", type);
5719         }
5721         return io_write(io, buf, bufsize);
5724 static bool
5725 status_update_file(struct status *status, enum line_type type)
5727         struct io io = {};
5728         bool result;
5730         if (!status_update_prepare(&io, type))
5731                 return FALSE;
5733         result = status_update_write(&io, status, type);
5734         return done_io(&io) && result;
5737 static bool
5738 status_update_files(struct view *view, struct line *line)
5740         char buf[sizeof(view->ref)];
5741         struct io io = {};
5742         bool result = TRUE;
5743         struct line *pos = view->line + view->lines;
5744         int files = 0;
5745         int file, done;
5746         int cursor_y = -1, cursor_x = -1;
5748         if (!status_update_prepare(&io, line->type))
5749                 return FALSE;
5751         for (pos = line; pos < view->line + view->lines && pos->data; pos++)
5752                 files++;
5754         string_copy(buf, view->ref);
5755         getsyx(cursor_y, cursor_x);
5756         for (file = 0, done = 5; result && file < files; line++, file++) {
5757                 int almost_done = file * 100 / files;
5759                 if (almost_done > done) {
5760                         done = almost_done;
5761                         string_format(view->ref, "updating file %u of %u (%d%% done)",
5762                                       file, files, done);
5763                         update_view_title(view);
5764                         setsyx(cursor_y, cursor_x);
5765                         doupdate();
5766                 }
5767                 result = status_update_write(&io, line->data, line->type);
5768         }
5769         string_copy(view->ref, buf);
5771         return done_io(&io) && result;
5774 static bool
5775 status_update(struct view *view)
5777         struct line *line = &view->line[view->lineno];
5779         assert(view->lines);
5781         if (!line->data) {
5782                 /* This should work even for the "On branch" line. */
5783                 if (line < view->line + view->lines && !line[1].data) {
5784                         report("Nothing to update");
5785                         return FALSE;
5786                 }
5788                 if (!status_update_files(view, line + 1)) {
5789                         report("Failed to update file status");
5790                         return FALSE;
5791                 }
5793         } else if (!status_update_file(line->data, line->type)) {
5794                 report("Failed to update file status");
5795                 return FALSE;
5796         }
5798         return TRUE;
5801 static bool
5802 status_revert(struct status *status, enum line_type type, bool has_none)
5804         if (!status || type != LINE_STAT_UNSTAGED) {
5805                 if (type == LINE_STAT_STAGED) {
5806                         report("Cannot revert changes to staged files");
5807                 } else if (type == LINE_STAT_UNTRACKED) {
5808                         report("Cannot revert changes to untracked files");
5809                 } else if (has_none) {
5810                         report("Nothing to revert");
5811                 } else {
5812                         report("Cannot revert changes to multiple files");
5813                 }
5814                 return FALSE;
5816         } else {
5817                 char mode[10] = "100644";
5818                 const char *reset_argv[] = {
5819                         "git", "update-index", "--cacheinfo", mode,
5820                                 status->old.rev, status->old.name, NULL
5821                 };
5822                 const char *checkout_argv[] = {
5823                         "git", "checkout", "--", status->old.name, NULL
5824                 };
5826                 if (!prompt_yesno("Are you sure you want to overwrite any changes?"))
5827                         return FALSE;
5828                 string_format(mode, "%o", status->old.mode);
5829                 return (status->status != 'U' || run_io_fg(reset_argv, opt_cdup)) &&
5830                         run_io_fg(checkout_argv, opt_cdup);
5831         }
5834 static enum request
5835 status_request(struct view *view, enum request request, struct line *line)
5837         struct status *status = line->data;
5839         switch (request) {
5840         case REQ_STATUS_UPDATE:
5841                 if (!status_update(view))
5842                         return REQ_NONE;
5843                 break;
5845         case REQ_STATUS_REVERT:
5846                 if (!status_revert(status, line->type, status_has_none(view, line)))
5847                         return REQ_NONE;
5848                 break;
5850         case REQ_STATUS_MERGE:
5851                 if (!status || status->status != 'U') {
5852                         report("Merging only possible for files with unmerged status ('U').");
5853                         return REQ_NONE;
5854                 }
5855                 open_mergetool(status->new.name);
5856                 break;
5858         case REQ_EDIT:
5859                 if (!status)
5860                         return request;
5861                 if (status->status == 'D') {
5862                         report("File has been deleted.");
5863                         return REQ_NONE;
5864                 }
5866                 open_editor(status->status != '?', status->new.name);
5867                 break;
5869         case REQ_VIEW_BLAME:
5870                 if (status) {
5871                         string_copy(opt_file, status->new.name);
5872                         opt_ref[0] = 0;
5873                 }
5874                 return request;
5876         case REQ_ENTER:
5877                 /* After returning the status view has been split to
5878                  * show the stage view. No further reloading is
5879                  * necessary. */
5880                 return status_enter(view, line);
5882         case REQ_REFRESH:
5883                 /* Simply reload the view. */
5884                 break;
5886         default:
5887                 return request;
5888         }
5890         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
5892         return REQ_NONE;
5895 static void
5896 status_select(struct view *view, struct line *line)
5898         struct status *status = line->data;
5899         char file[SIZEOF_STR] = "all files";
5900         const char *text;
5901         const char *key;
5903         if (status && !string_format(file, "'%s'", status->new.name))
5904                 return;
5906         if (!status && line[1].type == LINE_STAT_NONE)
5907                 line++;
5909         switch (line->type) {
5910         case LINE_STAT_STAGED:
5911                 text = "Press %s to unstage %s for commit";
5912                 break;
5914         case LINE_STAT_UNSTAGED:
5915                 text = "Press %s to stage %s for commit";
5916                 break;
5918         case LINE_STAT_UNTRACKED:
5919                 text = "Press %s to stage %s for addition";
5920                 break;
5922         case LINE_STAT_HEAD:
5923         case LINE_STAT_NONE:
5924                 text = "Nothing to update";
5925                 break;
5927         default:
5928                 die("line type %d not handled in switch", line->type);
5929         }
5931         if (status && status->status == 'U') {
5932                 text = "Press %s to resolve conflict in %s";
5933                 key = get_key(KEYMAP_STATUS, REQ_STATUS_MERGE);
5935         } else {
5936                 key = get_key(KEYMAP_STATUS, REQ_STATUS_UPDATE);
5937         }
5939         string_format(view->ref, text, key, file);
5942 static bool
5943 status_grep(struct view *view, struct line *line)
5945         struct status *status = line->data;
5947         if (status) {
5948                 const char buf[2] = { status->status, 0 };
5949                 const char *text[] = { status->new.name, buf, NULL };
5951                 return grep_text(view, text);
5952         }
5954         return FALSE;
5957 static struct view_ops status_ops = {
5958         "file",
5959         NULL,
5960         status_open,
5961         NULL,
5962         status_draw,
5963         status_request,
5964         status_grep,
5965         status_select,
5966 };
5969 static bool
5970 stage_diff_write(struct io *io, struct line *line, struct line *end)
5972         while (line < end) {
5973                 if (!io_write(io, line->data, strlen(line->data)) ||
5974                     !io_write(io, "\n", 1))
5975                         return FALSE;
5976                 line++;
5977                 if (line->type == LINE_DIFF_CHUNK ||
5978                     line->type == LINE_DIFF_HEADER)
5979                         break;
5980         }
5982         return TRUE;
5985 static struct line *
5986 stage_diff_find(struct view *view, struct line *line, enum line_type type)
5988         for (; view->line < line; line--)
5989                 if (line->type == type)
5990                         return line;
5992         return NULL;
5995 static bool
5996 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
5998         const char *apply_argv[SIZEOF_ARG] = {
5999                 "git", "apply", "--whitespace=nowarn", NULL
6000         };
6001         struct line *diff_hdr;
6002         struct io io = {};
6003         int argc = 3;
6005         diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
6006         if (!diff_hdr)
6007                 return FALSE;
6009         if (!revert)
6010                 apply_argv[argc++] = "--cached";
6011         if (revert || stage_line_type == LINE_STAT_STAGED)
6012                 apply_argv[argc++] = "-R";
6013         apply_argv[argc++] = "-";
6014         apply_argv[argc++] = NULL;
6015         if (!run_io(&io, apply_argv, opt_cdup, IO_WR))
6016                 return FALSE;
6018         if (!stage_diff_write(&io, diff_hdr, chunk) ||
6019             !stage_diff_write(&io, chunk, view->line + view->lines))
6020                 chunk = NULL;
6022         done_io(&io);
6023         run_io_bg(update_index_argv);
6025         return chunk ? TRUE : FALSE;
6028 static bool
6029 stage_update(struct view *view, struct line *line)
6031         struct line *chunk = NULL;
6033         if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
6034                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6036         if (chunk) {
6037                 if (!stage_apply_chunk(view, chunk, FALSE)) {
6038                         report("Failed to apply chunk");
6039                         return FALSE;
6040                 }
6042         } else if (!stage_status.status) {
6043                 view = VIEW(REQ_VIEW_STATUS);
6045                 for (line = view->line; line < view->line + view->lines; line++)
6046                         if (line->type == stage_line_type)
6047                                 break;
6049                 if (!status_update_files(view, line + 1)) {
6050                         report("Failed to update files");
6051                         return FALSE;
6052                 }
6054         } else if (!status_update_file(&stage_status, stage_line_type)) {
6055                 report("Failed to update file");
6056                 return FALSE;
6057         }
6059         return TRUE;
6062 static bool
6063 stage_revert(struct view *view, struct line *line)
6065         struct line *chunk = NULL;
6067         if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
6068                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6070         if (chunk) {
6071                 if (!prompt_yesno("Are you sure you want to revert changes?"))
6072                         return FALSE;
6074                 if (!stage_apply_chunk(view, chunk, TRUE)) {
6075                         report("Failed to revert chunk");
6076                         return FALSE;
6077                 }
6078                 return TRUE;
6080         } else {
6081                 return status_revert(stage_status.status ? &stage_status : NULL,
6082                                      stage_line_type, FALSE);
6083         }
6087 static void
6088 stage_next(struct view *view, struct line *line)
6090         int i;
6092         if (!stage_chunks) {
6093                 for (line = view->line; line < view->line + view->lines; line++) {
6094                         if (line->type != LINE_DIFF_CHUNK)
6095                                 continue;
6097                         if (!realloc_ints(&stage_chunk, stage_chunks, 1)) {
6098                                 report("Allocation failure");
6099                                 return;
6100                         }
6102                         stage_chunk[stage_chunks++] = line - view->line;
6103                 }
6104         }
6106         for (i = 0; i < stage_chunks; i++) {
6107                 if (stage_chunk[i] > view->lineno) {
6108                         do_scroll_view(view, stage_chunk[i] - view->lineno);
6109                         report("Chunk %d of %d", i + 1, stage_chunks);
6110                         return;
6111                 }
6112         }
6114         report("No next chunk found");
6117 static enum request
6118 stage_request(struct view *view, enum request request, struct line *line)
6120         switch (request) {
6121         case REQ_STATUS_UPDATE:
6122                 if (!stage_update(view, line))
6123                         return REQ_NONE;
6124                 break;
6126         case REQ_STATUS_REVERT:
6127                 if (!stage_revert(view, line))
6128                         return REQ_NONE;
6129                 break;
6131         case REQ_STAGE_NEXT:
6132                 if (stage_line_type == LINE_STAT_UNTRACKED) {
6133                         report("File is untracked; press %s to add",
6134                                get_key(KEYMAP_STAGE, REQ_STATUS_UPDATE));
6135                         return REQ_NONE;
6136                 }
6137                 stage_next(view, line);
6138                 return REQ_NONE;
6140         case REQ_EDIT:
6141                 if (!stage_status.new.name[0])
6142                         return request;
6143                 if (stage_status.status == 'D') {
6144                         report("File has been deleted.");
6145                         return REQ_NONE;
6146                 }
6148                 open_editor(stage_status.status != '?', stage_status.new.name);
6149                 break;
6151         case REQ_REFRESH:
6152                 /* Reload everything ... */
6153                 break;
6155         case REQ_VIEW_BLAME:
6156                 if (stage_status.new.name[0]) {
6157                         string_copy(opt_file, stage_status.new.name);
6158                         opt_ref[0] = 0;
6159                 }
6160                 return request;
6162         case REQ_ENTER:
6163                 return pager_request(view, request, line);
6165         default:
6166                 return request;
6167         }
6169         VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
6170         open_view(view, REQ_VIEW_STATUS, OPEN_REFRESH);
6172         /* Check whether the staged entry still exists, and close the
6173          * stage view if it doesn't. */
6174         if (!status_exists(&stage_status, stage_line_type)) {
6175                 status_restore(VIEW(REQ_VIEW_STATUS));
6176                 return REQ_VIEW_CLOSE;
6177         }
6179         if (stage_line_type == LINE_STAT_UNTRACKED) {
6180                 if (!suffixcmp(stage_status.new.name, -1, "/")) {
6181                         report("Cannot display a directory");
6182                         return REQ_NONE;
6183                 }
6185                 if (!prepare_update_file(view, stage_status.new.name)) {
6186                         report("Failed to open file: %s", strerror(errno));
6187                         return REQ_NONE;
6188                 }
6189         }
6190         open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
6192         return REQ_NONE;
6195 static struct view_ops stage_ops = {
6196         "line",
6197         NULL,
6198         NULL,
6199         pager_read,
6200         pager_draw,
6201         stage_request,
6202         pager_grep,
6203         pager_select,
6204 };
6207 /*
6208  * Revision graph
6209  */
6211 struct commit {
6212         char id[SIZEOF_REV];            /* SHA1 ID. */
6213         char title[128];                /* First line of the commit message. */
6214         const char *author;             /* Author of the commit. */
6215         time_t time;                    /* Date from the author ident. */
6216         struct ref_list *refs;          /* Repository references. */
6217         chtype graph[SIZEOF_REVGRAPH];  /* Ancestry chain graphics. */
6218         size_t graph_size;              /* The width of the graph array. */
6219         bool has_parents;               /* Rewritten --parents seen. */
6220 };
6222 /* Size of rev graph with no  "padding" columns */
6223 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
6225 struct rev_graph {
6226         struct rev_graph *prev, *next, *parents;
6227         char rev[SIZEOF_REVITEMS][SIZEOF_REV];
6228         size_t size;
6229         struct commit *commit;
6230         size_t pos;
6231         unsigned int boundary:1;
6232 };
6234 /* Parents of the commit being visualized. */
6235 static struct rev_graph graph_parents[4];
6237 /* The current stack of revisions on the graph. */
6238 static struct rev_graph graph_stacks[4] = {
6239         { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
6240         { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
6241         { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
6242         { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
6243 };
6245 static inline bool
6246 graph_parent_is_merge(struct rev_graph *graph)
6248         return graph->parents->size > 1;
6251 static inline void
6252 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
6254         struct commit *commit = graph->commit;
6256         if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
6257                 commit->graph[commit->graph_size++] = symbol;
6260 static void
6261 clear_rev_graph(struct rev_graph *graph)
6263         graph->boundary = 0;
6264         graph->size = graph->pos = 0;
6265         graph->commit = NULL;
6266         memset(graph->parents, 0, sizeof(*graph->parents));
6269 static void
6270 done_rev_graph(struct rev_graph *graph)
6272         if (graph_parent_is_merge(graph) &&
6273             graph->pos < graph->size - 1 &&
6274             graph->next->size == graph->size + graph->parents->size - 1) {
6275                 size_t i = graph->pos + graph->parents->size - 1;
6277                 graph->commit->graph_size = i * 2;
6278                 while (i < graph->next->size - 1) {
6279                         append_to_rev_graph(graph, ' ');
6280                         append_to_rev_graph(graph, '\\');
6281                         i++;
6282                 }
6283         }
6285         clear_rev_graph(graph);
6288 static void
6289 push_rev_graph(struct rev_graph *graph, const char *parent)
6291         int i;
6293         /* "Collapse" duplicate parents lines.
6294          *
6295          * FIXME: This needs to also update update the drawn graph but
6296          * for now it just serves as a method for pruning graph lines. */
6297         for (i = 0; i < graph->size; i++)
6298                 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
6299                         return;
6301         if (graph->size < SIZEOF_REVITEMS) {
6302                 string_copy_rev(graph->rev[graph->size++], parent);
6303         }
6306 static chtype
6307 get_rev_graph_symbol(struct rev_graph *graph)
6309         chtype symbol;
6311         if (graph->boundary)
6312                 symbol = REVGRAPH_BOUND;
6313         else if (graph->parents->size == 0)
6314                 symbol = REVGRAPH_INIT;
6315         else if (graph_parent_is_merge(graph))
6316                 symbol = REVGRAPH_MERGE;
6317         else if (graph->pos >= graph->size)
6318                 symbol = REVGRAPH_BRANCH;
6319         else
6320                 symbol = REVGRAPH_COMMIT;
6322         return symbol;
6325 static void
6326 draw_rev_graph(struct rev_graph *graph)
6328         struct rev_filler {
6329                 chtype separator, line;
6330         };
6331         enum { DEFAULT, RSHARP, RDIAG, LDIAG };
6332         static struct rev_filler fillers[] = {
6333                 { ' ',  '|' },
6334                 { '`',  '.' },
6335                 { '\'', ' ' },
6336                 { '/',  ' ' },
6337         };
6338         chtype symbol = get_rev_graph_symbol(graph);
6339         struct rev_filler *filler;
6340         size_t i;
6342         if (opt_line_graphics)
6343                 fillers[DEFAULT].line = line_graphics[LINE_GRAPHIC_VLINE];
6345         filler = &fillers[DEFAULT];
6347         for (i = 0; i < graph->pos; i++) {
6348                 append_to_rev_graph(graph, filler->line);
6349                 if (graph_parent_is_merge(graph->prev) &&
6350                     graph->prev->pos == i)
6351                         filler = &fillers[RSHARP];
6353                 append_to_rev_graph(graph, filler->separator);
6354         }
6356         /* Place the symbol for this revision. */
6357         append_to_rev_graph(graph, symbol);
6359         if (graph->prev->size > graph->size)
6360                 filler = &fillers[RDIAG];
6361         else
6362                 filler = &fillers[DEFAULT];
6364         i++;
6366         for (; i < graph->size; i++) {
6367                 append_to_rev_graph(graph, filler->separator);
6368                 append_to_rev_graph(graph, filler->line);
6369                 if (graph_parent_is_merge(graph->prev) &&
6370                     i < graph->prev->pos + graph->parents->size)
6371                         filler = &fillers[RSHARP];
6372                 if (graph->prev->size > graph->size)
6373                         filler = &fillers[LDIAG];
6374         }
6376         if (graph->prev->size > graph->size) {
6377                 append_to_rev_graph(graph, filler->separator);
6378                 if (filler->line != ' ')
6379                         append_to_rev_graph(graph, filler->line);
6380         }
6383 /* Prepare the next rev graph */
6384 static void
6385 prepare_rev_graph(struct rev_graph *graph)
6387         size_t i;
6389         /* First, traverse all lines of revisions up to the active one. */
6390         for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
6391                 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
6392                         break;
6394                 push_rev_graph(graph->next, graph->rev[graph->pos]);
6395         }
6397         /* Interleave the new revision parent(s). */
6398         for (i = 0; !graph->boundary && i < graph->parents->size; i++)
6399                 push_rev_graph(graph->next, graph->parents->rev[i]);
6401         /* Lastly, put any remaining revisions. */
6402         for (i = graph->pos + 1; i < graph->size; i++)
6403                 push_rev_graph(graph->next, graph->rev[i]);
6406 static void
6407 update_rev_graph(struct view *view, struct rev_graph *graph)
6409         /* If this is the finalizing update ... */
6410         if (graph->commit)
6411                 prepare_rev_graph(graph);
6413         /* Graph visualization needs a one rev look-ahead,
6414          * so the first update doesn't visualize anything. */
6415         if (!graph->prev->commit)
6416                 return;
6418         if (view->lines > 2)
6419                 view->line[view->lines - 3].dirty = 1;
6420         if (view->lines > 1)
6421                 view->line[view->lines - 2].dirty = 1;
6422         draw_rev_graph(graph->prev);
6423         done_rev_graph(graph->prev->prev);
6427 /*
6428  * Main view backend
6429  */
6431 static const char *main_argv[SIZEOF_ARG] = {
6432         "git", "log", "--no-color", "--pretty=raw", "--parents",
6433                       "--topo-order", "%(head)", NULL
6434 };
6436 static bool
6437 main_draw(struct view *view, struct line *line, unsigned int lineno)
6439         struct commit *commit = line->data;
6441         if (!commit->author)
6442                 return FALSE;
6444         if (opt_date && draw_date(view, &commit->time))
6445                 return TRUE;
6447         if (opt_author && draw_author(view, commit->author))
6448                 return TRUE;
6450         if (opt_rev_graph && commit->graph_size &&
6451             draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
6452                 return TRUE;
6454         if (opt_show_refs && commit->refs) {
6455                 size_t i;
6457                 for (i = 0; i < commit->refs->size; i++) {
6458                         struct ref *ref = commit->refs->refs[i];
6459                         enum line_type type;
6461                         if (ref->head)
6462                                 type = LINE_MAIN_HEAD;
6463                         else if (ref->ltag)
6464                                 type = LINE_MAIN_LOCAL_TAG;
6465                         else if (ref->tag)
6466                                 type = LINE_MAIN_TAG;
6467                         else if (ref->tracked)
6468                                 type = LINE_MAIN_TRACKED;
6469                         else if (ref->remote)
6470                                 type = LINE_MAIN_REMOTE;
6471                         else
6472                                 type = LINE_MAIN_REF;
6474                         if (draw_text(view, type, "[", TRUE) ||
6475                             draw_text(view, type, ref->name, TRUE) ||
6476                             draw_text(view, type, "]", TRUE))
6477                                 return TRUE;
6479                         if (draw_text(view, LINE_DEFAULT, " ", TRUE))
6480                                 return TRUE;
6481                 }
6482         }
6484         draw_text(view, LINE_DEFAULT, commit->title, TRUE);
6485         return TRUE;
6488 /* Reads git log --pretty=raw output and parses it into the commit struct. */
6489 static bool
6490 main_read(struct view *view, char *line)
6492         static struct rev_graph *graph = graph_stacks;
6493         enum line_type type;
6494         struct commit *commit;
6496         if (!line) {
6497                 int i;
6499                 if (!view->lines && !view->parent)
6500                         die("No revisions match the given arguments.");
6501                 if (view->lines > 0) {
6502                         commit = view->line[view->lines - 1].data;
6503                         view->line[view->lines - 1].dirty = 1;
6504                         if (!commit->author) {
6505                                 view->lines--;
6506                                 free(commit);
6507                                 graph->commit = NULL;
6508                         }
6509                 }
6510                 update_rev_graph(view, graph);
6512                 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
6513                         clear_rev_graph(&graph_stacks[i]);
6514                 return TRUE;
6515         }
6517         type = get_line_type(line);
6518         if (type == LINE_COMMIT) {
6519                 commit = calloc(1, sizeof(struct commit));
6520                 if (!commit)
6521                         return FALSE;
6523                 line += STRING_SIZE("commit ");
6524                 if (*line == '-') {
6525                         graph->boundary = 1;
6526                         line++;
6527                 }
6529                 string_copy_rev(commit->id, line);
6530                 commit->refs = get_ref_list(commit->id);
6531                 graph->commit = commit;
6532                 add_line_data(view, commit, LINE_MAIN_COMMIT);
6534                 while ((line = strchr(line, ' '))) {
6535                         line++;
6536                         push_rev_graph(graph->parents, line);
6537                         commit->has_parents = TRUE;
6538                 }
6539                 return TRUE;
6540         }
6542         if (!view->lines)
6543                 return TRUE;
6544         commit = view->line[view->lines - 1].data;
6546         switch (type) {
6547         case LINE_PARENT:
6548                 if (commit->has_parents)
6549                         break;
6550                 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
6551                 break;
6553         case LINE_AUTHOR:
6554                 parse_author_line(line + STRING_SIZE("author "),
6555                                   &commit->author, &commit->time);
6556                 update_rev_graph(view, graph);
6557                 graph = graph->next;
6558                 break;
6560         default:
6561                 /* Fill in the commit title if it has not already been set. */
6562                 if (commit->title[0])
6563                         break;
6565                 /* Require titles to start with a non-space character at the
6566                  * offset used by git log. */
6567                 if (strncmp(line, "    ", 4))
6568                         break;
6569                 line += 4;
6570                 /* Well, if the title starts with a whitespace character,
6571                  * try to be forgiving.  Otherwise we end up with no title. */
6572                 while (isspace(*line))
6573                         line++;
6574                 if (*line == '\0')
6575                         break;
6576                 /* FIXME: More graceful handling of titles; append "..." to
6577                  * shortened titles, etc. */
6579                 string_expand(commit->title, sizeof(commit->title), line, 1);
6580                 view->line[view->lines - 1].dirty = 1;
6581         }
6583         return TRUE;
6586 static enum request
6587 main_request(struct view *view, enum request request, struct line *line)
6589         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
6591         switch (request) {
6592         case REQ_ENTER:
6593                 open_view(view, REQ_VIEW_DIFF, flags);
6594                 break;
6595         case REQ_REFRESH:
6596                 load_refs();
6597                 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
6598                 break;
6599         default:
6600                 return request;
6601         }
6603         return REQ_NONE;
6606 static bool
6607 grep_refs(struct ref_list *list, regex_t *regex)
6609         regmatch_t pmatch;
6610         size_t i;
6612         if (!opt_show_refs || !list)
6613                 return FALSE;
6615         for (i = 0; i < list->size; i++) {
6616                 if (regexec(regex, list->refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
6617                         return TRUE;
6618         }
6620         return FALSE;
6623 static bool
6624 main_grep(struct view *view, struct line *line)
6626         struct commit *commit = line->data;
6627         const char *text[] = {
6628                 commit->title,
6629                 opt_author ? commit->author : "",
6630                 opt_date ? mkdate(&commit->time) : "",
6631                 NULL
6632         };
6634         return grep_text(view, text) || grep_refs(commit->refs, view->regex);
6637 static void
6638 main_select(struct view *view, struct line *line)
6640         struct commit *commit = line->data;
6642         string_copy_rev(view->ref, commit->id);
6643         string_copy_rev(ref_commit, view->ref);
6646 static struct view_ops main_ops = {
6647         "commit",
6648         main_argv,
6649         NULL,
6650         main_read,
6651         main_draw,
6652         main_request,
6653         main_grep,
6654         main_select,
6655 };
6658 /*
6659  * Unicode / UTF-8 handling
6660  *
6661  * NOTE: Much of the following code for dealing with Unicode is derived from
6662  * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
6663  * src/intl/charset.c from the UTF-8 branch commit elinks-0.11.0-g31f2c28.
6664  */
6666 static inline int
6667 unicode_width(unsigned long c)
6669         if (c >= 0x1100 &&
6670            (c <= 0x115f                         /* Hangul Jamo */
6671             || c == 0x2329
6672             || c == 0x232a
6673             || (c >= 0x2e80  && c <= 0xa4cf && c != 0x303f)
6674                                                 /* CJK ... Yi */
6675             || (c >= 0xac00  && c <= 0xd7a3)    /* Hangul Syllables */
6676             || (c >= 0xf900  && c <= 0xfaff)    /* CJK Compatibility Ideographs */
6677             || (c >= 0xfe30  && c <= 0xfe6f)    /* CJK Compatibility Forms */
6678             || (c >= 0xff00  && c <= 0xff60)    /* Fullwidth Forms */
6679             || (c >= 0xffe0  && c <= 0xffe6)
6680             || (c >= 0x20000 && c <= 0x2fffd)
6681             || (c >= 0x30000 && c <= 0x3fffd)))
6682                 return 2;
6684         if (c == '\t')
6685                 return opt_tab_size;
6687         return 1;
6690 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
6691  * Illegal bytes are set one. */
6692 static const unsigned char utf8_bytes[256] = {
6693         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,
6694         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,
6695         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,
6696         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,
6697         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,
6698         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,
6699         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,
6700         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,
6701 };
6703 /* Decode UTF-8 multi-byte representation into a Unicode character. */
6704 static inline unsigned long
6705 utf8_to_unicode(const char *string, size_t length)
6707         unsigned long unicode;
6709         switch (length) {
6710         case 1:
6711                 unicode  =   string[0];
6712                 break;
6713         case 2:
6714                 unicode  =  (string[0] & 0x1f) << 6;
6715                 unicode +=  (string[1] & 0x3f);
6716                 break;
6717         case 3:
6718                 unicode  =  (string[0] & 0x0f) << 12;
6719                 unicode += ((string[1] & 0x3f) << 6);
6720                 unicode +=  (string[2] & 0x3f);
6721                 break;
6722         case 4:
6723                 unicode  =  (string[0] & 0x0f) << 18;
6724                 unicode += ((string[1] & 0x3f) << 12);
6725                 unicode += ((string[2] & 0x3f) << 6);
6726                 unicode +=  (string[3] & 0x3f);
6727                 break;
6728         case 5:
6729                 unicode  =  (string[0] & 0x0f) << 24;
6730                 unicode += ((string[1] & 0x3f) << 18);
6731                 unicode += ((string[2] & 0x3f) << 12);
6732                 unicode += ((string[3] & 0x3f) << 6);
6733                 unicode +=  (string[4] & 0x3f);
6734                 break;
6735         case 6:
6736                 unicode  =  (string[0] & 0x01) << 30;
6737                 unicode += ((string[1] & 0x3f) << 24);
6738                 unicode += ((string[2] & 0x3f) << 18);
6739                 unicode += ((string[3] & 0x3f) << 12);
6740                 unicode += ((string[4] & 0x3f) << 6);
6741                 unicode +=  (string[5] & 0x3f);
6742                 break;
6743         default:
6744                 die("Invalid Unicode length");
6745         }
6747         /* Invalid characters could return the special 0xfffd value but NUL
6748          * should be just as good. */
6749         return unicode > 0xffff ? 0 : unicode;
6752 /* Calculates how much of string can be shown within the given maximum width
6753  * and sets trimmed parameter to non-zero value if all of string could not be
6754  * shown. If the reserve flag is TRUE, it will reserve at least one
6755  * trailing character, which can be useful when drawing a delimiter.
6756  *
6757  * Returns the number of bytes to output from string to satisfy max_width. */
6758 static size_t
6759 utf8_length(const char **start, size_t skip, int *width, size_t max_width, int *trimmed, bool reserve)
6761         const char *string = *start;
6762         const char *end = strchr(string, '\0');
6763         unsigned char last_bytes = 0;
6764         size_t last_ucwidth = 0;
6766         *width = 0;
6767         *trimmed = 0;
6769         while (string < end) {
6770                 int c = *(unsigned char *) string;
6771                 unsigned char bytes = utf8_bytes[c];
6772                 size_t ucwidth;
6773                 unsigned long unicode;
6775                 if (string + bytes > end)
6776                         break;
6778                 /* Change representation to figure out whether
6779                  * it is a single- or double-width character. */
6781                 unicode = utf8_to_unicode(string, bytes);
6782                 /* FIXME: Graceful handling of invalid Unicode character. */
6783                 if (!unicode)
6784                         break;
6786                 ucwidth = unicode_width(unicode);
6787                 if (skip > 0) {
6788                         skip -= ucwidth <= skip ? ucwidth : skip;
6789                         *start += bytes;
6790                 }
6791                 *width  += ucwidth;
6792                 if (*width > max_width) {
6793                         *trimmed = 1;
6794                         *width -= ucwidth;
6795                         if (reserve && *width == max_width) {
6796                                 string -= last_bytes;
6797                                 *width -= last_ucwidth;
6798                         }
6799                         break;
6800                 }
6802                 string  += bytes;
6803                 last_bytes = ucwidth ? bytes : 0;
6804                 last_ucwidth = ucwidth;
6805         }
6807         return string - *start;
6811 /*
6812  * Status management
6813  */
6815 /* Whether or not the curses interface has been initialized. */
6816 static bool cursed = FALSE;
6818 /* Terminal hacks and workarounds. */
6819 static bool use_scroll_redrawwin;
6820 static bool use_scroll_status_wclear;
6822 /* The status window is used for polling keystrokes. */
6823 static WINDOW *status_win;
6825 /* Reading from the prompt? */
6826 static bool input_mode = FALSE;
6828 static bool status_empty = FALSE;
6830 /* Update status and title window. */
6831 static void
6832 report(const char *msg, ...)
6834         struct view *view = display[current_view];
6836         if (input_mode)
6837                 return;
6839         if (!view) {
6840                 char buf[SIZEOF_STR];
6841                 va_list args;
6843                 va_start(args, msg);
6844                 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
6845                         buf[sizeof(buf) - 1] = 0;
6846                         buf[sizeof(buf) - 2] = '.';
6847                         buf[sizeof(buf) - 3] = '.';
6848                         buf[sizeof(buf) - 4] = '.';
6849                 }
6850                 va_end(args);
6851                 die("%s", buf);
6852         }
6854         if (!status_empty || *msg) {
6855                 va_list args;
6857                 va_start(args, msg);
6859                 wmove(status_win, 0, 0);
6860                 if (view->has_scrolled && use_scroll_status_wclear)
6861                         wclear(status_win);
6862                 if (*msg) {
6863                         vwprintw(status_win, msg, args);
6864                         status_empty = FALSE;
6865                 } else {
6866                         status_empty = TRUE;
6867                 }
6868                 wclrtoeol(status_win);
6869                 wnoutrefresh(status_win);
6871                 va_end(args);
6872         }
6874         update_view_title(view);
6877 /* Controls when nodelay should be in effect when polling user input. */
6878 static void
6879 set_nonblocking_input(bool loading)
6881         static unsigned int loading_views;
6883         if ((loading == FALSE && loading_views-- == 1) ||
6884             (loading == TRUE  && loading_views++ == 0))
6885                 nodelay(status_win, loading);
6888 static void
6889 init_display(void)
6891         const char *term;
6892         int x, y;
6894         /* Initialize the curses library */
6895         if (isatty(STDIN_FILENO)) {
6896                 cursed = !!initscr();
6897                 opt_tty = stdin;
6898         } else {
6899                 /* Leave stdin and stdout alone when acting as a pager. */
6900                 opt_tty = fopen("/dev/tty", "r+");
6901                 if (!opt_tty)
6902                         die("Failed to open /dev/tty");
6903                 cursed = !!newterm(NULL, opt_tty, opt_tty);
6904         }
6906         if (!cursed)
6907                 die("Failed to initialize curses");
6909         nonl();         /* Disable conversion and detect newlines from input. */
6910         cbreak();       /* Take input chars one at a time, no wait for \n */
6911         noecho();       /* Don't echo input */
6912         leaveok(stdscr, FALSE);
6914         if (has_colors())
6915                 init_colors();
6917         getmaxyx(stdscr, y, x);
6918         status_win = newwin(1, 0, y - 1, 0);
6919         if (!status_win)
6920                 die("Failed to create status window");
6922         /* Enable keyboard mapping */
6923         keypad(status_win, TRUE);
6924         wbkgdset(status_win, get_line_attr(LINE_STATUS));
6926         TABSIZE = opt_tab_size;
6927         if (opt_line_graphics) {
6928                 line_graphics[LINE_GRAPHIC_VLINE] = ACS_VLINE;
6929         }
6931         term = getenv("XTERM_VERSION") ? NULL : getenv("COLORTERM");
6932         if (term && !strcmp(term, "gnome-terminal")) {
6933                 /* In the gnome-terminal-emulator, the message from
6934                  * scrolling up one line when impossible followed by
6935                  * scrolling down one line causes corruption of the
6936                  * status line. This is fixed by calling wclear. */
6937                 use_scroll_status_wclear = TRUE;
6938                 use_scroll_redrawwin = FALSE;
6940         } else if (term && !strcmp(term, "xrvt-xpm")) {
6941                 /* No problems with full optimizations in xrvt-(unicode)
6942                  * and aterm. */
6943                 use_scroll_status_wclear = use_scroll_redrawwin = FALSE;
6945         } else {
6946                 /* When scrolling in (u)xterm the last line in the
6947                  * scrolling direction will update slowly. */
6948                 use_scroll_redrawwin = TRUE;
6949                 use_scroll_status_wclear = FALSE;
6950         }
6953 static int
6954 get_input(int prompt_position)
6956         struct view *view;
6957         int i, key, cursor_y, cursor_x;
6959         if (prompt_position)
6960                 input_mode = TRUE;
6962         while (TRUE) {
6963                 foreach_view (view, i) {
6964                         update_view(view);
6965                         if (view_is_displayed(view) && view->has_scrolled &&
6966                             use_scroll_redrawwin)
6967                                 redrawwin(view->win);
6968                         view->has_scrolled = FALSE;
6969                 }
6971                 /* Update the cursor position. */
6972                 if (prompt_position) {
6973                         getbegyx(status_win, cursor_y, cursor_x);
6974                         cursor_x = prompt_position;
6975                 } else {
6976                         view = display[current_view];
6977                         getbegyx(view->win, cursor_y, cursor_x);
6978                         cursor_x = view->width - 1;
6979                         cursor_y += view->lineno - view->offset;
6980                 }
6981                 setsyx(cursor_y, cursor_x);
6983                 /* Refresh, accept single keystroke of input */
6984                 doupdate();
6985                 key = wgetch(status_win);
6987                 /* wgetch() with nodelay() enabled returns ERR when
6988                  * there's no input. */
6989                 if (key == ERR) {
6991                 } else if (key == KEY_RESIZE) {
6992                         int height, width;
6994                         getmaxyx(stdscr, height, width);
6996                         wresize(status_win, 1, width);
6997                         mvwin(status_win, height - 1, 0);
6998                         wnoutrefresh(status_win);
6999                         resize_display();
7000                         redraw_display(TRUE);
7002                 } else {
7003                         input_mode = FALSE;
7004                         return key;
7005                 }
7006         }
7009 static char *
7010 prompt_input(const char *prompt, input_handler handler, void *data)
7012         enum input_status status = INPUT_OK;
7013         static char buf[SIZEOF_STR];
7014         size_t pos = 0;
7016         buf[pos] = 0;
7018         while (status == INPUT_OK || status == INPUT_SKIP) {
7019                 int key;
7021                 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
7022                 wclrtoeol(status_win);
7024                 key = get_input(pos + 1);
7025                 switch (key) {
7026                 case KEY_RETURN:
7027                 case KEY_ENTER:
7028                 case '\n':
7029                         status = pos ? INPUT_STOP : INPUT_CANCEL;
7030                         break;
7032                 case KEY_BACKSPACE:
7033                         if (pos > 0)
7034                                 buf[--pos] = 0;
7035                         else
7036                                 status = INPUT_CANCEL;
7037                         break;
7039                 case KEY_ESC:
7040                         status = INPUT_CANCEL;
7041                         break;
7043                 default:
7044                         if (pos >= sizeof(buf)) {
7045                                 report("Input string too long");
7046                                 return NULL;
7047                         }
7049                         status = handler(data, buf, key);
7050                         if (status == INPUT_OK)
7051                                 buf[pos++] = (char) key;
7052                 }
7053         }
7055         /* Clear the status window */
7056         status_empty = FALSE;
7057         report("");
7059         if (status == INPUT_CANCEL)
7060                 return NULL;
7062         buf[pos++] = 0;
7064         return buf;
7067 static enum input_status
7068 prompt_yesno_handler(void *data, char *buf, int c)
7070         if (c == 'y' || c == 'Y')
7071                 return INPUT_STOP;
7072         if (c == 'n' || c == 'N')
7073                 return INPUT_CANCEL;
7074         return INPUT_SKIP;
7077 static bool
7078 prompt_yesno(const char *prompt)
7080         char prompt2[SIZEOF_STR];
7082         if (!string_format(prompt2, "%s [Yy/Nn]", prompt))
7083                 return FALSE;
7085         return !!prompt_input(prompt2, prompt_yesno_handler, NULL);
7088 static enum input_status
7089 read_prompt_handler(void *data, char *buf, int c)
7091         return isprint(c) ? INPUT_OK : INPUT_SKIP;
7094 static char *
7095 read_prompt(const char *prompt)
7097         return prompt_input(prompt, read_prompt_handler, NULL);
7100 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected)
7102         enum input_status status = INPUT_OK;
7103         int size = 0;
7105         while (items[size].text)
7106                 size++;
7108         while (status == INPUT_OK) {
7109                 const struct menu_item *item = &items[*selected];
7110                 int key;
7111                 int i;
7113                 mvwprintw(status_win, 0, 0, "%s (%d of %d) ",
7114                           prompt, *selected + 1, size);
7115                 if (item->hotkey)
7116                         wprintw(status_win, "[%c] ", (char) item->hotkey);
7117                 wprintw(status_win, "%s", item->text);
7118                 wclrtoeol(status_win);
7120                 key = get_input(COLS - 1);
7121                 switch (key) {
7122                 case KEY_RETURN:
7123                 case KEY_ENTER:
7124                 case '\n':
7125                         status = INPUT_STOP;
7126                         break;
7128                 case KEY_LEFT:
7129                 case KEY_UP:
7130                         *selected = *selected - 1;
7131                         if (*selected < 0)
7132                                 *selected = size - 1;
7133                         break;
7135                 case KEY_RIGHT:
7136                 case KEY_DOWN:
7137                         *selected = (*selected + 1) % size;
7138                         break;
7140                 case KEY_ESC:
7141                         status = INPUT_CANCEL;
7142                         break;
7144                 default:
7145                         for (i = 0; items[i].text; i++)
7146                                 if (items[i].hotkey == key) {
7147                                         *selected = i;
7148                                         status = INPUT_STOP;
7149                                         break;
7150                                 }
7151                 }
7152         }
7154         /* Clear the status window */
7155         status_empty = FALSE;
7156         report("");
7158         return status != INPUT_CANCEL;
7161 /*
7162  * Repository properties
7163  */
7165 static struct ref **refs = NULL;
7166 static size_t refs_size = 0;
7168 static struct ref_list **ref_lists = NULL;
7169 static size_t ref_lists_size = 0;
7171 DEFINE_ALLOCATOR(realloc_refs, struct ref *, 256)
7172 DEFINE_ALLOCATOR(realloc_refs_list, struct ref *, 8)
7173 DEFINE_ALLOCATOR(realloc_ref_lists, struct ref_list *, 8)
7175 static int
7176 compare_refs(const void *ref1_, const void *ref2_)
7178         const struct ref *ref1 = *(const struct ref **)ref1_;
7179         const struct ref *ref2 = *(const struct ref **)ref2_;
7181         if (ref1->tag != ref2->tag)
7182                 return ref2->tag - ref1->tag;
7183         if (ref1->ltag != ref2->ltag)
7184                 return ref2->ltag - ref2->ltag;
7185         if (ref1->head != ref2->head)
7186                 return ref2->head - ref1->head;
7187         if (ref1->tracked != ref2->tracked)
7188                 return ref2->tracked - ref1->tracked;
7189         if (ref1->remote != ref2->remote)
7190                 return ref2->remote - ref1->remote;
7191         return strcmp(ref1->name, ref2->name);
7194 static void
7195 foreach_ref(bool (*visitor)(void *data, struct ref *ref), void *data)
7197         size_t i;
7199         for (i = 0; i < refs_size; i++)
7200                 if (!visitor(data, refs[i]))
7201                         break;
7204 static struct ref_list *
7205 get_ref_list(const char *id)
7207         struct ref_list *list;
7208         size_t i;
7210         for (i = 0; i < ref_lists_size; i++)
7211                 if (!strcmp(id, ref_lists[i]->id))
7212                         return ref_lists[i];
7214         if (!realloc_ref_lists(&ref_lists, ref_lists_size, 1))
7215                 return NULL;
7216         list = calloc(1, sizeof(*list));
7217         if (!list)
7218                 return NULL;
7220         for (i = 0; i < refs_size; i++) {
7221                 if (!strcmp(id, refs[i]->id) &&
7222                     realloc_refs_list(&list->refs, list->size, 1))
7223                         list->refs[list->size++] = refs[i];
7224         }
7226         if (!list->refs) {
7227                 free(list);
7228                 return NULL;
7229         }
7231         qsort(list->refs, list->size, sizeof(*list->refs), compare_refs);
7232         ref_lists[ref_lists_size++] = list;
7233         return list;
7236 static int
7237 read_ref(char *id, size_t idlen, char *name, size_t namelen)
7239         struct ref *ref = NULL;
7240         bool tag = FALSE;
7241         bool ltag = FALSE;
7242         bool remote = FALSE;
7243         bool tracked = FALSE;
7244         bool head = FALSE;
7245         int from = 0, to = refs_size - 1;
7247         if (!prefixcmp(name, "refs/tags/")) {
7248                 if (!suffixcmp(name, namelen, "^{}")) {
7249                         namelen -= 3;
7250                         name[namelen] = 0;
7251                 } else {
7252                         ltag = TRUE;
7253                 }
7255                 tag = TRUE;
7256                 namelen -= STRING_SIZE("refs/tags/");
7257                 name    += STRING_SIZE("refs/tags/");
7259         } else if (!prefixcmp(name, "refs/remotes/")) {
7260                 remote = TRUE;
7261                 namelen -= STRING_SIZE("refs/remotes/");
7262                 name    += STRING_SIZE("refs/remotes/");
7263                 tracked  = !strcmp(opt_remote, name);
7265         } else if (!prefixcmp(name, "refs/heads/")) {
7266                 namelen -= STRING_SIZE("refs/heads/");
7267                 name    += STRING_SIZE("refs/heads/");
7268                 head     = !strncmp(opt_head, name, namelen);
7270         } else if (!strcmp(name, "HEAD")) {
7271                 string_ncopy(opt_head_rev, id, idlen);
7272                 return OK;
7273         }
7275         /* If we are reloading or it's an annotated tag, replace the
7276          * previous SHA1 with the resolved commit id; relies on the fact
7277          * git-ls-remote lists the commit id of an annotated tag right
7278          * before the commit id it points to. */
7279         while (from <= to) {
7280                 size_t pos = (to + from) / 2;
7281                 int cmp = strcmp(name, refs[pos]->name);
7283                 if (!cmp) {
7284                         ref = refs[pos];
7285                         break;
7286                 }
7288                 if (cmp < 0)
7289                         to = pos - 1;
7290                 else
7291                         from = pos + 1;
7292         }
7294         if (!ref) {
7295                 if (!realloc_refs(&refs, refs_size, 1))
7296                         return ERR;
7297                 ref = calloc(1, sizeof(*ref) + namelen);
7298                 if (!ref)
7299                         return ERR;
7300                 memmove(refs + from + 1, refs + from,
7301                         (refs_size - from) * sizeof(*refs));
7302                 refs[from] = ref;
7303                 strncpy(ref->name, name, namelen);
7304                 refs_size++;
7305         }
7307         ref->head = head;
7308         ref->tag = tag;
7309         ref->ltag = ltag;
7310         ref->remote = remote;
7311         ref->tracked = tracked;
7312         string_copy_rev(ref->id, id);
7314         return OK;
7317 static int
7318 load_refs(void)
7320         const char *head_argv[] = {
7321                 "git", "symbolic-ref", "HEAD", NULL
7322         };
7323         static const char *ls_remote_argv[SIZEOF_ARG] = {
7324                 "git", "ls-remote", opt_git_dir, NULL
7325         };
7326         static bool init = FALSE;
7327         size_t i;
7329         if (!init) {
7330                 argv_from_env(ls_remote_argv, "TIG_LS_REMOTE");
7331                 init = TRUE;
7332         }
7334         if (!*opt_git_dir)
7335                 return OK;
7337         if (run_io_buf(head_argv, opt_head, sizeof(opt_head)) &&
7338             !prefixcmp(opt_head, "refs/heads/")) {
7339                 char *offset = opt_head + STRING_SIZE("refs/heads/");
7341                 memmove(opt_head, offset, strlen(offset) + 1);
7342         }
7344         for (i = 0; i < refs_size; i++)
7345                 refs[i]->id[0] = 0;
7347         if (run_io_load(ls_remote_argv, "\t", read_ref) == ERR)
7348                 return ERR;
7350         /* Update the ref lists to reflect changes. */
7351         for (i = 0; i < ref_lists_size; i++) {
7352                 struct ref_list *list = ref_lists[i];
7353                 size_t old, new;
7355                 for (old = new = 0; old < list->size; old++)
7356                         if (!strcmp(list->id, list->refs[old]->id))
7357                                 list->refs[new++] = list->refs[old];
7358                 list->size = new;
7359         }
7361         return OK;
7364 static void
7365 set_remote_branch(const char *name, const char *value, size_t valuelen)
7367         if (!strcmp(name, ".remote")) {
7368                 string_ncopy(opt_remote, value, valuelen);
7370         } else if (*opt_remote && !strcmp(name, ".merge")) {
7371                 size_t from = strlen(opt_remote);
7373                 if (!prefixcmp(value, "refs/heads/"))
7374                         value += STRING_SIZE("refs/heads/");
7376                 if (!string_format_from(opt_remote, &from, "/%s", value))
7377                         opt_remote[0] = 0;
7378         }
7381 static void
7382 set_repo_config_option(char *name, char *value, int (*cmd)(int, const char **))
7384         const char *argv[SIZEOF_ARG] = { name, "=" };
7385         int argc = 1 + (cmd == option_set_command);
7386         int error = ERR;
7388         if (!argv_from_string(argv, &argc, value))
7389                 config_msg = "Too many option arguments";
7390         else
7391                 error = cmd(argc, argv);
7393         if (error == ERR)
7394                 warn("Option 'tig.%s': %s", name, config_msg);
7397 static bool
7398 set_environment_variable(const char *name, const char *value)
7400         size_t len = strlen(name) + 1 + strlen(value) + 1;
7401         char *env = malloc(len);
7403         if (env &&
7404             string_nformat(env, len, NULL, "%s=%s", name, value) &&
7405             putenv(env) == 0)
7406                 return TRUE;
7407         free(env);
7408         return FALSE;
7411 static void
7412 set_work_tree(const char *value)
7414         char cwd[SIZEOF_STR];
7416         if (!getcwd(cwd, sizeof(cwd)))
7417                 die("Failed to get cwd path: %s", strerror(errno));
7418         if (chdir(opt_git_dir) < 0)
7419                 die("Failed to chdir(%s): %s", strerror(errno));
7420         if (!getcwd(opt_git_dir, sizeof(opt_git_dir)))
7421                 die("Failed to get git path: %s", strerror(errno));
7422         if (chdir(cwd) < 0)
7423                 die("Failed to chdir(%s): %s", cwd, strerror(errno));
7424         if (chdir(value) < 0)
7425                 die("Failed to chdir(%s): %s", value, strerror(errno));
7426         if (!getcwd(cwd, sizeof(cwd)))
7427                 die("Failed to get cwd path: %s", strerror(errno));
7428         if (!set_environment_variable("GIT_WORK_TREE", cwd))
7429                 die("Failed to set GIT_WORK_TREE to '%s'", cwd);
7430         if (!set_environment_variable("GIT_DIR", opt_git_dir))
7431                 die("Failed to set GIT_DIR to '%s'", opt_git_dir);
7432         opt_is_inside_work_tree = TRUE;
7435 static int
7436 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
7438         if (!strcmp(name, "i18n.commitencoding"))
7439                 string_ncopy(opt_encoding, value, valuelen);
7441         else if (!strcmp(name, "core.editor"))
7442                 string_ncopy(opt_editor, value, valuelen);
7444         else if (!strcmp(name, "core.worktree"))
7445                 set_work_tree(value);
7447         else if (!prefixcmp(name, "tig.color."))
7448                 set_repo_config_option(name + 10, value, option_color_command);
7450         else if (!prefixcmp(name, "tig.bind."))
7451                 set_repo_config_option(name + 9, value, option_bind_command);
7453         else if (!prefixcmp(name, "tig."))
7454                 set_repo_config_option(name + 4, value, option_set_command);
7456         else if (*opt_head && !prefixcmp(name, "branch.") &&
7457                  !strncmp(name + 7, opt_head, strlen(opt_head)))
7458                 set_remote_branch(name + 7 + strlen(opt_head), value, valuelen);
7460         return OK;
7463 static int
7464 load_git_config(void)
7466         const char *config_list_argv[] = { "git", "config", "--list", NULL };
7468         return run_io_load(config_list_argv, "=", read_repo_config_option);
7471 static int
7472 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
7474         if (!opt_git_dir[0]) {
7475                 string_ncopy(opt_git_dir, name, namelen);
7477         } else if (opt_is_inside_work_tree == -1) {
7478                 /* This can be 3 different values depending on the
7479                  * version of git being used. If git-rev-parse does not
7480                  * understand --is-inside-work-tree it will simply echo
7481                  * the option else either "true" or "false" is printed.
7482                  * Default to true for the unknown case. */
7483                 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
7485         } else if (*name == '.') {
7486                 string_ncopy(opt_cdup, name, namelen);
7488         } else {
7489                 string_ncopy(opt_prefix, name, namelen);
7490         }
7492         return OK;
7495 static int
7496 load_repo_info(void)
7498         const char *rev_parse_argv[] = {
7499                 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
7500                         "--show-cdup", "--show-prefix", NULL
7501         };
7503         return run_io_load(rev_parse_argv, "=", read_repo_info);
7507 /*
7508  * Main
7509  */
7511 static const char usage[] =
7512 "tig " TIG_VERSION " (" __DATE__ ")\n"
7513 "\n"
7514 "Usage: tig        [options] [revs] [--] [paths]\n"
7515 "   or: tig show   [options] [revs] [--] [paths]\n"
7516 "   or: tig blame  [rev] path\n"
7517 "   or: tig status\n"
7518 "   or: tig <      [git command output]\n"
7519 "\n"
7520 "Options:\n"
7521 "  -v, --version   Show version and exit\n"
7522 "  -h, --help      Show help message and exit";
7524 static void __NORETURN
7525 quit(int sig)
7527         /* XXX: Restore tty modes and let the OS cleanup the rest! */
7528         if (cursed)
7529                 endwin();
7530         exit(0);
7533 static void __NORETURN
7534 die(const char *err, ...)
7536         va_list args;
7538         endwin();
7540         va_start(args, err);
7541         fputs("tig: ", stderr);
7542         vfprintf(stderr, err, args);
7543         fputs("\n", stderr);
7544         va_end(args);
7546         exit(1);
7549 static void
7550 warn(const char *msg, ...)
7552         va_list args;
7554         va_start(args, msg);
7555         fputs("tig warning: ", stderr);
7556         vfprintf(stderr, msg, args);
7557         fputs("\n", stderr);
7558         va_end(args);
7561 static enum request
7562 parse_options(int argc, const char *argv[])
7564         enum request request = REQ_VIEW_MAIN;
7565         const char *subcommand;
7566         bool seen_dashdash = FALSE;
7567         /* XXX: This is vulnerable to the user overriding options
7568          * required for the main view parser. */
7569         const char *custom_argv[SIZEOF_ARG] = {
7570                 "git", "log", "--no-color", "--pretty=raw", "--parents",
7571                         "--topo-order", NULL
7572         };
7573         int i, j = 6;
7575         if (!isatty(STDIN_FILENO)) {
7576                 io_open(&VIEW(REQ_VIEW_PAGER)->io, "");
7577                 return REQ_VIEW_PAGER;
7578         }
7580         if (argc <= 1)
7581                 return REQ_NONE;
7583         subcommand = argv[1];
7584         if (!strcmp(subcommand, "status")) {
7585                 if (argc > 2)
7586                         warn("ignoring arguments after `%s'", subcommand);
7587                 return REQ_VIEW_STATUS;
7589         } else if (!strcmp(subcommand, "blame")) {
7590                 if (argc <= 2 || argc > 4)
7591                         die("invalid number of options to blame\n\n%s", usage);
7593                 i = 2;
7594                 if (argc == 4) {
7595                         string_ncopy(opt_ref, argv[i], strlen(argv[i]));
7596                         i++;
7597                 }
7599                 string_ncopy(opt_file, argv[i], strlen(argv[i]));
7600                 return REQ_VIEW_BLAME;
7602         } else if (!strcmp(subcommand, "show")) {
7603                 request = REQ_VIEW_DIFF;
7605         } else {
7606                 subcommand = NULL;
7607         }
7609         if (subcommand) {
7610                 custom_argv[1] = subcommand;
7611                 j = 2;
7612         }
7614         for (i = 1 + !!subcommand; i < argc; i++) {
7615                 const char *opt = argv[i];
7617                 if (seen_dashdash || !strcmp(opt, "--")) {
7618                         seen_dashdash = TRUE;
7620                 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
7621                         printf("tig version %s\n", TIG_VERSION);
7622                         quit(0);
7624                 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
7625                         printf("%s\n", usage);
7626                         quit(0);
7627                 }
7629                 custom_argv[j++] = opt;
7630                 if (j >= ARRAY_SIZE(custom_argv))
7631                         die("command too long");
7632         }
7634         if (!prepare_update(VIEW(request), custom_argv, NULL, FORMAT_NONE))
7635                 die("Failed to format arguments");
7637         return request;
7640 int
7641 main(int argc, const char *argv[])
7643         enum request request = parse_options(argc, argv);
7644         struct view *view;
7645         size_t i;
7647         signal(SIGINT, quit);
7648         signal(SIGPIPE, SIG_IGN);
7650         if (setlocale(LC_ALL, "")) {
7651                 char *codeset = nl_langinfo(CODESET);
7653                 string_ncopy(opt_codeset, codeset, strlen(codeset));
7654         }
7656         if (load_repo_info() == ERR)
7657                 die("Failed to load repo info.");
7659         if (load_options() == ERR)
7660                 die("Failed to load user config.");
7662         if (load_git_config() == ERR)
7663                 die("Failed to load repo config.");
7665         /* Require a git repository unless when running in pager mode. */
7666         if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
7667                 die("Not a git repository");
7669         if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
7670                 opt_utf8 = FALSE;
7672         if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
7673                 opt_iconv = iconv_open(opt_codeset, opt_encoding);
7674                 if (opt_iconv == ICONV_NONE)
7675                         die("Failed to initialize character set conversion");
7676         }
7678         if (load_refs() == ERR)
7679                 die("Failed to load refs.");
7681         foreach_view (view, i)
7682                 argv_from_env(view->ops->argv, view->cmd_env);
7684         init_display();
7686         if (request != REQ_NONE)
7687                 open_view(NULL, request, OPEN_PREPARED);
7688         request = request == REQ_NONE ? REQ_VIEW_MAIN : REQ_NONE;
7690         while (view_driver(display[current_view], request)) {
7691                 int key = get_input(0);
7693                 view = display[current_view];
7694                 request = get_keybinding(view->keymap, key);
7696                 /* Some low-level request handling. This keeps access to
7697                  * status_win restricted. */
7698                 switch (request) {
7699                 case REQ_PROMPT:
7700                 {
7701                         char *cmd = read_prompt(":");
7703                         if (cmd && isdigit(*cmd)) {
7704                                 int lineno = view->lineno + 1;
7706                                 if (parse_int(&lineno, cmd, 1, view->lines + 1) == OK) {
7707                                         select_view_line(view, lineno - 1);
7708                                         report("");
7709                                 } else {
7710                                         report("Unable to parse '%s' as a line number", cmd);
7711                                 }
7713                         } else if (cmd) {
7714                                 struct view *next = VIEW(REQ_VIEW_PAGER);
7715                                 const char *argv[SIZEOF_ARG] = { "git" };
7716                                 int argc = 1;
7718                                 /* When running random commands, initially show the
7719                                  * command in the title. However, it maybe later be
7720                                  * overwritten if a commit line is selected. */
7721                                 string_ncopy(next->ref, cmd, strlen(cmd));
7723                                 if (!argv_from_string(argv, &argc, cmd)) {
7724                                         report("Too many arguments");
7725                                 } else if (!prepare_update(next, argv, NULL, FORMAT_DASH)) {
7726                                         report("Failed to format command");
7727                                 } else {
7728                                         open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
7729                                 }
7730                         }
7732                         request = REQ_NONE;
7733                         break;
7734                 }
7735                 case REQ_SEARCH:
7736                 case REQ_SEARCH_BACK:
7737                 {
7738                         const char *prompt = request == REQ_SEARCH ? "/" : "?";
7739                         char *search = read_prompt(prompt);
7741                         if (search)
7742                                 string_ncopy(opt_search, search, strlen(search));
7743                         else if (*opt_search)
7744                                 request = request == REQ_SEARCH ?
7745                                         REQ_FIND_NEXT :
7746                                         REQ_FIND_PREV;
7747                         else
7748                                 request = REQ_NONE;
7749                         break;
7750                 }
7751                 default:
7752                         break;
7753                 }
7754         }
7756         quit(0);
7758         return 0;