Code

Add support for displaying relative dates
[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 #ifndef GIT_CONFIG
118 #define GIT_CONFIG "config"
119 #endif
121 /* Some ASCII-shorthands fitted into the ncurses namespace. */
122 #define KEY_TAB         '\t'
123 #define KEY_RETURN      '\r'
124 #define KEY_ESC         27
127 struct ref {
128         char id[SIZEOF_REV];    /* Commit SHA1 ID */
129         unsigned int head:1;    /* Is it the current HEAD? */
130         unsigned int tag:1;     /* Is it a tag? */
131         unsigned int ltag:1;    /* If so, is the tag local? */
132         unsigned int remote:1;  /* Is it a remote ref? */
133         unsigned int tracked:1; /* Is it the remote for the current HEAD? */
134         char name[1];           /* Ref name; tag or head names are shortened. */
135 };
137 struct ref_list {
138         char id[SIZEOF_REV];    /* Commit SHA1 ID */
139         size_t size;            /* Number of refs. */
140         struct ref **refs;      /* References for this ID. */
141 };
143 static struct ref_list *get_ref_list(const char *id);
144 static void foreach_ref(bool (*visitor)(void *data, struct ref *ref), void *data);
145 static int load_refs(void);
147 enum format_flags {
148         FORMAT_ALL,             /* Perform replacement in all arguments. */
149         FORMAT_DASH,            /* Perform replacement up until "--". */
150         FORMAT_NONE             /* No replacement should be performed. */
151 };
153 static bool format_argv(const char *dst[], const char *src[], enum format_flags flags);
155 enum input_status {
156         INPUT_OK,
157         INPUT_SKIP,
158         INPUT_STOP,
159         INPUT_CANCEL
160 };
162 typedef enum input_status (*input_handler)(void *data, char *buf, int c);
164 static char *prompt_input(const char *prompt, input_handler handler, void *data);
165 static bool prompt_yesno(const char *prompt);
167 struct menu_item {
168         int hotkey;
169         const char *text;
170         void *data;
171 };
173 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected);
175 /*
176  * Allocation helpers ... Entering macro hell to never be seen again.
177  */
179 #define DEFINE_ALLOCATOR(name, type, chunk_size)                                \
180 static type *                                                                   \
181 name(type **mem, size_t size, size_t increase)                                  \
182 {                                                                               \
183         size_t num_chunks = (size + chunk_size - 1) / chunk_size;               \
184         size_t num_chunks_new = (size + increase + chunk_size - 1) / chunk_size;\
185         type *tmp = *mem;                                                       \
186                                                                                 \
187         if (mem == NULL || num_chunks != num_chunks_new) {                      \
188                 tmp = realloc(tmp, num_chunks_new * chunk_size * sizeof(type)); \
189                 if (tmp)                                                        \
190                         *mem = tmp;                                             \
191         }                                                                       \
192                                                                                 \
193         return tmp;                                                             \
196 /*
197  * String helpers
198  */
200 static inline void
201 string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t srclen)
203         if (srclen > dstlen - 1)
204                 srclen = dstlen - 1;
206         strncpy(dst, src, srclen);
207         dst[srclen] = 0;
210 /* Shorthands for safely copying into a fixed buffer. */
212 #define string_copy(dst, src) \
213         string_ncopy_do(dst, sizeof(dst), src, sizeof(src))
215 #define string_ncopy(dst, src, srclen) \
216         string_ncopy_do(dst, sizeof(dst), src, srclen)
218 #define string_copy_rev(dst, src) \
219         string_ncopy_do(dst, SIZEOF_REV, src, SIZEOF_REV - 1)
221 #define string_add(dst, from, src) \
222         string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src))
224 static void
225 string_expand(char *dst, size_t dstlen, const char *src, int tabsize)
227         size_t size, pos;
229         for (size = pos = 0; size < dstlen - 1 && src[pos]; pos++) {
230                 if (src[pos] == '\t') {
231                         size_t expanded = tabsize - (size % tabsize);
233                         if (expanded + size >= dstlen - 1)
234                                 expanded = dstlen - size - 1;
235                         memcpy(dst + size, "        ", expanded);
236                         size += expanded;
237                 } else {
238                         dst[size++] = src[pos];
239                 }
240         }
242         dst[size] = 0;
245 static char *
246 chomp_string(char *name)
248         int namelen;
250         while (isspace(*name))
251                 name++;
253         namelen = strlen(name) - 1;
254         while (namelen > 0 && isspace(name[namelen]))
255                 name[namelen--] = 0;
257         return name;
260 static bool
261 string_nformat(char *buf, size_t bufsize, size_t *bufpos, const char *fmt, ...)
263         va_list args;
264         size_t pos = bufpos ? *bufpos : 0;
266         va_start(args, fmt);
267         pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
268         va_end(args);
270         if (bufpos)
271                 *bufpos = pos;
273         return pos >= bufsize ? FALSE : TRUE;
276 #define string_format(buf, fmt, args...) \
277         string_nformat(buf, sizeof(buf), NULL, fmt, args)
279 #define string_format_from(buf, from, fmt, args...) \
280         string_nformat(buf, sizeof(buf), from, fmt, args)
282 static int
283 string_enum_compare(const char *str1, const char *str2, int len)
285         size_t i;
287 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
289         /* Diff-Header == DIFF_HEADER */
290         for (i = 0; i < len; i++) {
291                 if (toupper(str1[i]) == toupper(str2[i]))
292                         continue;
294                 if (string_enum_sep(str1[i]) &&
295                     string_enum_sep(str2[i]))
296                         continue;
298                 return str1[i] - str2[i];
299         }
301         return 0;
304 struct enum_map {
305         const char *name;
306         int namelen;
307         int value;
308 };
310 #define ENUM_MAP(name, value) { name, STRING_SIZE(name), value }
312 static bool
313 map_enum_do(const struct enum_map *map, size_t map_size, int *value, const char *name)
315         size_t namelen = strlen(name);
316         int i;
318         for (i = 0; i < map_size; i++)
319                 if (namelen == map[i].namelen &&
320                     !string_enum_compare(name, map[i].name, namelen)) {
321                         *value = map[i].value;
322                         return TRUE;
323                 }
325         return FALSE;
328 #define map_enum(attr, map, name) \
329         map_enum_do(map, ARRAY_SIZE(map), attr, name)
331 #define prefixcmp(str1, str2) \
332         strncmp(str1, str2, STRING_SIZE(str2))
334 static inline int
335 suffixcmp(const char *str, int slen, const char *suffix)
337         size_t len = slen >= 0 ? slen : strlen(str);
338         size_t suffixlen = strlen(suffix);
340         return suffixlen < len ? strcmp(str + len - suffixlen, suffix) : -1;
344 /*
345  * What value of "tz" was in effect back then at "time" in the
346  * local timezone?
347  */
348 static int local_tzoffset(time_t time)
350         time_t t, t_local;
351         struct tm tm;
352         int offset, eastwest; 
354         t = time;
355         localtime_r(&t, &tm);
356         t_local = mktime(&tm);
358         if (t_local < t) {
359                 eastwest = -1;
360                 offset = t - t_local;
361         } else {
362                 eastwest = 1;
363                 offset = t_local - t;
364         }
365         offset /= 60; /* in minutes */
366         offset = (offset % 60) + ((offset / 60) * 100);
367         return offset * eastwest;
370 enum date {
371         DATE_NONE = 0,
372         DATE_DEFAULT,
373         DATE_RELATIVE,
374         DATE_SHORT
375 };
377 static char *
378 string_date(const time_t *time, enum date date)
380         static char buf[DATE_COLS + 1];
381         static const struct enum_map reldate[] = {
382                 { "second", 1,                  60 * 2 },
383                 { "minute", 60,                 60 * 60 * 2 },
384                 { "hour",   60 * 60,            60 * 60 * 24 * 2 },
385                 { "day",    60 * 60 * 24,       60 * 60 * 24 * 7 * 2 },
386                 { "week",   60 * 60 * 24 * 7,   60 * 60 * 24 * 7 * 5 },
387                 { "month",  60 * 60 * 24 * 30,  60 * 60 * 24 * 30 * 12 },
388         };
389         struct tm tm;
391         if (date == DATE_RELATIVE) {
392                 struct timeval now;
393                 time_t date = *time + local_tzoffset(*time);
394                 time_t seconds;
395                 int i;
397                 gettimeofday(&now, NULL);
398                 seconds = now.tv_sec < date ? date - now.tv_sec : now.tv_sec - date;
399                 for (i = 0; i < ARRAY_SIZE(reldate); i++) {
400                         if (seconds >= reldate[i].value)
401                                 continue;
403                         seconds /= reldate[i].namelen;
404                         if (!string_format(buf, "%ld %s%s %s",
405                                            seconds, reldate[i].name,
406                                            seconds > 1 ? "s" : "",
407                                            now.tv_sec >= date ? "ago" : "ahead"))
408                                 break;
409                         return buf;
410                 }
411         }
413         gmtime_r(time, &tm);
414         return strftime(buf, sizeof(buf), DATE_FORMAT, &tm) ? buf : NULL;
418 static bool
419 argv_from_string(const char *argv[SIZEOF_ARG], int *argc, char *cmd)
421         int valuelen;
423         while (*cmd && *argc < SIZEOF_ARG && (valuelen = strcspn(cmd, " \t"))) {
424                 bool advance = cmd[valuelen] != 0;
426                 cmd[valuelen] = 0;
427                 argv[(*argc)++] = chomp_string(cmd);
428                 cmd = chomp_string(cmd + valuelen + advance);
429         }
431         if (*argc < SIZEOF_ARG)
432                 argv[*argc] = NULL;
433         return *argc < SIZEOF_ARG;
436 static void
437 argv_from_env(const char **argv, const char *name)
439         char *env = argv ? getenv(name) : NULL;
440         int argc = 0;
442         if (env && *env)
443                 env = strdup(env);
444         if (env && !argv_from_string(argv, &argc, env))
445                 die("Too many arguments in the `%s` environment variable", name);
449 /*
450  * Executing external commands.
451  */
453 enum io_type {
454         IO_FD,                  /* File descriptor based IO. */
455         IO_BG,                  /* Execute command in the background. */
456         IO_FG,                  /* Execute command with same std{in,out,err}. */
457         IO_RD,                  /* Read only fork+exec IO. */
458         IO_WR,                  /* Write only fork+exec IO. */
459         IO_AP,                  /* Append fork+exec output to file. */
460 };
462 struct io {
463         enum io_type type;      /* The requested type of pipe. */
464         const char *dir;        /* Directory from which to execute. */
465         pid_t pid;              /* Pipe for reading or writing. */
466         int pipe;               /* Pipe end for reading or writing. */
467         int error;              /* Error status. */
468         const char *argv[SIZEOF_ARG];   /* Shell command arguments. */
469         char *buf;              /* Read buffer. */
470         size_t bufalloc;        /* Allocated buffer size. */
471         size_t bufsize;         /* Buffer content size. */
472         char *bufpos;           /* Current buffer position. */
473         unsigned int eof:1;     /* Has end of file been reached. */
474 };
476 static void
477 reset_io(struct io *io)
479         io->pipe = -1;
480         io->pid = 0;
481         io->buf = io->bufpos = NULL;
482         io->bufalloc = io->bufsize = 0;
483         io->error = 0;
484         io->eof = 0;
487 static void
488 init_io(struct io *io, const char *dir, enum io_type type)
490         reset_io(io);
491         io->type = type;
492         io->dir = dir;
495 static bool
496 init_io_rd(struct io *io, const char *argv[], const char *dir,
497                 enum format_flags flags)
499         init_io(io, dir, IO_RD);
500         return format_argv(io->argv, argv, flags);
503 static bool
504 io_open(struct io *io, const char *name)
506         init_io(io, NULL, IO_FD);
507         io->pipe = *name ? open(name, O_RDONLY) : STDIN_FILENO;
508         if (io->pipe == -1)
509                 io->error = errno;
510         return io->pipe != -1;
513 static bool
514 kill_io(struct io *io)
516         return io->pid == 0 || kill(io->pid, SIGKILL) != -1;
519 static bool
520 done_io(struct io *io)
522         pid_t pid = io->pid;
524         if (io->pipe != -1)
525                 close(io->pipe);
526         free(io->buf);
527         reset_io(io);
529         while (pid > 0) {
530                 int status;
531                 pid_t waiting = waitpid(pid, &status, 0);
533                 if (waiting < 0) {
534                         if (errno == EINTR)
535                                 continue;
536                         report("waitpid failed (%s)", strerror(errno));
537                         return FALSE;
538                 }
540                 return waiting == pid &&
541                        !WIFSIGNALED(status) &&
542                        WIFEXITED(status) &&
543                        !WEXITSTATUS(status);
544         }
546         return TRUE;
549 static bool
550 start_io(struct io *io)
552         int pipefds[2] = { -1, -1 };
554         if (io->type == IO_FD)
555                 return TRUE;
557         if ((io->type == IO_RD || io->type == IO_WR) &&
558             pipe(pipefds) < 0)
559                 return FALSE;
560         else if (io->type == IO_AP)
561                 pipefds[1] = io->pipe;
563         if ((io->pid = fork())) {
564                 if (pipefds[!(io->type == IO_WR)] != -1)
565                         close(pipefds[!(io->type == IO_WR)]);
566                 if (io->pid != -1) {
567                         io->pipe = pipefds[!!(io->type == IO_WR)];
568                         return TRUE;
569                 }
571         } else {
572                 if (io->type != IO_FG) {
573                         int devnull = open("/dev/null", O_RDWR);
574                         int readfd  = io->type == IO_WR ? pipefds[0] : devnull;
575                         int writefd = (io->type == IO_RD || io->type == IO_AP)
576                                                         ? pipefds[1] : devnull;
578                         dup2(readfd,  STDIN_FILENO);
579                         dup2(writefd, STDOUT_FILENO);
580                         dup2(devnull, STDERR_FILENO);
582                         close(devnull);
583                         if (pipefds[0] != -1)
584                                 close(pipefds[0]);
585                         if (pipefds[1] != -1)
586                                 close(pipefds[1]);
587                 }
589                 if (io->dir && *io->dir && chdir(io->dir) == -1)
590                         die("Failed to change directory: %s", strerror(errno));
592                 execvp(io->argv[0], (char *const*) io->argv);
593                 die("Failed to execute program: %s", strerror(errno));
594         }
596         if (pipefds[!!(io->type == IO_WR)] != -1)
597                 close(pipefds[!!(io->type == IO_WR)]);
598         return FALSE;
601 static bool
602 run_io(struct io *io, const char **argv, const char *dir, enum io_type type)
604         init_io(io, dir, type);
605         if (!format_argv(io->argv, argv, FORMAT_NONE))
606                 return FALSE;
607         return start_io(io);
610 static int
611 run_io_do(struct io *io)
613         return start_io(io) && done_io(io);
616 static int
617 run_io_bg(const char **argv)
619         struct io io = {};
621         init_io(&io, NULL, IO_BG);
622         if (!format_argv(io.argv, argv, FORMAT_NONE))
623                 return FALSE;
624         return run_io_do(&io);
627 static bool
628 run_io_fg(const char **argv, const char *dir)
630         struct io io = {};
632         init_io(&io, dir, IO_FG);
633         if (!format_argv(io.argv, argv, FORMAT_NONE))
634                 return FALSE;
635         return run_io_do(&io);
638 static bool
639 run_io_append(const char **argv, enum format_flags flags, int fd)
641         struct io io = {};
643         init_io(&io, NULL, IO_AP);
644         io.pipe = fd;
645         if (format_argv(io.argv, argv, flags))
646                 return run_io_do(&io);
647         close(fd);
648         return FALSE;
651 static bool
652 run_io_rd(struct io *io, const char **argv, enum format_flags flags)
654         return init_io_rd(io, argv, NULL, flags) && start_io(io);
657 static bool
658 io_eof(struct io *io)
660         return io->eof;
663 static int
664 io_error(struct io *io)
666         return io->error;
669 static char *
670 io_strerror(struct io *io)
672         return strerror(io->error);
675 static bool
676 io_can_read(struct io *io)
678         struct timeval tv = { 0, 500 };
679         fd_set fds;
681         FD_ZERO(&fds);
682         FD_SET(io->pipe, &fds);
684         return select(io->pipe + 1, &fds, NULL, NULL, &tv) > 0;
687 static ssize_t
688 io_read(struct io *io, void *buf, size_t bufsize)
690         do {
691                 ssize_t readsize = read(io->pipe, buf, bufsize);
693                 if (readsize < 0 && (errno == EAGAIN || errno == EINTR))
694                         continue;
695                 else if (readsize == -1)
696                         io->error = errno;
697                 else if (readsize == 0)
698                         io->eof = 1;
699                 return readsize;
700         } while (1);
703 DEFINE_ALLOCATOR(realloc_io_buf, char, BUFSIZ)
705 static char *
706 io_get(struct io *io, int c, bool can_read)
708         char *eol;
709         ssize_t readsize;
711         while (TRUE) {
712                 if (io->bufsize > 0) {
713                         eol = memchr(io->bufpos, c, io->bufsize);
714                         if (eol) {
715                                 char *line = io->bufpos;
717                                 *eol = 0;
718                                 io->bufpos = eol + 1;
719                                 io->bufsize -= io->bufpos - line;
720                                 return line;
721                         }
722                 }
724                 if (io_eof(io)) {
725                         if (io->bufsize) {
726                                 io->bufpos[io->bufsize] = 0;
727                                 io->bufsize = 0;
728                                 return io->bufpos;
729                         }
730                         return NULL;
731                 }
733                 if (!can_read)
734                         return NULL;
736                 if (io->bufsize > 0 && io->bufpos > io->buf)
737                         memmove(io->buf, io->bufpos, io->bufsize);
739                 if (io->bufalloc == io->bufsize) {
740                         if (!realloc_io_buf(&io->buf, io->bufalloc, BUFSIZ))
741                                 return NULL;
742                         io->bufalloc += BUFSIZ;
743                 }
745                 io->bufpos = io->buf;
746                 readsize = io_read(io, io->buf + io->bufsize, io->bufalloc - io->bufsize);
747                 if (io_error(io))
748                         return NULL;
749                 io->bufsize += readsize;
750         }
753 static bool
754 io_write(struct io *io, const void *buf, size_t bufsize)
756         size_t written = 0;
758         while (!io_error(io) && written < bufsize) {
759                 ssize_t size;
761                 size = write(io->pipe, buf + written, bufsize - written);
762                 if (size < 0 && (errno == EAGAIN || errno == EINTR))
763                         continue;
764                 else if (size == -1)
765                         io->error = errno;
766                 else
767                         written += size;
768         }
770         return written == bufsize;
773 static bool
774 io_read_buf(struct io *io, char buf[], size_t bufsize)
776         char *result = io_get(io, '\n', TRUE);
778         if (result) {
779                 result = chomp_string(result);
780                 string_ncopy_do(buf, bufsize, result, strlen(result));
781         }
783         return done_io(io) && result;
786 static bool
787 run_io_buf(const char **argv, char buf[], size_t bufsize)
789         struct io io = {};
791         return run_io_rd(&io, argv, FORMAT_NONE) && io_read_buf(&io, buf, bufsize);
794 static int
795 io_load(struct io *io, const char *separators,
796         int (*read_property)(char *, size_t, char *, size_t))
798         char *name;
799         int state = OK;
801         if (!start_io(io))
802                 return ERR;
804         while (state == OK && (name = io_get(io, '\n', TRUE))) {
805                 char *value;
806                 size_t namelen;
807                 size_t valuelen;
809                 name = chomp_string(name);
810                 namelen = strcspn(name, separators);
812                 if (name[namelen]) {
813                         name[namelen] = 0;
814                         value = chomp_string(name + namelen + 1);
815                         valuelen = strlen(value);
817                 } else {
818                         value = "";
819                         valuelen = 0;
820                 }
822                 state = read_property(name, namelen, value, valuelen);
823         }
825         if (state != ERR && io_error(io))
826                 state = ERR;
827         done_io(io);
829         return state;
832 static int
833 run_io_load(const char **argv, const char *separators,
834             int (*read_property)(char *, size_t, char *, size_t))
836         struct io io = {};
838         return init_io_rd(&io, argv, NULL, FORMAT_NONE)
839                 ? io_load(&io, separators, read_property) : ERR;
843 /*
844  * User requests
845  */
847 #define REQ_INFO \
848         /* XXX: Keep the view request first and in sync with views[]. */ \
849         REQ_GROUP("View switching") \
850         REQ_(VIEW_MAIN,         "Show main view"), \
851         REQ_(VIEW_DIFF,         "Show diff view"), \
852         REQ_(VIEW_LOG,          "Show log view"), \
853         REQ_(VIEW_TREE,         "Show tree view"), \
854         REQ_(VIEW_BLOB,         "Show blob view"), \
855         REQ_(VIEW_BLAME,        "Show blame view"), \
856         REQ_(VIEW_BRANCH,       "Show branch view"), \
857         REQ_(VIEW_HELP,         "Show help page"), \
858         REQ_(VIEW_PAGER,        "Show pager view"), \
859         REQ_(VIEW_STATUS,       "Show status view"), \
860         REQ_(VIEW_STAGE,        "Show stage view"), \
861         \
862         REQ_GROUP("View manipulation") \
863         REQ_(ENTER,             "Enter current line and scroll"), \
864         REQ_(NEXT,              "Move to next"), \
865         REQ_(PREVIOUS,          "Move to previous"), \
866         REQ_(PARENT,            "Move to parent"), \
867         REQ_(VIEW_NEXT,         "Move focus to next view"), \
868         REQ_(REFRESH,           "Reload and refresh"), \
869         REQ_(MAXIMIZE,          "Maximize the current view"), \
870         REQ_(VIEW_CLOSE,        "Close the current view"), \
871         REQ_(QUIT,              "Close all views and quit"), \
872         \
873         REQ_GROUP("View specific requests") \
874         REQ_(STATUS_UPDATE,     "Update file status"), \
875         REQ_(STATUS_REVERT,     "Revert file changes"), \
876         REQ_(STATUS_MERGE,      "Merge file using external tool"), \
877         REQ_(STAGE_NEXT,        "Find next chunk to stage"), \
878         \
879         REQ_GROUP("Cursor navigation") \
880         REQ_(MOVE_UP,           "Move cursor one line up"), \
881         REQ_(MOVE_DOWN,         "Move cursor one line down"), \
882         REQ_(MOVE_PAGE_DOWN,    "Move cursor one page down"), \
883         REQ_(MOVE_PAGE_UP,      "Move cursor one page up"), \
884         REQ_(MOVE_FIRST_LINE,   "Move cursor to first line"), \
885         REQ_(MOVE_LAST_LINE,    "Move cursor to last line"), \
886         \
887         REQ_GROUP("Scrolling") \
888         REQ_(SCROLL_LEFT,       "Scroll two columns left"), \
889         REQ_(SCROLL_RIGHT,      "Scroll two columns right"), \
890         REQ_(SCROLL_LINE_UP,    "Scroll one line up"), \
891         REQ_(SCROLL_LINE_DOWN,  "Scroll one line down"), \
892         REQ_(SCROLL_PAGE_UP,    "Scroll one page up"), \
893         REQ_(SCROLL_PAGE_DOWN,  "Scroll one page down"), \
894         \
895         REQ_GROUP("Searching") \
896         REQ_(SEARCH,            "Search the view"), \
897         REQ_(SEARCH_BACK,       "Search backwards in the view"), \
898         REQ_(FIND_NEXT,         "Find next search match"), \
899         REQ_(FIND_PREV,         "Find previous search match"), \
900         \
901         REQ_GROUP("Option manipulation") \
902         REQ_(OPTIONS,           "Open option menu"), \
903         REQ_(TOGGLE_LINENO,     "Toggle line numbers"), \
904         REQ_(TOGGLE_DATE,       "Toggle date display"), \
905         REQ_(TOGGLE_DATE_SHORT, "Toggle short (date-only) dates"), \
906         REQ_(TOGGLE_AUTHOR,     "Toggle author display"), \
907         REQ_(TOGGLE_REV_GRAPH,  "Toggle revision graph visualization"), \
908         REQ_(TOGGLE_REFS,       "Toggle reference display (tags/branches)"), \
909         REQ_(TOGGLE_SORT_ORDER, "Toggle ascending/descending sort order"), \
910         REQ_(TOGGLE_SORT_FIELD, "Toggle field to sort by"), \
911         \
912         REQ_GROUP("Misc") \
913         REQ_(PROMPT,            "Bring up the prompt"), \
914         REQ_(SCREEN_REDRAW,     "Redraw the screen"), \
915         REQ_(SHOW_VERSION,      "Show version information"), \
916         REQ_(STOP_LOADING,      "Stop all loading views"), \
917         REQ_(EDIT,              "Open in editor"), \
918         REQ_(NONE,              "Do nothing")
921 /* User action requests. */
922 enum request {
923 #define REQ_GROUP(help)
924 #define REQ_(req, help) REQ_##req
926         /* Offset all requests to avoid conflicts with ncurses getch values. */
927         REQ_OFFSET = KEY_MAX + 1,
928         REQ_INFO
930 #undef  REQ_GROUP
931 #undef  REQ_
932 };
934 struct request_info {
935         enum request request;
936         const char *name;
937         int namelen;
938         const char *help;
939 };
941 static const struct request_info req_info[] = {
942 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
943 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
944         REQ_INFO
945 #undef  REQ_GROUP
946 #undef  REQ_
947 };
949 static enum request
950 get_request(const char *name)
952         int namelen = strlen(name);
953         int i;
955         for (i = 0; i < ARRAY_SIZE(req_info); i++)
956                 if (req_info[i].namelen == namelen &&
957                     !string_enum_compare(req_info[i].name, name, namelen))
958                         return req_info[i].request;
960         return REQ_NONE;
964 /*
965  * Options
966  */
968 /* Option and state variables. */
969 static enum date opt_date               = DATE_DEFAULT;
970 static bool opt_author                  = TRUE;
971 static bool opt_line_number             = FALSE;
972 static bool opt_line_graphics           = TRUE;
973 static bool opt_rev_graph               = FALSE;
974 static bool opt_show_refs               = TRUE;
975 static int opt_num_interval             = 5;
976 static double opt_hscroll               = 0.50;
977 static double opt_scale_split_view      = 2.0 / 3.0;
978 static int opt_tab_size                 = 8;
979 static int opt_author_cols              = 19;
980 static char opt_path[SIZEOF_STR]        = "";
981 static char opt_file[SIZEOF_STR]        = "";
982 static char opt_ref[SIZEOF_REF]         = "";
983 static char opt_head[SIZEOF_REF]        = "";
984 static char opt_head_rev[SIZEOF_REV]    = "";
985 static char opt_remote[SIZEOF_REF]      = "";
986 static char opt_encoding[20]            = "UTF-8";
987 static bool opt_utf8                    = TRUE;
988 static char opt_codeset[20]             = "UTF-8";
989 static iconv_t opt_iconv                = ICONV_NONE;
990 static char opt_search[SIZEOF_STR]      = "";
991 static char opt_cdup[SIZEOF_STR]        = "";
992 static char opt_prefix[SIZEOF_STR]      = "";
993 static char opt_git_dir[SIZEOF_STR]     = "";
994 static signed char opt_is_inside_work_tree      = -1; /* set to TRUE or FALSE */
995 static char opt_editor[SIZEOF_STR]      = "";
996 static FILE *opt_tty                    = NULL;
998 #define is_initial_commit()     (!*opt_head_rev)
999 #define is_head_commit(rev)     (!strcmp((rev), "HEAD") || !strcmp(opt_head_rev, (rev)))
1000 #define mkdate(time)            string_date(time, opt_date)
1003 /*
1004  * Line-oriented content detection.
1005  */
1007 #define LINE_INFO \
1008 LINE(DIFF_HEADER,  "diff --git ",       COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1009 LINE(DIFF_CHUNK,   "@@",                COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1010 LINE(DIFF_ADD,     "+",                 COLOR_GREEN,    COLOR_DEFAULT,  0), \
1011 LINE(DIFF_DEL,     "-",                 COLOR_RED,      COLOR_DEFAULT,  0), \
1012 LINE(DIFF_INDEX,        "index ",         COLOR_BLUE,   COLOR_DEFAULT,  0), \
1013 LINE(DIFF_OLDMODE,      "old file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
1014 LINE(DIFF_NEWMODE,      "new file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
1015 LINE(DIFF_COPY_FROM,    "copy from",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
1016 LINE(DIFF_COPY_TO,      "copy to",        COLOR_YELLOW, COLOR_DEFAULT,  0), \
1017 LINE(DIFF_RENAME_FROM,  "rename from",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
1018 LINE(DIFF_RENAME_TO,    "rename to",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
1019 LINE(DIFF_SIMILARITY,   "similarity ",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
1020 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
1021 LINE(DIFF_TREE,         "diff-tree ",     COLOR_BLUE,   COLOR_DEFAULT,  0), \
1022 LINE(PP_AUTHOR,    "Author: ",          COLOR_CYAN,     COLOR_DEFAULT,  0), \
1023 LINE(PP_COMMIT,    "Commit: ",          COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1024 LINE(PP_MERGE,     "Merge: ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
1025 LINE(PP_DATE,      "Date:   ",          COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1026 LINE(PP_ADATE,     "AuthorDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1027 LINE(PP_CDATE,     "CommitDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1028 LINE(PP_REFS,      "Refs: ",            COLOR_RED,      COLOR_DEFAULT,  0), \
1029 LINE(COMMIT,       "commit ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
1030 LINE(PARENT,       "parent ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
1031 LINE(TREE,         "tree ",             COLOR_BLUE,     COLOR_DEFAULT,  0), \
1032 LINE(AUTHOR,       "author ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
1033 LINE(COMMITTER,    "committer ",        COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1034 LINE(SIGNOFF,      "    Signed-off-by", COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1035 LINE(ACKED,        "    Acked-by",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1036 LINE(DEFAULT,      "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
1037 LINE(CURSOR,       "",                  COLOR_WHITE,    COLOR_GREEN,    A_BOLD), \
1038 LINE(STATUS,       "",                  COLOR_GREEN,    COLOR_DEFAULT,  0), \
1039 LINE(DELIMITER,    "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1040 LINE(DATE,         "",                  COLOR_BLUE,     COLOR_DEFAULT,  0), \
1041 LINE(MODE,         "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1042 LINE(LINE_NUMBER,  "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1043 LINE(TITLE_BLUR,   "",                  COLOR_WHITE,    COLOR_BLUE,     0), \
1044 LINE(TITLE_FOCUS,  "",                  COLOR_WHITE,    COLOR_BLUE,     A_BOLD), \
1045 LINE(MAIN_COMMIT,  "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
1046 LINE(MAIN_TAG,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  A_BOLD), \
1047 LINE(MAIN_LOCAL_TAG,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1048 LINE(MAIN_REMOTE,  "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1049 LINE(MAIN_TRACKED, "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_BOLD), \
1050 LINE(MAIN_REF,     "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1051 LINE(MAIN_HEAD,    "",                  COLOR_CYAN,     COLOR_DEFAULT,  A_BOLD), \
1052 LINE(MAIN_REVGRAPH,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1053 LINE(TREE_HEAD,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_BOLD), \
1054 LINE(TREE_DIR,     "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_NORMAL), \
1055 LINE(TREE_FILE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
1056 LINE(STAT_HEAD,    "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1057 LINE(STAT_SECTION, "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1058 LINE(STAT_NONE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
1059 LINE(STAT_STAGED,  "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1060 LINE(STAT_UNSTAGED,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1061 LINE(STAT_UNTRACKED,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1062 LINE(HELP_KEYMAP,  "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1063 LINE(HELP_GROUP,   "",                  COLOR_BLUE,     COLOR_DEFAULT,  0), \
1064 LINE(BLAME_ID,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0)
1066 enum line_type {
1067 #define LINE(type, line, fg, bg, attr) \
1068         LINE_##type
1069         LINE_INFO,
1070         LINE_NONE
1071 #undef  LINE
1072 };
1074 struct line_info {
1075         const char *name;       /* Option name. */
1076         int namelen;            /* Size of option name. */
1077         const char *line;       /* The start of line to match. */
1078         int linelen;            /* Size of string to match. */
1079         int fg, bg, attr;       /* Color and text attributes for the lines. */
1080 };
1082 static struct line_info line_info[] = {
1083 #define LINE(type, line, fg, bg, attr) \
1084         { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
1085         LINE_INFO
1086 #undef  LINE
1087 };
1089 static enum line_type
1090 get_line_type(const char *line)
1092         int linelen = strlen(line);
1093         enum line_type type;
1095         for (type = 0; type < ARRAY_SIZE(line_info); type++)
1096                 /* Case insensitive search matches Signed-off-by lines better. */
1097                 if (linelen >= line_info[type].linelen &&
1098                     !strncasecmp(line_info[type].line, line, line_info[type].linelen))
1099                         return type;
1101         return LINE_DEFAULT;
1104 static inline int
1105 get_line_attr(enum line_type type)
1107         assert(type < ARRAY_SIZE(line_info));
1108         return COLOR_PAIR(type) | line_info[type].attr;
1111 static struct line_info *
1112 get_line_info(const char *name)
1114         size_t namelen = strlen(name);
1115         enum line_type type;
1117         for (type = 0; type < ARRAY_SIZE(line_info); type++)
1118                 if (namelen == line_info[type].namelen &&
1119                     !string_enum_compare(line_info[type].name, name, namelen))
1120                         return &line_info[type];
1122         return NULL;
1125 static void
1126 init_colors(void)
1128         int default_bg = line_info[LINE_DEFAULT].bg;
1129         int default_fg = line_info[LINE_DEFAULT].fg;
1130         enum line_type type;
1132         start_color();
1134         if (assume_default_colors(default_fg, default_bg) == ERR) {
1135                 default_bg = COLOR_BLACK;
1136                 default_fg = COLOR_WHITE;
1137         }
1139         for (type = 0; type < ARRAY_SIZE(line_info); type++) {
1140                 struct line_info *info = &line_info[type];
1141                 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
1142                 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
1144                 init_pair(type, fg, bg);
1145         }
1148 struct line {
1149         enum line_type type;
1151         /* State flags */
1152         unsigned int selected:1;
1153         unsigned int dirty:1;
1154         unsigned int cleareol:1;
1155         unsigned int other:16;
1157         void *data;             /* User data */
1158 };
1161 /*
1162  * Keys
1163  */
1165 struct keybinding {
1166         int alias;
1167         enum request request;
1168 };
1170 static const struct keybinding default_keybindings[] = {
1171         /* View switching */
1172         { 'm',          REQ_VIEW_MAIN },
1173         { 'd',          REQ_VIEW_DIFF },
1174         { 'l',          REQ_VIEW_LOG },
1175         { 't',          REQ_VIEW_TREE },
1176         { 'f',          REQ_VIEW_BLOB },
1177         { 'B',          REQ_VIEW_BLAME },
1178         { 'H',          REQ_VIEW_BRANCH },
1179         { 'p',          REQ_VIEW_PAGER },
1180         { 'h',          REQ_VIEW_HELP },
1181         { 'S',          REQ_VIEW_STATUS },
1182         { 'c',          REQ_VIEW_STAGE },
1184         /* View manipulation */
1185         { 'q',          REQ_VIEW_CLOSE },
1186         { KEY_TAB,      REQ_VIEW_NEXT },
1187         { KEY_RETURN,   REQ_ENTER },
1188         { KEY_UP,       REQ_PREVIOUS },
1189         { KEY_DOWN,     REQ_NEXT },
1190         { 'R',          REQ_REFRESH },
1191         { KEY_F(5),     REQ_REFRESH },
1192         { 'O',          REQ_MAXIMIZE },
1194         /* Cursor navigation */
1195         { 'k',          REQ_MOVE_UP },
1196         { 'j',          REQ_MOVE_DOWN },
1197         { KEY_HOME,     REQ_MOVE_FIRST_LINE },
1198         { KEY_END,      REQ_MOVE_LAST_LINE },
1199         { KEY_NPAGE,    REQ_MOVE_PAGE_DOWN },
1200         { ' ',          REQ_MOVE_PAGE_DOWN },
1201         { KEY_PPAGE,    REQ_MOVE_PAGE_UP },
1202         { 'b',          REQ_MOVE_PAGE_UP },
1203         { '-',          REQ_MOVE_PAGE_UP },
1205         /* Scrolling */
1206         { KEY_LEFT,     REQ_SCROLL_LEFT },
1207         { KEY_RIGHT,    REQ_SCROLL_RIGHT },
1208         { KEY_IC,       REQ_SCROLL_LINE_UP },
1209         { KEY_DC,       REQ_SCROLL_LINE_DOWN },
1210         { 'w',          REQ_SCROLL_PAGE_UP },
1211         { 's',          REQ_SCROLL_PAGE_DOWN },
1213         /* Searching */
1214         { '/',          REQ_SEARCH },
1215         { '?',          REQ_SEARCH_BACK },
1216         { 'n',          REQ_FIND_NEXT },
1217         { 'N',          REQ_FIND_PREV },
1219         /* Misc */
1220         { 'Q',          REQ_QUIT },
1221         { 'z',          REQ_STOP_LOADING },
1222         { 'v',          REQ_SHOW_VERSION },
1223         { 'r',          REQ_SCREEN_REDRAW },
1224         { 'o',          REQ_OPTIONS },
1225         { '.',          REQ_TOGGLE_LINENO },
1226         { 'D',          REQ_TOGGLE_DATE },
1227         { 'A',          REQ_TOGGLE_AUTHOR },
1228         { 'g',          REQ_TOGGLE_REV_GRAPH },
1229         { 'F',          REQ_TOGGLE_REFS },
1230         { 'I',          REQ_TOGGLE_SORT_ORDER },
1231         { 'i',          REQ_TOGGLE_SORT_FIELD },
1232         { ':',          REQ_PROMPT },
1233         { 'u',          REQ_STATUS_UPDATE },
1234         { '!',          REQ_STATUS_REVERT },
1235         { 'M',          REQ_STATUS_MERGE },
1236         { '@',          REQ_STAGE_NEXT },
1237         { ',',          REQ_PARENT },
1238         { 'e',          REQ_EDIT },
1239 };
1241 #define KEYMAP_INFO \
1242         KEYMAP_(GENERIC), \
1243         KEYMAP_(MAIN), \
1244         KEYMAP_(DIFF), \
1245         KEYMAP_(LOG), \
1246         KEYMAP_(TREE), \
1247         KEYMAP_(BLOB), \
1248         KEYMAP_(BLAME), \
1249         KEYMAP_(BRANCH), \
1250         KEYMAP_(PAGER), \
1251         KEYMAP_(HELP), \
1252         KEYMAP_(STATUS), \
1253         KEYMAP_(STAGE)
1255 enum keymap {
1256 #define KEYMAP_(name) KEYMAP_##name
1257         KEYMAP_INFO
1258 #undef  KEYMAP_
1259 };
1261 static const struct enum_map keymap_table[] = {
1262 #define KEYMAP_(name) ENUM_MAP(#name, KEYMAP_##name)
1263         KEYMAP_INFO
1264 #undef  KEYMAP_
1265 };
1267 #define set_keymap(map, name) map_enum(map, keymap_table, name)
1269 struct keybinding_table {
1270         struct keybinding *data;
1271         size_t size;
1272 };
1274 static struct keybinding_table keybindings[ARRAY_SIZE(keymap_table)];
1276 static void
1277 add_keybinding(enum keymap keymap, enum request request, int key)
1279         struct keybinding_table *table = &keybindings[keymap];
1281         table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data));
1282         if (!table->data)
1283                 die("Failed to allocate keybinding");
1284         table->data[table->size].alias = key;
1285         table->data[table->size++].request = request;
1288 /* Looks for a key binding first in the given map, then in the generic map, and
1289  * lastly in the default keybindings. */
1290 static enum request
1291 get_keybinding(enum keymap keymap, int key)
1293         size_t i;
1295         for (i = 0; i < keybindings[keymap].size; i++)
1296                 if (keybindings[keymap].data[i].alias == key)
1297                         return keybindings[keymap].data[i].request;
1299         for (i = 0; i < keybindings[KEYMAP_GENERIC].size; i++)
1300                 if (keybindings[KEYMAP_GENERIC].data[i].alias == key)
1301                         return keybindings[KEYMAP_GENERIC].data[i].request;
1303         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1304                 if (default_keybindings[i].alias == key)
1305                         return default_keybindings[i].request;
1307         return (enum request) key;
1311 struct key {
1312         const char *name;
1313         int value;
1314 };
1316 static const struct key key_table[] = {
1317         { "Enter",      KEY_RETURN },
1318         { "Space",      ' ' },
1319         { "Backspace",  KEY_BACKSPACE },
1320         { "Tab",        KEY_TAB },
1321         { "Escape",     KEY_ESC },
1322         { "Left",       KEY_LEFT },
1323         { "Right",      KEY_RIGHT },
1324         { "Up",         KEY_UP },
1325         { "Down",       KEY_DOWN },
1326         { "Insert",     KEY_IC },
1327         { "Delete",     KEY_DC },
1328         { "Hash",       '#' },
1329         { "Home",       KEY_HOME },
1330         { "End",        KEY_END },
1331         { "PageUp",     KEY_PPAGE },
1332         { "PageDown",   KEY_NPAGE },
1333         { "F1",         KEY_F(1) },
1334         { "F2",         KEY_F(2) },
1335         { "F3",         KEY_F(3) },
1336         { "F4",         KEY_F(4) },
1337         { "F5",         KEY_F(5) },
1338         { "F6",         KEY_F(6) },
1339         { "F7",         KEY_F(7) },
1340         { "F8",         KEY_F(8) },
1341         { "F9",         KEY_F(9) },
1342         { "F10",        KEY_F(10) },
1343         { "F11",        KEY_F(11) },
1344         { "F12",        KEY_F(12) },
1345 };
1347 static int
1348 get_key_value(const char *name)
1350         int i;
1352         for (i = 0; i < ARRAY_SIZE(key_table); i++)
1353                 if (!strcasecmp(key_table[i].name, name))
1354                         return key_table[i].value;
1356         if (strlen(name) == 1 && isprint(*name))
1357                 return (int) *name;
1359         return ERR;
1362 static const char *
1363 get_key_name(int key_value)
1365         static char key_char[] = "'X'";
1366         const char *seq = NULL;
1367         int key;
1369         for (key = 0; key < ARRAY_SIZE(key_table); key++)
1370                 if (key_table[key].value == key_value)
1371                         seq = key_table[key].name;
1373         if (seq == NULL &&
1374             key_value < 127 &&
1375             isprint(key_value)) {
1376                 key_char[1] = (char) key_value;
1377                 seq = key_char;
1378         }
1380         return seq ? seq : "(no key)";
1383 static bool
1384 append_key(char *buf, size_t *pos, const struct keybinding *keybinding)
1386         const char *sep = *pos > 0 ? ", " : "";
1387         const char *keyname = get_key_name(keybinding->alias);
1389         return string_nformat(buf, BUFSIZ, pos, "%s%s", sep, keyname);
1392 static bool
1393 append_keymap_request_keys(char *buf, size_t *pos, enum request request,
1394                            enum keymap keymap, bool all)
1396         int i;
1398         for (i = 0; i < keybindings[keymap].size; i++) {
1399                 if (keybindings[keymap].data[i].request == request) {
1400                         if (!append_key(buf, pos, &keybindings[keymap].data[i]))
1401                                 return FALSE;
1402                         if (!all)
1403                                 break;
1404                 }
1405         }
1407         return TRUE;
1410 #define get_key(keymap, request) get_keys(keymap, request, FALSE)
1412 static const char *
1413 get_keys(enum keymap keymap, enum request request, bool all)
1415         static char buf[BUFSIZ];
1416         size_t pos = 0;
1417         int i;
1419         buf[pos] = 0;
1421         if (!append_keymap_request_keys(buf, &pos, request, keymap, all))
1422                 return "Too many keybindings!";
1423         if (pos > 0 && !all)
1424                 return buf;
1426         if (keymap != KEYMAP_GENERIC) {
1427                 /* Only the generic keymap includes the default keybindings when
1428                  * listing all keys. */
1429                 if (all)
1430                         return buf;
1432                 if (!append_keymap_request_keys(buf, &pos, request, KEYMAP_GENERIC, all))
1433                         return "Too many keybindings!";
1434                 if (pos)
1435                         return buf;
1436         }
1438         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1439                 if (default_keybindings[i].request == request) {
1440                         if (!append_key(buf, &pos, &default_keybindings[i]))
1441                                 return "Too many keybindings!";
1442                         if (!all)
1443                                 return buf;
1444                 }
1445         }
1447         return buf;
1450 struct run_request {
1451         enum keymap keymap;
1452         int key;
1453         const char *argv[SIZEOF_ARG];
1454 };
1456 static struct run_request *run_request;
1457 static size_t run_requests;
1459 DEFINE_ALLOCATOR(realloc_run_requests, struct run_request, 8)
1461 static enum request
1462 add_run_request(enum keymap keymap, int key, int argc, const char **argv)
1464         struct run_request *req;
1466         if (argc >= ARRAY_SIZE(req->argv) - 1)
1467                 return REQ_NONE;
1469         if (!realloc_run_requests(&run_request, run_requests, 1))
1470                 return REQ_NONE;
1472         req = &run_request[run_requests];
1473         req->keymap = keymap;
1474         req->key = key;
1475         req->argv[0] = NULL;
1477         if (!format_argv(req->argv, argv, FORMAT_NONE))
1478                 return REQ_NONE;
1480         return REQ_NONE + ++run_requests;
1483 static struct run_request *
1484 get_run_request(enum request request)
1486         if (request <= REQ_NONE)
1487                 return NULL;
1488         return &run_request[request - REQ_NONE - 1];
1491 static void
1492 add_builtin_run_requests(void)
1494         const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1495         const char *commit[] = { "git", "commit", NULL };
1496         const char *gc[] = { "git", "gc", NULL };
1497         struct {
1498                 enum keymap keymap;
1499                 int key;
1500                 int argc;
1501                 const char **argv;
1502         } reqs[] = {
1503                 { KEYMAP_MAIN,    'C', ARRAY_SIZE(cherry_pick) - 1, cherry_pick },
1504                 { KEYMAP_STATUS,  'C', ARRAY_SIZE(commit) - 1, commit },
1505                 { KEYMAP_GENERIC, 'G', ARRAY_SIZE(gc) - 1, gc },
1506         };
1507         int i;
1509         for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1510                 enum request req;
1512                 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argc, reqs[i].argv);
1513                 if (req != REQ_NONE)
1514                         add_keybinding(reqs[i].keymap, req, reqs[i].key);
1515         }
1518 /*
1519  * User config file handling.
1520  */
1522 static int   config_lineno;
1523 static bool  config_errors;
1524 static const char *config_msg;
1526 static const struct enum_map color_map[] = {
1527 #define COLOR_MAP(name) ENUM_MAP(#name, COLOR_##name)
1528         COLOR_MAP(DEFAULT),
1529         COLOR_MAP(BLACK),
1530         COLOR_MAP(BLUE),
1531         COLOR_MAP(CYAN),
1532         COLOR_MAP(GREEN),
1533         COLOR_MAP(MAGENTA),
1534         COLOR_MAP(RED),
1535         COLOR_MAP(WHITE),
1536         COLOR_MAP(YELLOW),
1537 };
1539 static const struct enum_map attr_map[] = {
1540 #define ATTR_MAP(name) ENUM_MAP(#name, A_##name)
1541         ATTR_MAP(NORMAL),
1542         ATTR_MAP(BLINK),
1543         ATTR_MAP(BOLD),
1544         ATTR_MAP(DIM),
1545         ATTR_MAP(REVERSE),
1546         ATTR_MAP(STANDOUT),
1547         ATTR_MAP(UNDERLINE),
1548 };
1550 #define set_attribute(attr, name)       map_enum(attr, attr_map, name)
1552 static int parse_step(double *opt, const char *arg)
1554         *opt = atoi(arg);
1555         if (!strchr(arg, '%'))
1556                 return OK;
1558         /* "Shift down" so 100% and 1 does not conflict. */
1559         *opt = (*opt - 1) / 100;
1560         if (*opt >= 1.0) {
1561                 *opt = 0.99;
1562                 config_msg = "Step value larger than 100%";
1563                 return ERR;
1564         }
1565         if (*opt < 0.0) {
1566                 *opt = 1;
1567                 config_msg = "Invalid step value";
1568                 return ERR;
1569         }
1570         return OK;
1573 static int
1574 parse_int(int *opt, const char *arg, int min, int max)
1576         int value = atoi(arg);
1578         if (min <= value && value <= max) {
1579                 *opt = value;
1580                 return OK;
1581         }
1583         config_msg = "Integer value out of bound";
1584         return ERR;
1587 static bool
1588 set_color(int *color, const char *name)
1590         if (map_enum(color, color_map, name))
1591                 return TRUE;
1592         if (!prefixcmp(name, "color"))
1593                 return parse_int(color, name + 5, 0, 255) == OK;
1594         return FALSE;
1597 /* Wants: object fgcolor bgcolor [attribute] */
1598 static int
1599 option_color_command(int argc, const char *argv[])
1601         struct line_info *info;
1603         if (argc < 3) {
1604                 config_msg = "Wrong number of arguments given to color command";
1605                 return ERR;
1606         }
1608         info = get_line_info(argv[0]);
1609         if (!info) {
1610                 static const struct enum_map obsolete[] = {
1611                         ENUM_MAP("main-delim",  LINE_DELIMITER),
1612                         ENUM_MAP("main-date",   LINE_DATE),
1613                         ENUM_MAP("main-author", LINE_AUTHOR),
1614                 };
1615                 int index;
1617                 if (!map_enum(&index, obsolete, argv[0])) {
1618                         config_msg = "Unknown color name";
1619                         return ERR;
1620                 }
1621                 info = &line_info[index];
1622         }
1624         if (!set_color(&info->fg, argv[1]) ||
1625             !set_color(&info->bg, argv[2])) {
1626                 config_msg = "Unknown color";
1627                 return ERR;
1628         }
1630         info->attr = 0;
1631         while (argc-- > 3) {
1632                 int attr;
1634                 if (!set_attribute(&attr, argv[argc])) {
1635                         config_msg = "Unknown attribute";
1636                         return ERR;
1637                 }
1638                 info->attr |= attr;
1639         }
1641         return OK;
1644 static int parse_bool(bool *opt, const char *arg)
1646         *opt = (!strcmp(arg, "1") || !strcmp(arg, "true") || !strcmp(arg, "yes"))
1647                 ? TRUE : FALSE;
1648         return OK;
1651 static int
1652 parse_string(char *opt, const char *arg, size_t optsize)
1654         int arglen = strlen(arg);
1656         switch (arg[0]) {
1657         case '\"':
1658         case '\'':
1659                 if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1660                         config_msg = "Unmatched quotation";
1661                         return ERR;
1662                 }
1663                 arg += 1; arglen -= 2;
1664         default:
1665                 string_ncopy_do(opt, optsize, arg, arglen);
1666                 return OK;
1667         }
1670 /* Wants: name = value */
1671 static int
1672 option_set_command(int argc, const char *argv[])
1674         if (argc != 3) {
1675                 config_msg = "Wrong number of arguments given to set command";
1676                 return ERR;
1677         }
1679         if (strcmp(argv[1], "=")) {
1680                 config_msg = "No value assigned";
1681                 return ERR;
1682         }
1684         if (!strcmp(argv[0], "show-author"))
1685                 return parse_bool(&opt_author, argv[2]);
1687         if (!strcmp(argv[0], "show-date")) {
1688                 bool show_date;
1690                 if (!strcmp(argv[2], "relative")) {
1691                         opt_date = DATE_RELATIVE;
1692                         return OK;
1693                 } else if (!strcmp(argv[2], "short")) {
1694                         opt_date = DATE_SHORT;
1695                         return OK;
1696                 } else if (parse_bool(&show_date, argv[2])) {
1697                         opt_date = show_date ? DATE_DEFAULT : DATE_NONE;
1698                 }
1699                 return ERR;
1700         }
1702         if (!strcmp(argv[0], "show-rev-graph"))
1703                 return parse_bool(&opt_rev_graph, argv[2]);
1705         if (!strcmp(argv[0], "show-refs"))
1706                 return parse_bool(&opt_show_refs, argv[2]);
1708         if (!strcmp(argv[0], "show-line-numbers"))
1709                 return parse_bool(&opt_line_number, argv[2]);
1711         if (!strcmp(argv[0], "line-graphics"))
1712                 return parse_bool(&opt_line_graphics, argv[2]);
1714         if (!strcmp(argv[0], "line-number-interval"))
1715                 return parse_int(&opt_num_interval, argv[2], 1, 1024);
1717         if (!strcmp(argv[0], "author-width"))
1718                 return parse_int(&opt_author_cols, argv[2], 0, 1024);
1720         if (!strcmp(argv[0], "horizontal-scroll"))
1721                 return parse_step(&opt_hscroll, argv[2]);
1723         if (!strcmp(argv[0], "split-view-height"))
1724                 return parse_step(&opt_scale_split_view, argv[2]);
1726         if (!strcmp(argv[0], "tab-size"))
1727                 return parse_int(&opt_tab_size, argv[2], 1, 1024);
1729         if (!strcmp(argv[0], "commit-encoding"))
1730                 return parse_string(opt_encoding, argv[2], sizeof(opt_encoding));
1732         config_msg = "Unknown variable name";
1733         return ERR;
1736 /* Wants: mode request key */
1737 static int
1738 option_bind_command(int argc, const char *argv[])
1740         enum request request;
1741         int keymap = -1;
1742         int key;
1744         if (argc < 3) {
1745                 config_msg = "Wrong number of arguments given to bind command";
1746                 return ERR;
1747         }
1749         if (set_keymap(&keymap, argv[0]) == ERR) {
1750                 config_msg = "Unknown key map";
1751                 return ERR;
1752         }
1754         key = get_key_value(argv[1]);
1755         if (key == ERR) {
1756                 config_msg = "Unknown key";
1757                 return ERR;
1758         }
1760         request = get_request(argv[2]);
1761         if (request == REQ_NONE) {
1762                 static const struct enum_map obsolete[] = {
1763                         ENUM_MAP("cherry-pick",         REQ_NONE),
1764                         ENUM_MAP("screen-resize",       REQ_NONE),
1765                         ENUM_MAP("tree-parent",         REQ_PARENT),
1766                 };
1767                 int alias;
1769                 if (map_enum(&alias, obsolete, argv[2])) {
1770                         if (alias != REQ_NONE)
1771                                 add_keybinding(keymap, alias, key);
1772                         config_msg = "Obsolete request name";
1773                         return ERR;
1774                 }
1775         }
1776         if (request == REQ_NONE && *argv[2]++ == '!')
1777                 request = add_run_request(keymap, key, argc - 2, argv + 2);
1778         if (request == REQ_NONE) {
1779                 config_msg = "Unknown request name";
1780                 return ERR;
1781         }
1783         add_keybinding(keymap, request, key);
1785         return OK;
1788 static int
1789 set_option(const char *opt, char *value)
1791         const char *argv[SIZEOF_ARG];
1792         int argc = 0;
1794         if (!argv_from_string(argv, &argc, value)) {
1795                 config_msg = "Too many option arguments";
1796                 return ERR;
1797         }
1799         if (!strcmp(opt, "color"))
1800                 return option_color_command(argc, argv);
1802         if (!strcmp(opt, "set"))
1803                 return option_set_command(argc, argv);
1805         if (!strcmp(opt, "bind"))
1806                 return option_bind_command(argc, argv);
1808         config_msg = "Unknown option command";
1809         return ERR;
1812 static int
1813 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
1815         int status = OK;
1817         config_lineno++;
1818         config_msg = "Internal error";
1820         /* Check for comment markers, since read_properties() will
1821          * only ensure opt and value are split at first " \t". */
1822         optlen = strcspn(opt, "#");
1823         if (optlen == 0)
1824                 return OK;
1826         if (opt[optlen] != 0) {
1827                 config_msg = "No option value";
1828                 status = ERR;
1830         }  else {
1831                 /* Look for comment endings in the value. */
1832                 size_t len = strcspn(value, "#");
1834                 if (len < valuelen) {
1835                         valuelen = len;
1836                         value[valuelen] = 0;
1837                 }
1839                 status = set_option(opt, value);
1840         }
1842         if (status == ERR) {
1843                 warn("Error on line %d, near '%.*s': %s",
1844                      config_lineno, (int) optlen, opt, config_msg);
1845                 config_errors = TRUE;
1846         }
1848         /* Always keep going if errors are encountered. */
1849         return OK;
1852 static void
1853 load_option_file(const char *path)
1855         struct io io = {};
1857         /* It's OK that the file doesn't exist. */
1858         if (!io_open(&io, path))
1859                 return;
1861         config_lineno = 0;
1862         config_errors = FALSE;
1864         if (io_load(&io, " \t", read_option) == ERR ||
1865             config_errors == TRUE)
1866                 warn("Errors while loading %s.", path);
1869 static int
1870 load_options(void)
1872         const char *home = getenv("HOME");
1873         const char *tigrc_user = getenv("TIGRC_USER");
1874         const char *tigrc_system = getenv("TIGRC_SYSTEM");
1875         char buf[SIZEOF_STR];
1877         add_builtin_run_requests();
1879         if (!tigrc_system)
1880                 tigrc_system = SYSCONFDIR "/tigrc";
1881         load_option_file(tigrc_system);
1883         if (!tigrc_user) {
1884                 if (!home || !string_format(buf, "%s/.tigrc", home))
1885                         return ERR;
1886                 tigrc_user = buf;
1887         }
1888         load_option_file(tigrc_user);
1890         return OK;
1894 /*
1895  * The viewer
1896  */
1898 struct view;
1899 struct view_ops;
1901 /* The display array of active views and the index of the current view. */
1902 static struct view *display[2];
1903 static unsigned int current_view;
1905 #define foreach_displayed_view(view, i) \
1906         for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1908 #define displayed_views()       (display[1] != NULL ? 2 : 1)
1910 /* Current head and commit ID */
1911 static char ref_blob[SIZEOF_REF]        = "";
1912 static char ref_commit[SIZEOF_REF]      = "HEAD";
1913 static char ref_head[SIZEOF_REF]        = "HEAD";
1915 struct view {
1916         const char *name;       /* View name */
1917         const char *cmd_env;    /* Command line set via environment */
1918         const char *id;         /* Points to either of ref_{head,commit,blob} */
1920         struct view_ops *ops;   /* View operations */
1922         enum keymap keymap;     /* What keymap does this view have */
1923         bool git_dir;           /* Whether the view requires a git directory. */
1925         char ref[SIZEOF_REF];   /* Hovered commit reference */
1926         char vid[SIZEOF_REF];   /* View ID. Set to id member when updating. */
1928         int height, width;      /* The width and height of the main window */
1929         WINDOW *win;            /* The main window */
1930         WINDOW *title;          /* The title window living below the main window */
1932         /* Navigation */
1933         unsigned long offset;   /* Offset of the window top */
1934         unsigned long yoffset;  /* Offset from the window side. */
1935         unsigned long lineno;   /* Current line number */
1936         unsigned long p_offset; /* Previous offset of the window top */
1937         unsigned long p_yoffset;/* Previous offset from the window side */
1938         unsigned long p_lineno; /* Previous current line number */
1939         bool p_restore;         /* Should the previous position be restored. */
1941         /* Searching */
1942         char grep[SIZEOF_STR];  /* Search string */
1943         regex_t *regex;         /* Pre-compiled regexp */
1945         /* If non-NULL, points to the view that opened this view. If this view
1946          * is closed tig will switch back to the parent view. */
1947         struct view *parent;
1949         /* Buffering */
1950         size_t lines;           /* Total number of lines */
1951         struct line *line;      /* Line index */
1952         unsigned int digits;    /* Number of digits in the lines member. */
1954         /* Drawing */
1955         struct line *curline;   /* Line currently being drawn. */
1956         enum line_type curtype; /* Attribute currently used for drawing. */
1957         unsigned long col;      /* Column when drawing. */
1958         bool has_scrolled;      /* View was scrolled. */
1960         /* Loading */
1961         struct io io;
1962         struct io *pipe;
1963         time_t start_time;
1964         time_t update_secs;
1965 };
1967 struct view_ops {
1968         /* What type of content being displayed. Used in the title bar. */
1969         const char *type;
1970         /* Default command arguments. */
1971         const char **argv;
1972         /* Open and reads in all view content. */
1973         bool (*open)(struct view *view);
1974         /* Read one line; updates view->line. */
1975         bool (*read)(struct view *view, char *data);
1976         /* Draw one line; @lineno must be < view->height. */
1977         bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
1978         /* Depending on view handle a special requests. */
1979         enum request (*request)(struct view *view, enum request request, struct line *line);
1980         /* Search for regexp in a line. */
1981         bool (*grep)(struct view *view, struct line *line);
1982         /* Select line */
1983         void (*select)(struct view *view, struct line *line);
1984 };
1986 static struct view_ops blame_ops;
1987 static struct view_ops blob_ops;
1988 static struct view_ops diff_ops;
1989 static struct view_ops help_ops;
1990 static struct view_ops log_ops;
1991 static struct view_ops main_ops;
1992 static struct view_ops pager_ops;
1993 static struct view_ops stage_ops;
1994 static struct view_ops status_ops;
1995 static struct view_ops tree_ops;
1996 static struct view_ops branch_ops;
1998 #define VIEW_STR(name, env, ref, ops, map, git) \
1999         { name, #env, ref, ops, map, git }
2001 #define VIEW_(id, name, ops, git, ref) \
2002         VIEW_STR(name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
2005 static struct view views[] = {
2006         VIEW_(MAIN,   "main",   &main_ops,   TRUE,  ref_head),
2007         VIEW_(DIFF,   "diff",   &diff_ops,   TRUE,  ref_commit),
2008         VIEW_(LOG,    "log",    &log_ops,    TRUE,  ref_head),
2009         VIEW_(TREE,   "tree",   &tree_ops,   TRUE,  ref_commit),
2010         VIEW_(BLOB,   "blob",   &blob_ops,   TRUE,  ref_blob),
2011         VIEW_(BLAME,  "blame",  &blame_ops,  TRUE,  ref_commit),
2012         VIEW_(BRANCH, "branch", &branch_ops, TRUE,  ref_head),
2013         VIEW_(HELP,   "help",   &help_ops,   FALSE, ""),
2014         VIEW_(PAGER,  "pager",  &pager_ops,  FALSE, "stdin"),
2015         VIEW_(STATUS, "status", &status_ops, TRUE,  ""),
2016         VIEW_(STAGE,  "stage",  &stage_ops,  TRUE,  ""),
2017 };
2019 #define VIEW(req)       (&views[(req) - REQ_OFFSET - 1])
2020 #define VIEW_REQ(view)  ((view) - views + REQ_OFFSET + 1)
2022 #define foreach_view(view, i) \
2023         for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
2025 #define view_is_displayed(view) \
2026         (view == display[0] || view == display[1])
2029 enum line_graphic {
2030         LINE_GRAPHIC_VLINE
2031 };
2033 static chtype line_graphics[] = {
2034         /* LINE_GRAPHIC_VLINE: */ '|'
2035 };
2037 static inline void
2038 set_view_attr(struct view *view, enum line_type type)
2040         if (!view->curline->selected && view->curtype != type) {
2041                 wattrset(view->win, get_line_attr(type));
2042                 wchgat(view->win, -1, 0, type, NULL);
2043                 view->curtype = type;
2044         }
2047 static int
2048 draw_chars(struct view *view, enum line_type type, const char *string,
2049            int max_len, bool use_tilde)
2051         int len = 0;
2052         int col = 0;
2053         int trimmed = FALSE;
2054         size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2056         if (max_len <= 0)
2057                 return 0;
2059         if (opt_utf8) {
2060                 len = utf8_length(&string, skip, &col, max_len, &trimmed, use_tilde);
2061         } else {
2062                 col = len = strlen(string);
2063                 if (len > max_len) {
2064                         if (use_tilde) {
2065                                 max_len -= 1;
2066                         }
2067                         col = len = max_len;
2068                         trimmed = TRUE;
2069                 }
2070         }
2072         set_view_attr(view, type);
2073         if (len > 0)
2074                 waddnstr(view->win, string, len);
2075         if (trimmed && use_tilde) {
2076                 set_view_attr(view, LINE_DELIMITER);
2077                 waddch(view->win, '~');
2078                 col++;
2079         }
2081         return col;
2084 static int
2085 draw_space(struct view *view, enum line_type type, int max, int spaces)
2087         static char space[] = "                    ";
2088         int col = 0;
2090         spaces = MIN(max, spaces);
2092         while (spaces > 0) {
2093                 int len = MIN(spaces, sizeof(space) - 1);
2095                 col += draw_chars(view, type, space, len, FALSE);
2096                 spaces -= len;
2097         }
2099         return col;
2102 static bool
2103 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
2105         view->col += draw_chars(view, type, string, view->width + view->yoffset - view->col, trim);
2106         return view->width + view->yoffset <= view->col;
2109 static bool
2110 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
2112         size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2113         int max = view->width + view->yoffset - view->col;
2114         int i;
2116         if (max < size)
2117                 size = max;
2119         set_view_attr(view, type);
2120         /* Using waddch() instead of waddnstr() ensures that
2121          * they'll be rendered correctly for the cursor line. */
2122         for (i = skip; i < size; i++)
2123                 waddch(view->win, graphic[i]);
2125         view->col += size;
2126         if (size < max && skip <= size)
2127                 waddch(view->win, ' ');
2128         view->col++;
2130         return view->width + view->yoffset <= view->col;
2133 static bool
2134 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
2136         int max = MIN(view->width + view->yoffset - view->col, len);
2137         int col;
2139         if (text)
2140                 col = draw_chars(view, type, text, max - 1, trim);
2141         else
2142                 col = draw_space(view, type, max - 1, max - 1);
2144         view->col += col;
2145         view->col += draw_space(view, LINE_DEFAULT, max - col, max - col);
2146         return view->width + view->yoffset <= view->col;
2149 static bool
2150 draw_date(struct view *view, time_t *time)
2152         const char *date = mkdate(time);
2153         int cols = opt_date == DATE_SHORT ? DATE_SHORT_COLS : DATE_COLS;
2155         return draw_field(view, LINE_DATE, date, cols, FALSE);
2158 static bool
2159 draw_author(struct view *view, const char *author)
2161         bool trim = opt_author_cols == 0 || opt_author_cols > 5 || !author;
2163         if (!trim) {
2164                 static char initials[10];
2165                 size_t pos;
2167 #define is_initial_sep(c) (isspace(c) || ispunct(c) || (c) == '@')
2169                 memset(initials, 0, sizeof(initials));
2170                 for (pos = 0; *author && pos < opt_author_cols - 1; author++, pos++) {
2171                         while (is_initial_sep(*author))
2172                                 author++;
2173                         strncpy(&initials[pos], author, sizeof(initials) - 1 - pos);
2174                         while (*author && !is_initial_sep(author[1]))
2175                                 author++;
2176                 }
2178                 author = initials;
2179         }
2181         return draw_field(view, LINE_AUTHOR, author, opt_author_cols, trim);
2184 static bool
2185 draw_mode(struct view *view, mode_t mode)
2187         const char *str;
2189         if (S_ISDIR(mode))
2190                 str = "drwxr-xr-x";
2191         else if (S_ISLNK(mode))
2192                 str = "lrwxrwxrwx";
2193         else if (S_ISGITLINK(mode))
2194                 str = "m---------";
2195         else if (S_ISREG(mode) && mode & S_IXUSR)
2196                 str = "-rwxr-xr-x";
2197         else if (S_ISREG(mode))
2198                 str = "-rw-r--r--";
2199         else
2200                 str = "----------";
2202         return draw_field(view, LINE_MODE, str, STRING_SIZE("-rw-r--r-- "), FALSE);
2205 static bool
2206 draw_lineno(struct view *view, unsigned int lineno)
2208         char number[10];
2209         int digits3 = view->digits < 3 ? 3 : view->digits;
2210         int max = MIN(view->width + view->yoffset - view->col, digits3);
2211         char *text = NULL;
2213         lineno += view->offset + 1;
2214         if (lineno == 1 || (lineno % opt_num_interval) == 0) {
2215                 static char fmt[] = "%1ld";
2217                 fmt[1] = '0' + (view->digits <= 9 ? digits3 : 1);
2218                 if (string_format(number, fmt, lineno))
2219                         text = number;
2220         }
2221         if (text)
2222                 view->col += draw_chars(view, LINE_LINE_NUMBER, text, max, TRUE);
2223         else
2224                 view->col += draw_space(view, LINE_LINE_NUMBER, max, digits3);
2225         return draw_graphic(view, LINE_DEFAULT, &line_graphics[LINE_GRAPHIC_VLINE], 1);
2228 static bool
2229 draw_view_line(struct view *view, unsigned int lineno)
2231         struct line *line;
2232         bool selected = (view->offset + lineno == view->lineno);
2234         assert(view_is_displayed(view));
2236         if (view->offset + lineno >= view->lines)
2237                 return FALSE;
2239         line = &view->line[view->offset + lineno];
2241         wmove(view->win, lineno, 0);
2242         if (line->cleareol)
2243                 wclrtoeol(view->win);
2244         view->col = 0;
2245         view->curline = line;
2246         view->curtype = LINE_NONE;
2247         line->selected = FALSE;
2248         line->dirty = line->cleareol = 0;
2250         if (selected) {
2251                 set_view_attr(view, LINE_CURSOR);
2252                 line->selected = TRUE;
2253                 view->ops->select(view, line);
2254         }
2256         return view->ops->draw(view, line, lineno);
2259 static void
2260 redraw_view_dirty(struct view *view)
2262         bool dirty = FALSE;
2263         int lineno;
2265         for (lineno = 0; lineno < view->height; lineno++) {
2266                 if (view->offset + lineno >= view->lines)
2267                         break;
2268                 if (!view->line[view->offset + lineno].dirty)
2269                         continue;
2270                 dirty = TRUE;
2271                 if (!draw_view_line(view, lineno))
2272                         break;
2273         }
2275         if (!dirty)
2276                 return;
2277         wnoutrefresh(view->win);
2280 static void
2281 redraw_view_from(struct view *view, int lineno)
2283         assert(0 <= lineno && lineno < view->height);
2285         for (; lineno < view->height; lineno++) {
2286                 if (!draw_view_line(view, lineno))
2287                         break;
2288         }
2290         wnoutrefresh(view->win);
2293 static void
2294 redraw_view(struct view *view)
2296         werase(view->win);
2297         redraw_view_from(view, 0);
2301 static void
2302 update_view_title(struct view *view)
2304         char buf[SIZEOF_STR];
2305         char state[SIZEOF_STR];
2306         size_t bufpos = 0, statelen = 0;
2308         assert(view_is_displayed(view));
2310         if (view != VIEW(REQ_VIEW_STATUS) && view->lines) {
2311                 unsigned int view_lines = view->offset + view->height;
2312                 unsigned int lines = view->lines
2313                                    ? MIN(view_lines, view->lines) * 100 / view->lines
2314                                    : 0;
2316                 string_format_from(state, &statelen, " - %s %d of %d (%d%%)",
2317                                    view->ops->type,
2318                                    view->lineno + 1,
2319                                    view->lines,
2320                                    lines);
2322         }
2324         if (view->pipe) {
2325                 time_t secs = time(NULL) - view->start_time;
2327                 /* Three git seconds are a long time ... */
2328                 if (secs > 2)
2329                         string_format_from(state, &statelen, " loading %lds", secs);
2330         }
2332         string_format_from(buf, &bufpos, "[%s]", view->name);
2333         if (*view->ref && bufpos < view->width) {
2334                 size_t refsize = strlen(view->ref);
2335                 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2337                 if (minsize < view->width)
2338                         refsize = view->width - minsize + 7;
2339                 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2340         }
2342         if (statelen && bufpos < view->width) {
2343                 string_format_from(buf, &bufpos, "%s", state);
2344         }
2346         if (view == display[current_view])
2347                 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2348         else
2349                 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2351         mvwaddnstr(view->title, 0, 0, buf, bufpos);
2352         wclrtoeol(view->title);
2353         wnoutrefresh(view->title);
2356 static int
2357 apply_step(double step, int value)
2359         if (step >= 1)
2360                 return (int) step;
2361         value *= step + 0.01;
2362         return value ? value : 1;
2365 static void
2366 resize_display(void)
2368         int offset, i;
2369         struct view *base = display[0];
2370         struct view *view = display[1] ? display[1] : display[0];
2372         /* Setup window dimensions */
2374         getmaxyx(stdscr, base->height, base->width);
2376         /* Make room for the status window. */
2377         base->height -= 1;
2379         if (view != base) {
2380                 /* Horizontal split. */
2381                 view->width   = base->width;
2382                 view->height  = apply_step(opt_scale_split_view, base->height);
2383                 view->height  = MAX(view->height, MIN_VIEW_HEIGHT);
2384                 view->height  = MIN(view->height, base->height - MIN_VIEW_HEIGHT);
2385                 base->height -= view->height;
2387                 /* Make room for the title bar. */
2388                 view->height -= 1;
2389         }
2391         /* Make room for the title bar. */
2392         base->height -= 1;
2394         offset = 0;
2396         foreach_displayed_view (view, i) {
2397                 if (!view->win) {
2398                         view->win = newwin(view->height, 0, offset, 0);
2399                         if (!view->win)
2400                                 die("Failed to create %s view", view->name);
2402                         scrollok(view->win, FALSE);
2404                         view->title = newwin(1, 0, offset + view->height, 0);
2405                         if (!view->title)
2406                                 die("Failed to create title window");
2408                 } else {
2409                         wresize(view->win, view->height, view->width);
2410                         mvwin(view->win,   offset, 0);
2411                         mvwin(view->title, offset + view->height, 0);
2412                 }
2414                 offset += view->height + 1;
2415         }
2418 static void
2419 redraw_display(bool clear)
2421         struct view *view;
2422         int i;
2424         foreach_displayed_view (view, i) {
2425                 if (clear)
2426                         wclear(view->win);
2427                 redraw_view(view);
2428                 update_view_title(view);
2429         }
2432 static void
2433 toggle_date_option(enum date *date)
2435         static const char *help[] = {
2436                 "no",
2437                 "default",
2438                 "relative",
2439                 "short"
2440         };
2442         opt_date = (opt_date + 1) % ARRAY_SIZE(help);
2443         redraw_display(FALSE);
2444         report("Displaying %s dates", help[opt_date]);
2447 static void
2448 toggle_view_option(bool *option, const char *help)
2450         *option = !*option;
2451         redraw_display(FALSE);
2452         report("%sabling %s", *option ? "En" : "Dis", help);
2455 static void
2456 open_option_menu(void)
2458         const struct menu_item menu[] = {
2459                 { '.', "line numbers", &opt_line_number },
2460                 { 'D', "date display", &opt_date },
2461                 { 'A', "author display", &opt_author },
2462                 { 'g', "revision graph display", &opt_rev_graph },
2463                 { 'F', "reference display", &opt_show_refs },
2464                 { 0 }
2465         };
2466         int selected = 0;
2468         if (prompt_menu("Toggle option", menu, &selected)) {
2469                 if (menu[selected].data == &opt_date)
2470                         toggle_date_option(menu[selected].data);
2471                 else
2472                         toggle_view_option(menu[selected].data, menu[selected].text);
2473         }
2476 static void
2477 maximize_view(struct view *view)
2479         memset(display, 0, sizeof(display));
2480         current_view = 0;
2481         display[current_view] = view;
2482         resize_display();
2483         redraw_display(FALSE);
2484         report("");
2488 /*
2489  * Navigation
2490  */
2492 static bool
2493 goto_view_line(struct view *view, unsigned long offset, unsigned long lineno)
2495         if (lineno >= view->lines)
2496                 lineno = view->lines > 0 ? view->lines - 1 : 0;
2498         if (offset > lineno || offset + view->height <= lineno) {
2499                 unsigned long half = view->height / 2;
2501                 if (lineno > half)
2502                         offset = lineno - half;
2503                 else
2504                         offset = 0;
2505         }
2507         if (offset != view->offset || lineno != view->lineno) {
2508                 view->offset = offset;
2509                 view->lineno = lineno;
2510                 return TRUE;
2511         }
2513         return FALSE;
2516 /* Scrolling backend */
2517 static void
2518 do_scroll_view(struct view *view, int lines)
2520         bool redraw_current_line = FALSE;
2522         /* The rendering expects the new offset. */
2523         view->offset += lines;
2525         assert(0 <= view->offset && view->offset < view->lines);
2526         assert(lines);
2528         /* Move current line into the view. */
2529         if (view->lineno < view->offset) {
2530                 view->lineno = view->offset;
2531                 redraw_current_line = TRUE;
2532         } else if (view->lineno >= view->offset + view->height) {
2533                 view->lineno = view->offset + view->height - 1;
2534                 redraw_current_line = TRUE;
2535         }
2537         assert(view->offset <= view->lineno && view->lineno < view->lines);
2539         /* Redraw the whole screen if scrolling is pointless. */
2540         if (view->height < ABS(lines)) {
2541                 redraw_view(view);
2543         } else {
2544                 int line = lines > 0 ? view->height - lines : 0;
2545                 int end = line + ABS(lines);
2547                 scrollok(view->win, TRUE);
2548                 wscrl(view->win, lines);
2549                 scrollok(view->win, FALSE);
2551                 while (line < end && draw_view_line(view, line))
2552                         line++;
2554                 if (redraw_current_line)
2555                         draw_view_line(view, view->lineno - view->offset);
2556                 wnoutrefresh(view->win);
2557         }
2559         view->has_scrolled = TRUE;
2560         report("");
2563 /* Scroll frontend */
2564 static void
2565 scroll_view(struct view *view, enum request request)
2567         int lines = 1;
2569         assert(view_is_displayed(view));
2571         switch (request) {
2572         case REQ_SCROLL_LEFT:
2573                 if (view->yoffset == 0) {
2574                         report("Cannot scroll beyond the first column");
2575                         return;
2576                 }
2577                 if (view->yoffset <= apply_step(opt_hscroll, view->width))
2578                         view->yoffset = 0;
2579                 else
2580                         view->yoffset -= apply_step(opt_hscroll, view->width);
2581                 redraw_view_from(view, 0);
2582                 report("");
2583                 return;
2584         case REQ_SCROLL_RIGHT:
2585                 view->yoffset += apply_step(opt_hscroll, view->width);
2586                 redraw_view(view);
2587                 report("");
2588                 return;
2589         case REQ_SCROLL_PAGE_DOWN:
2590                 lines = view->height;
2591         case REQ_SCROLL_LINE_DOWN:
2592                 if (view->offset + lines > view->lines)
2593                         lines = view->lines - view->offset;
2595                 if (lines == 0 || view->offset + view->height >= view->lines) {
2596                         report("Cannot scroll beyond the last line");
2597                         return;
2598                 }
2599                 break;
2601         case REQ_SCROLL_PAGE_UP:
2602                 lines = view->height;
2603         case REQ_SCROLL_LINE_UP:
2604                 if (lines > view->offset)
2605                         lines = view->offset;
2607                 if (lines == 0) {
2608                         report("Cannot scroll beyond the first line");
2609                         return;
2610                 }
2612                 lines = -lines;
2613                 break;
2615         default:
2616                 die("request %d not handled in switch", request);
2617         }
2619         do_scroll_view(view, lines);
2622 /* Cursor moving */
2623 static void
2624 move_view(struct view *view, enum request request)
2626         int scroll_steps = 0;
2627         int steps;
2629         switch (request) {
2630         case REQ_MOVE_FIRST_LINE:
2631                 steps = -view->lineno;
2632                 break;
2634         case REQ_MOVE_LAST_LINE:
2635                 steps = view->lines - view->lineno - 1;
2636                 break;
2638         case REQ_MOVE_PAGE_UP:
2639                 steps = view->height > view->lineno
2640                       ? -view->lineno : -view->height;
2641                 break;
2643         case REQ_MOVE_PAGE_DOWN:
2644                 steps = view->lineno + view->height >= view->lines
2645                       ? view->lines - view->lineno - 1 : view->height;
2646                 break;
2648         case REQ_MOVE_UP:
2649                 steps = -1;
2650                 break;
2652         case REQ_MOVE_DOWN:
2653                 steps = 1;
2654                 break;
2656         default:
2657                 die("request %d not handled in switch", request);
2658         }
2660         if (steps <= 0 && view->lineno == 0) {
2661                 report("Cannot move beyond the first line");
2662                 return;
2664         } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2665                 report("Cannot move beyond the last line");
2666                 return;
2667         }
2669         /* Move the current line */
2670         view->lineno += steps;
2671         assert(0 <= view->lineno && view->lineno < view->lines);
2673         /* Check whether the view needs to be scrolled */
2674         if (view->lineno < view->offset ||
2675             view->lineno >= view->offset + view->height) {
2676                 scroll_steps = steps;
2677                 if (steps < 0 && -steps > view->offset) {
2678                         scroll_steps = -view->offset;
2680                 } else if (steps > 0) {
2681                         if (view->lineno == view->lines - 1 &&
2682                             view->lines > view->height) {
2683                                 scroll_steps = view->lines - view->offset - 1;
2684                                 if (scroll_steps >= view->height)
2685                                         scroll_steps -= view->height - 1;
2686                         }
2687                 }
2688         }
2690         if (!view_is_displayed(view)) {
2691                 view->offset += scroll_steps;
2692                 assert(0 <= view->offset && view->offset < view->lines);
2693                 view->ops->select(view, &view->line[view->lineno]);
2694                 return;
2695         }
2697         /* Repaint the old "current" line if we be scrolling */
2698         if (ABS(steps) < view->height)
2699                 draw_view_line(view, view->lineno - steps - view->offset);
2701         if (scroll_steps) {
2702                 do_scroll_view(view, scroll_steps);
2703                 return;
2704         }
2706         /* Draw the current line */
2707         draw_view_line(view, view->lineno - view->offset);
2709         wnoutrefresh(view->win);
2710         report("");
2714 /*
2715  * Searching
2716  */
2718 static void search_view(struct view *view, enum request request);
2720 static bool
2721 grep_text(struct view *view, const char *text[])
2723         regmatch_t pmatch;
2724         size_t i;
2726         for (i = 0; text[i]; i++)
2727                 if (*text[i] &&
2728                     regexec(view->regex, text[i], 1, &pmatch, 0) != REG_NOMATCH)
2729                         return TRUE;
2730         return FALSE;
2733 static void
2734 select_view_line(struct view *view, unsigned long lineno)
2736         unsigned long old_lineno = view->lineno;
2737         unsigned long old_offset = view->offset;
2739         if (goto_view_line(view, view->offset, lineno)) {
2740                 if (view_is_displayed(view)) {
2741                         if (old_offset != view->offset) {
2742                                 redraw_view(view);
2743                         } else {
2744                                 draw_view_line(view, old_lineno - view->offset);
2745                                 draw_view_line(view, view->lineno - view->offset);
2746                                 wnoutrefresh(view->win);
2747                         }
2748                 } else {
2749                         view->ops->select(view, &view->line[view->lineno]);
2750                 }
2751         }
2754 static void
2755 find_next(struct view *view, enum request request)
2757         unsigned long lineno = view->lineno;
2758         int direction;
2760         if (!*view->grep) {
2761                 if (!*opt_search)
2762                         report("No previous search");
2763                 else
2764                         search_view(view, request);
2765                 return;
2766         }
2768         switch (request) {
2769         case REQ_SEARCH:
2770         case REQ_FIND_NEXT:
2771                 direction = 1;
2772                 break;
2774         case REQ_SEARCH_BACK:
2775         case REQ_FIND_PREV:
2776                 direction = -1;
2777                 break;
2779         default:
2780                 return;
2781         }
2783         if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
2784                 lineno += direction;
2786         /* Note, lineno is unsigned long so will wrap around in which case it
2787          * will become bigger than view->lines. */
2788         for (; lineno < view->lines; lineno += direction) {
2789                 if (view->ops->grep(view, &view->line[lineno])) {
2790                         select_view_line(view, lineno);
2791                         report("Line %ld matches '%s'", lineno + 1, view->grep);
2792                         return;
2793                 }
2794         }
2796         report("No match found for '%s'", view->grep);
2799 static void
2800 search_view(struct view *view, enum request request)
2802         int regex_err;
2804         if (view->regex) {
2805                 regfree(view->regex);
2806                 *view->grep = 0;
2807         } else {
2808                 view->regex = calloc(1, sizeof(*view->regex));
2809                 if (!view->regex)
2810                         return;
2811         }
2813         regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
2814         if (regex_err != 0) {
2815                 char buf[SIZEOF_STR] = "unknown error";
2817                 regerror(regex_err, view->regex, buf, sizeof(buf));
2818                 report("Search failed: %s", buf);
2819                 return;
2820         }
2822         string_copy(view->grep, opt_search);
2824         find_next(view, request);
2827 /*
2828  * Incremental updating
2829  */
2831 static void
2832 reset_view(struct view *view)
2834         int i;
2836         for (i = 0; i < view->lines; i++)
2837                 free(view->line[i].data);
2838         free(view->line);
2840         view->p_offset = view->offset;
2841         view->p_yoffset = view->yoffset;
2842         view->p_lineno = view->lineno;
2844         view->line = NULL;
2845         view->offset = 0;
2846         view->yoffset = 0;
2847         view->lines  = 0;
2848         view->lineno = 0;
2849         view->vid[0] = 0;
2850         view->update_secs = 0;
2853 static void
2854 free_argv(const char *argv[])
2856         int argc;
2858         for (argc = 0; argv[argc]; argc++)
2859                 free((void *) argv[argc]);
2862 static bool
2863 format_argv(const char *dst_argv[], const char *src_argv[], enum format_flags flags)
2865         char buf[SIZEOF_STR];
2866         int argc;
2867         bool noreplace = flags == FORMAT_NONE;
2869         free_argv(dst_argv);
2871         for (argc = 0; src_argv[argc]; argc++) {
2872                 const char *arg = src_argv[argc];
2873                 size_t bufpos = 0;
2875                 while (arg) {
2876                         char *next = strstr(arg, "%(");
2877                         int len = next - arg;
2878                         const char *value;
2880                         if (!next || noreplace) {
2881                                 if (flags == FORMAT_DASH && !strcmp(arg, "--"))
2882                                         noreplace = TRUE;
2883                                 len = strlen(arg);
2884                                 value = "";
2886                         } else if (!prefixcmp(next, "%(directory)")) {
2887                                 value = opt_path;
2889                         } else if (!prefixcmp(next, "%(file)")) {
2890                                 value = opt_file;
2892                         } else if (!prefixcmp(next, "%(ref)")) {
2893                                 value = *opt_ref ? opt_ref : "HEAD";
2895                         } else if (!prefixcmp(next, "%(head)")) {
2896                                 value = ref_head;
2898                         } else if (!prefixcmp(next, "%(commit)")) {
2899                                 value = ref_commit;
2901                         } else if (!prefixcmp(next, "%(blob)")) {
2902                                 value = ref_blob;
2904                         } else {
2905                                 report("Unknown replacement: `%s`", next);
2906                                 return FALSE;
2907                         }
2909                         if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
2910                                 return FALSE;
2912                         arg = next && !noreplace ? strchr(next, ')') + 1 : NULL;
2913                 }
2915                 dst_argv[argc] = strdup(buf);
2916                 if (!dst_argv[argc])
2917                         break;
2918         }
2920         dst_argv[argc] = NULL;
2922         return src_argv[argc] == NULL;
2925 static bool
2926 restore_view_position(struct view *view)
2928         if (!view->p_restore || (view->pipe && view->lines <= view->p_lineno))
2929                 return FALSE;
2931         /* Changing the view position cancels the restoring. */
2932         /* FIXME: Changing back to the first line is not detected. */
2933         if (view->offset != 0 || view->lineno != 0) {
2934                 view->p_restore = FALSE;
2935                 return FALSE;
2936         }
2938         if (goto_view_line(view, view->p_offset, view->p_lineno) &&
2939             view_is_displayed(view))
2940                 werase(view->win);
2942         view->yoffset = view->p_yoffset;
2943         view->p_restore = FALSE;
2945         return TRUE;
2948 static void
2949 end_update(struct view *view, bool force)
2951         if (!view->pipe)
2952                 return;
2953         while (!view->ops->read(view, NULL))
2954                 if (!force)
2955                         return;
2956         set_nonblocking_input(FALSE);
2957         if (force)
2958                 kill_io(view->pipe);
2959         done_io(view->pipe);
2960         view->pipe = NULL;
2963 static void
2964 setup_update(struct view *view, const char *vid)
2966         set_nonblocking_input(TRUE);
2967         reset_view(view);
2968         string_copy_rev(view->vid, vid);
2969         view->pipe = &view->io;
2970         view->start_time = time(NULL);
2973 static bool
2974 prepare_update(struct view *view, const char *argv[], const char *dir,
2975                enum format_flags flags)
2977         if (view->pipe)
2978                 end_update(view, TRUE);
2979         return init_io_rd(&view->io, argv, dir, flags);
2982 static bool
2983 prepare_update_file(struct view *view, const char *name)
2985         if (view->pipe)
2986                 end_update(view, TRUE);
2987         return io_open(&view->io, name);
2990 static bool
2991 begin_update(struct view *view, bool refresh)
2993         if (view->pipe)
2994                 end_update(view, TRUE);
2996         if (refresh) {
2997                 if (!start_io(&view->io))
2998                         return FALSE;
3000         } else {
3001                 if (view == VIEW(REQ_VIEW_TREE) && strcmp(view->vid, view->id))
3002                         opt_path[0] = 0;
3004                 if (!run_io_rd(&view->io, view->ops->argv, FORMAT_ALL))
3005                         return FALSE;
3007                 /* Put the current ref_* value to the view title ref
3008                  * member. This is needed by the blob view. Most other
3009                  * views sets it automatically after loading because the
3010                  * first line is a commit line. */
3011                 string_copy_rev(view->ref, view->id);
3012         }
3014         setup_update(view, view->id);
3016         return TRUE;
3019 static bool
3020 update_view(struct view *view)
3022         char out_buffer[BUFSIZ * 2];
3023         char *line;
3024         /* Clear the view and redraw everything since the tree sorting
3025          * might have rearranged things. */
3026         bool redraw = view->lines == 0;
3027         bool can_read = TRUE;
3029         if (!view->pipe)
3030                 return TRUE;
3032         if (!io_can_read(view->pipe)) {
3033                 if (view->lines == 0 && view_is_displayed(view)) {
3034                         time_t secs = time(NULL) - view->start_time;
3036                         if (secs > 1 && secs > view->update_secs) {
3037                                 if (view->update_secs == 0)
3038                                         redraw_view(view);
3039                                 update_view_title(view);
3040                                 view->update_secs = secs;
3041                         }
3042                 }
3043                 return TRUE;
3044         }
3046         for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
3047                 if (opt_iconv != ICONV_NONE) {
3048                         ICONV_CONST char *inbuf = line;
3049                         size_t inlen = strlen(line) + 1;
3051                         char *outbuf = out_buffer;
3052                         size_t outlen = sizeof(out_buffer);
3054                         size_t ret;
3056                         ret = iconv(opt_iconv, &inbuf, &inlen, &outbuf, &outlen);
3057                         if (ret != (size_t) -1)
3058                                 line = out_buffer;
3059                 }
3061                 if (!view->ops->read(view, line)) {
3062                         report("Allocation failure");
3063                         end_update(view, TRUE);
3064                         return FALSE;
3065                 }
3066         }
3068         {
3069                 unsigned long lines = view->lines;
3070                 int digits;
3072                 for (digits = 0; lines; digits++)
3073                         lines /= 10;
3075                 /* Keep the displayed view in sync with line number scaling. */
3076                 if (digits != view->digits) {
3077                         view->digits = digits;
3078                         if (opt_line_number || view == VIEW(REQ_VIEW_BLAME))
3079                                 redraw = TRUE;
3080                 }
3081         }
3083         if (io_error(view->pipe)) {
3084                 report("Failed to read: %s", io_strerror(view->pipe));
3085                 end_update(view, TRUE);
3087         } else if (io_eof(view->pipe)) {
3088                 report("");
3089                 end_update(view, FALSE);
3090         }
3092         if (restore_view_position(view))
3093                 redraw = TRUE;
3095         if (!view_is_displayed(view))
3096                 return TRUE;
3098         if (redraw)
3099                 redraw_view_from(view, 0);
3100         else
3101                 redraw_view_dirty(view);
3103         /* Update the title _after_ the redraw so that if the redraw picks up a
3104          * commit reference in view->ref it'll be available here. */
3105         update_view_title(view);
3106         return TRUE;
3109 DEFINE_ALLOCATOR(realloc_lines, struct line, 256)
3111 static struct line *
3112 add_line_data(struct view *view, void *data, enum line_type type)
3114         struct line *line;
3116         if (!realloc_lines(&view->line, view->lines, 1))
3117                 return NULL;
3119         line = &view->line[view->lines++];
3120         memset(line, 0, sizeof(*line));
3121         line->type = type;
3122         line->data = data;
3123         line->dirty = 1;
3125         return line;
3128 static struct line *
3129 add_line_text(struct view *view, const char *text, enum line_type type)
3131         char *data = text ? strdup(text) : NULL;
3133         return data ? add_line_data(view, data, type) : NULL;
3136 static struct line *
3137 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
3139         char buf[SIZEOF_STR];
3140         va_list args;
3142         va_start(args, fmt);
3143         if (vsnprintf(buf, sizeof(buf), fmt, args) >= sizeof(buf))
3144                 buf[0] = 0;
3145         va_end(args);
3147         return buf[0] ? add_line_text(view, buf, type) : NULL;
3150 /*
3151  * View opening
3152  */
3154 enum open_flags {
3155         OPEN_DEFAULT = 0,       /* Use default view switching. */
3156         OPEN_SPLIT = 1,         /* Split current view. */
3157         OPEN_RELOAD = 4,        /* Reload view even if it is the current. */
3158         OPEN_REFRESH = 16,      /* Refresh view using previous command. */
3159         OPEN_PREPARED = 32,     /* Open already prepared command. */
3160 };
3162 static void
3163 open_view(struct view *prev, enum request request, enum open_flags flags)
3165         bool split = !!(flags & OPEN_SPLIT);
3166         bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
3167         bool nomaximize = !!(flags & OPEN_REFRESH);
3168         struct view *view = VIEW(request);
3169         int nviews = displayed_views();
3170         struct view *base_view = display[0];
3172         if (view == prev && nviews == 1 && !reload) {
3173                 report("Already in %s view", view->name);
3174                 return;
3175         }
3177         if (view->git_dir && !opt_git_dir[0]) {
3178                 report("The %s view is disabled in pager view", view->name);
3179                 return;
3180         }
3182         if (split) {
3183                 display[1] = view;
3184                 current_view = 1;
3185         } else if (!nomaximize) {
3186                 /* Maximize the current view. */
3187                 memset(display, 0, sizeof(display));
3188                 current_view = 0;
3189                 display[current_view] = view;
3190         }
3192         /* Resize the view when switching between split- and full-screen,
3193          * or when switching between two different full-screen views. */
3194         if (nviews != displayed_views() ||
3195             (nviews == 1 && base_view != display[0]))
3196                 resize_display();
3198         if (view->ops->open) {
3199                 if (view->pipe)
3200                         end_update(view, TRUE);
3201                 if (!view->ops->open(view)) {
3202                         report("Failed to load %s view", view->name);
3203                         return;
3204                 }
3205                 restore_view_position(view);
3207         } else if ((reload || strcmp(view->vid, view->id)) &&
3208                    !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
3209                 report("Failed to load %s view", view->name);
3210                 return;
3211         }
3213         if (split && prev->lineno - prev->offset >= prev->height) {
3214                 /* Take the title line into account. */
3215                 int lines = prev->lineno - prev->offset - prev->height + 1;
3217                 /* Scroll the view that was split if the current line is
3218                  * outside the new limited view. */
3219                 do_scroll_view(prev, lines);
3220         }
3222         if (prev && view != prev) {
3223                 if (split) {
3224                         /* "Blur" the previous view. */
3225                         update_view_title(prev);
3226                 }
3228                 view->parent = prev;
3229         }
3231         if (view->pipe && view->lines == 0) {
3232                 /* Clear the old view and let the incremental updating refill
3233                  * the screen. */
3234                 werase(view->win);
3235                 view->p_restore = flags & (OPEN_RELOAD | OPEN_REFRESH);
3236                 report("");
3237         } else if (view_is_displayed(view)) {
3238                 redraw_view(view);
3239                 report("");
3240         }
3243 static void
3244 open_external_viewer(const char *argv[], const char *dir)
3246         def_prog_mode();           /* save current tty modes */
3247         endwin();                  /* restore original tty modes */
3248         run_io_fg(argv, dir);
3249         fprintf(stderr, "Press Enter to continue");
3250         getc(opt_tty);
3251         reset_prog_mode();
3252         redraw_display(TRUE);
3255 static void
3256 open_mergetool(const char *file)
3258         const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
3260         open_external_viewer(mergetool_argv, opt_cdup);
3263 static void
3264 open_editor(bool from_root, const char *file)
3266         const char *editor_argv[] = { "vi", file, NULL };
3267         const char *editor;
3269         editor = getenv("GIT_EDITOR");
3270         if (!editor && *opt_editor)
3271                 editor = opt_editor;
3272         if (!editor)
3273                 editor = getenv("VISUAL");
3274         if (!editor)
3275                 editor = getenv("EDITOR");
3276         if (!editor)
3277                 editor = "vi";
3279         editor_argv[0] = editor;
3280         open_external_viewer(editor_argv, from_root ? opt_cdup : NULL);
3283 static void
3284 open_run_request(enum request request)
3286         struct run_request *req = get_run_request(request);
3287         const char *argv[ARRAY_SIZE(req->argv)] = { NULL };
3289         if (!req) {
3290                 report("Unknown run request");
3291                 return;
3292         }
3294         if (format_argv(argv, req->argv, FORMAT_ALL))
3295                 open_external_viewer(argv, NULL);
3296         free_argv(argv);
3299 /*
3300  * User request switch noodle
3301  */
3303 static int
3304 view_driver(struct view *view, enum request request)
3306         int i;
3308         if (request == REQ_NONE)
3309                 return TRUE;
3311         if (request > REQ_NONE) {
3312                 open_run_request(request);
3313                 /* FIXME: When all views can refresh always do this. */
3314                 if (view == VIEW(REQ_VIEW_STATUS) ||
3315                     view == VIEW(REQ_VIEW_MAIN) ||
3316                     view == VIEW(REQ_VIEW_LOG) ||
3317                     view == VIEW(REQ_VIEW_BRANCH) ||
3318                     view == VIEW(REQ_VIEW_STAGE))
3319                         request = REQ_REFRESH;
3320                 else
3321                         return TRUE;
3322         }
3324         if (view && view->lines) {
3325                 request = view->ops->request(view, request, &view->line[view->lineno]);
3326                 if (request == REQ_NONE)
3327                         return TRUE;
3328         }
3330         switch (request) {
3331         case REQ_MOVE_UP:
3332         case REQ_MOVE_DOWN:
3333         case REQ_MOVE_PAGE_UP:
3334         case REQ_MOVE_PAGE_DOWN:
3335         case REQ_MOVE_FIRST_LINE:
3336         case REQ_MOVE_LAST_LINE:
3337                 move_view(view, request);
3338                 break;
3340         case REQ_SCROLL_LEFT:
3341         case REQ_SCROLL_RIGHT:
3342         case REQ_SCROLL_LINE_DOWN:
3343         case REQ_SCROLL_LINE_UP:
3344         case REQ_SCROLL_PAGE_DOWN:
3345         case REQ_SCROLL_PAGE_UP:
3346                 scroll_view(view, request);
3347                 break;
3349         case REQ_VIEW_BLAME:
3350                 if (!opt_file[0]) {
3351                         report("No file chosen, press %s to open tree view",
3352                                get_key(view->keymap, REQ_VIEW_TREE));
3353                         break;
3354                 }
3355                 open_view(view, request, OPEN_DEFAULT);
3356                 break;
3358         case REQ_VIEW_BLOB:
3359                 if (!ref_blob[0]) {
3360                         report("No file chosen, press %s to open tree view",
3361                                get_key(view->keymap, REQ_VIEW_TREE));
3362                         break;
3363                 }
3364                 open_view(view, request, OPEN_DEFAULT);
3365                 break;
3367         case REQ_VIEW_PAGER:
3368                 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3369                         report("No pager content, press %s to run command from prompt",
3370                                get_key(view->keymap, REQ_PROMPT));
3371                         break;
3372                 }
3373                 open_view(view, request, OPEN_DEFAULT);
3374                 break;
3376         case REQ_VIEW_STAGE:
3377                 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3378                         report("No stage content, press %s to open the status view and choose file",
3379                                get_key(view->keymap, REQ_VIEW_STATUS));
3380                         break;
3381                 }
3382                 open_view(view, request, OPEN_DEFAULT);
3383                 break;
3385         case REQ_VIEW_STATUS:
3386                 if (opt_is_inside_work_tree == FALSE) {
3387                         report("The status view requires a working tree");
3388                         break;
3389                 }
3390                 open_view(view, request, OPEN_DEFAULT);
3391                 break;
3393         case REQ_VIEW_MAIN:
3394         case REQ_VIEW_DIFF:
3395         case REQ_VIEW_LOG:
3396         case REQ_VIEW_TREE:
3397         case REQ_VIEW_HELP:
3398         case REQ_VIEW_BRANCH:
3399                 open_view(view, request, OPEN_DEFAULT);
3400                 break;
3402         case REQ_NEXT:
3403         case REQ_PREVIOUS:
3404                 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3406                 if ((view == VIEW(REQ_VIEW_DIFF) &&
3407                      view->parent == VIEW(REQ_VIEW_MAIN)) ||
3408                    (view == VIEW(REQ_VIEW_DIFF) &&
3409                      view->parent == VIEW(REQ_VIEW_BLAME)) ||
3410                    (view == VIEW(REQ_VIEW_STAGE) &&
3411                      view->parent == VIEW(REQ_VIEW_STATUS)) ||
3412                    (view == VIEW(REQ_VIEW_BLOB) &&
3413                      view->parent == VIEW(REQ_VIEW_TREE)) ||
3414                    (view == VIEW(REQ_VIEW_MAIN) &&
3415                      view->parent == VIEW(REQ_VIEW_BRANCH))) {
3416                         int line;
3418                         view = view->parent;
3419                         line = view->lineno;
3420                         move_view(view, request);
3421                         if (view_is_displayed(view))
3422                                 update_view_title(view);
3423                         if (line != view->lineno)
3424                                 view->ops->request(view, REQ_ENTER,
3425                                                    &view->line[view->lineno]);
3427                 } else {
3428                         move_view(view, request);
3429                 }
3430                 break;
3432         case REQ_VIEW_NEXT:
3433         {
3434                 int nviews = displayed_views();
3435                 int next_view = (current_view + 1) % nviews;
3437                 if (next_view == current_view) {
3438                         report("Only one view is displayed");
3439                         break;
3440                 }
3442                 current_view = next_view;
3443                 /* Blur out the title of the previous view. */
3444                 update_view_title(view);
3445                 report("");
3446                 break;
3447         }
3448         case REQ_REFRESH:
3449                 report("Refreshing is not yet supported for the %s view", view->name);
3450                 break;
3452         case REQ_MAXIMIZE:
3453                 if (displayed_views() == 2)
3454                         maximize_view(view);
3455                 break;
3457         case REQ_OPTIONS:
3458                 open_option_menu();
3459                 break;
3461         case REQ_TOGGLE_LINENO:
3462                 toggle_view_option(&opt_line_number, "line numbers");
3463                 break;
3465         case REQ_TOGGLE_DATE:
3466                 toggle_date_option(&opt_date);
3467                 break;
3469         case REQ_TOGGLE_AUTHOR:
3470                 toggle_view_option(&opt_author, "author display");
3471                 break;
3473         case REQ_TOGGLE_REV_GRAPH:
3474                 toggle_view_option(&opt_rev_graph, "revision graph display");
3475                 break;
3477         case REQ_TOGGLE_REFS:
3478                 toggle_view_option(&opt_show_refs, "reference display");
3479                 break;
3481         case REQ_TOGGLE_SORT_FIELD:
3482         case REQ_TOGGLE_SORT_ORDER:
3483                 report("Sorting is not yet supported for the %s view", view->name);
3484                 break;
3486         case REQ_SEARCH:
3487         case REQ_SEARCH_BACK:
3488                 search_view(view, request);
3489                 break;
3491         case REQ_FIND_NEXT:
3492         case REQ_FIND_PREV:
3493                 find_next(view, request);
3494                 break;
3496         case REQ_STOP_LOADING:
3497                 for (i = 0; i < ARRAY_SIZE(views); i++) {
3498                         view = &views[i];
3499                         if (view->pipe)
3500                                 report("Stopped loading the %s view", view->name),
3501                         end_update(view, TRUE);
3502                 }
3503                 break;
3505         case REQ_SHOW_VERSION:
3506                 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3507                 return TRUE;
3509         case REQ_SCREEN_REDRAW:
3510                 redraw_display(TRUE);
3511                 break;
3513         case REQ_EDIT:
3514                 report("Nothing to edit");
3515                 break;
3517         case REQ_ENTER:
3518                 report("Nothing to enter");
3519                 break;
3521         case REQ_VIEW_CLOSE:
3522                 /* XXX: Mark closed views by letting view->parent point to the
3523                  * view itself. Parents to closed view should never be
3524                  * followed. */
3525                 if (view->parent &&
3526                     view->parent->parent != view->parent) {
3527                         maximize_view(view->parent);
3528                         view->parent = view;
3529                         break;
3530                 }
3531                 /* Fall-through */
3532         case REQ_QUIT:
3533                 return FALSE;
3535         default:
3536                 report("Unknown key, press %s for help",
3537                        get_key(view->keymap, REQ_VIEW_HELP));
3538                 return TRUE;
3539         }
3541         return TRUE;
3545 /*
3546  * View backend utilities
3547  */
3549 enum sort_field {
3550         ORDERBY_NAME,
3551         ORDERBY_DATE,
3552         ORDERBY_AUTHOR,
3553 };
3555 struct sort_state {
3556         const enum sort_field *fields;
3557         size_t size, current;
3558         bool reverse;
3559 };
3561 #define SORT_STATE(fields) { fields, ARRAY_SIZE(fields), 0 }
3562 #define get_sort_field(state) ((state).fields[(state).current])
3563 #define sort_order(state, result) ((state).reverse ? -(result) : (result))
3565 static void
3566 sort_view(struct view *view, enum request request, struct sort_state *state,
3567           int (*compare)(const void *, const void *))
3569         switch (request) {
3570         case REQ_TOGGLE_SORT_FIELD:
3571                 state->current = (state->current + 1) % state->size;
3572                 break;
3574         case REQ_TOGGLE_SORT_ORDER:
3575                 state->reverse = !state->reverse;
3576                 break;
3577         default:
3578                 die("Not a sort request");
3579         }
3581         qsort(view->line, view->lines, sizeof(*view->line), compare);
3582         redraw_view(view);
3585 DEFINE_ALLOCATOR(realloc_authors, const char *, 256)
3587 /* Small author cache to reduce memory consumption. It uses binary
3588  * search to lookup or find place to position new entries. No entries
3589  * are ever freed. */
3590 static const char *
3591 get_author(const char *name)
3593         static const char **authors;
3594         static size_t authors_size;
3595         int from = 0, to = authors_size - 1;
3597         while (from <= to) {
3598                 size_t pos = (to + from) / 2;
3599                 int cmp = strcmp(name, authors[pos]);
3601                 if (!cmp)
3602                         return authors[pos];
3604                 if (cmp < 0)
3605                         to = pos - 1;
3606                 else
3607                         from = pos + 1;
3608         }
3610         if (!realloc_authors(&authors, authors_size, 1))
3611                 return NULL;
3612         name = strdup(name);
3613         if (!name)
3614                 return NULL;
3616         memmove(authors + from + 1, authors + from, (authors_size - from) * sizeof(*authors));
3617         authors[from] = name;
3618         authors_size++;
3620         return name;
3623 static void
3624 parse_timezone(time_t *time, const char *zone)
3626         long tz;
3628         tz  = ('0' - zone[1]) * 60 * 60 * 10;
3629         tz += ('0' - zone[2]) * 60 * 60;
3630         tz += ('0' - zone[3]) * 60;
3631         tz += ('0' - zone[4]);
3633         if (zone[0] == '-')
3634                 tz = -tz;
3636         *time -= tz;
3639 /* Parse author lines where the name may be empty:
3640  *      author  <email@address.tld> 1138474660 +0100
3641  */
3642 static void
3643 parse_author_line(char *ident, const char **author, time_t *time)
3645         char *nameend = strchr(ident, '<');
3646         char *emailend = strchr(ident, '>');
3648         if (nameend && emailend)
3649                 *nameend = *emailend = 0;
3650         ident = chomp_string(ident);
3651         if (!*ident) {
3652                 if (nameend)
3653                         ident = chomp_string(nameend + 1);
3654                 if (!*ident)
3655                         ident = "Unknown";
3656         }
3658         *author = get_author(ident);
3660         /* Parse epoch and timezone */
3661         if (emailend && emailend[1] == ' ') {
3662                 char *secs = emailend + 2;
3663                 char *zone = strchr(secs, ' ');
3665                 *time = (time_t) atol(secs);
3667                 if (zone && strlen(zone) == STRING_SIZE(" +0700"))
3668                         parse_timezone(time, zone + 1);
3669         }
3672 static bool
3673 open_commit_parent_menu(char buf[SIZEOF_STR], int *parents)
3675         char rev[SIZEOF_REV];
3676         const char *revlist_argv[] = {
3677                 "git", "log", "--no-color", "-1", "--pretty=format:%s", rev, NULL
3678         };
3679         struct menu_item *items;
3680         char text[SIZEOF_STR];
3681         bool ok = TRUE;
3682         int i;
3684         items = calloc(*parents + 1, sizeof(*items));
3685         if (!items)
3686                 return FALSE;
3688         for (i = 0; i < *parents; i++) {
3689                 string_copy_rev(rev, &buf[SIZEOF_REV * i]);
3690                 if (!run_io_buf(revlist_argv, text, sizeof(text)) ||
3691                     !(items[i].text = strdup(text))) {
3692                         ok = FALSE;
3693                         break;
3694                 }
3695         }
3697         if (ok) {
3698                 *parents = 0;
3699                 ok = prompt_menu("Select parent", items, parents);
3700         }
3701         for (i = 0; items[i].text; i++)
3702                 free((char *) items[i].text);
3703         free(items);
3704         return ok;
3707 static bool
3708 select_commit_parent(const char *id, char rev[SIZEOF_REV], const char *path)
3710         char buf[SIZEOF_STR * 4];
3711         const char *revlist_argv[] = {
3712                 "git", "log", "--no-color", "-1",
3713                         "--pretty=format:%P", id, "--", path, NULL
3714         };
3715         int parents;
3717         if (!run_io_buf(revlist_argv, buf, sizeof(buf)) ||
3718             (parents = strlen(buf) / 40) < 0) {
3719                 report("Failed to get parent information");
3720                 return FALSE;
3722         } else if (parents == 0) {
3723                 if (path)
3724                         report("Path '%s' does not exist in the parent", path);
3725                 else
3726                         report("The selected commit has no parents");
3727                 return FALSE;
3728         }
3730         if (parents > 1 && !open_commit_parent_menu(buf, &parents))
3731                 return FALSE;
3733         string_copy_rev(rev, &buf[41 * parents]);
3734         return TRUE;
3737 /*
3738  * Pager backend
3739  */
3741 static bool
3742 pager_draw(struct view *view, struct line *line, unsigned int lineno)
3744         char text[SIZEOF_STR];
3746         if (opt_line_number && draw_lineno(view, lineno))
3747                 return TRUE;
3749         string_expand(text, sizeof(text), line->data, opt_tab_size);
3750         draw_text(view, line->type, text, TRUE);
3751         return TRUE;
3754 static bool
3755 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
3757         const char *describe_argv[] = { "git", "describe", commit_id, NULL };
3758         char ref[SIZEOF_STR];
3760         if (!run_io_buf(describe_argv, ref, sizeof(ref)) || !*ref)
3761                 return TRUE;
3763         /* This is the only fatal call, since it can "corrupt" the buffer. */
3764         if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
3765                 return FALSE;
3767         return TRUE;
3770 static void
3771 add_pager_refs(struct view *view, struct line *line)
3773         char buf[SIZEOF_STR];
3774         char *commit_id = (char *)line->data + STRING_SIZE("commit ");
3775         struct ref_list *list;
3776         size_t bufpos = 0, i;
3777         const char *sep = "Refs: ";
3778         bool is_tag = FALSE;
3780         assert(line->type == LINE_COMMIT);
3782         list = get_ref_list(commit_id);
3783         if (!list) {
3784                 if (view == VIEW(REQ_VIEW_DIFF))
3785                         goto try_add_describe_ref;
3786                 return;
3787         }
3789         for (i = 0; i < list->size; i++) {
3790                 struct ref *ref = list->refs[i];
3791                 const char *fmt = ref->tag    ? "%s[%s]" :
3792                                   ref->remote ? "%s<%s>" : "%s%s";
3794                 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
3795                         return;
3796                 sep = ", ";
3797                 if (ref->tag)
3798                         is_tag = TRUE;
3799         }
3801         if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
3802 try_add_describe_ref:
3803                 /* Add <tag>-g<commit_id> "fake" reference. */
3804                 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
3805                         return;
3806         }
3808         if (bufpos == 0)
3809                 return;
3811         add_line_text(view, buf, LINE_PP_REFS);
3814 static bool
3815 pager_read(struct view *view, char *data)
3817         struct line *line;
3819         if (!data)
3820                 return TRUE;
3822         line = add_line_text(view, data, get_line_type(data));
3823         if (!line)
3824                 return FALSE;
3826         if (line->type == LINE_COMMIT &&
3827             (view == VIEW(REQ_VIEW_DIFF) ||
3828              view == VIEW(REQ_VIEW_LOG)))
3829                 add_pager_refs(view, line);
3831         return TRUE;
3834 static enum request
3835 pager_request(struct view *view, enum request request, struct line *line)
3837         int split = 0;
3839         if (request != REQ_ENTER)
3840                 return request;
3842         if (line->type == LINE_COMMIT &&
3843            (view == VIEW(REQ_VIEW_LOG) ||
3844             view == VIEW(REQ_VIEW_PAGER))) {
3845                 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
3846                 split = 1;
3847         }
3849         /* Always scroll the view even if it was split. That way
3850          * you can use Enter to scroll through the log view and
3851          * split open each commit diff. */
3852         scroll_view(view, REQ_SCROLL_LINE_DOWN);
3854         /* FIXME: A minor workaround. Scrolling the view will call report("")
3855          * but if we are scrolling a non-current view this won't properly
3856          * update the view title. */
3857         if (split)
3858                 update_view_title(view);
3860         return REQ_NONE;
3863 static bool
3864 pager_grep(struct view *view, struct line *line)
3866         const char *text[] = { line->data, NULL };
3868         return grep_text(view, text);
3871 static void
3872 pager_select(struct view *view, struct line *line)
3874         if (line->type == LINE_COMMIT) {
3875                 char *text = (char *)line->data + STRING_SIZE("commit ");
3877                 if (view != VIEW(REQ_VIEW_PAGER))
3878                         string_copy_rev(view->ref, text);
3879                 string_copy_rev(ref_commit, text);
3880         }
3883 static struct view_ops pager_ops = {
3884         "line",
3885         NULL,
3886         NULL,
3887         pager_read,
3888         pager_draw,
3889         pager_request,
3890         pager_grep,
3891         pager_select,
3892 };
3894 static const char *log_argv[SIZEOF_ARG] = {
3895         "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
3896 };
3898 static enum request
3899 log_request(struct view *view, enum request request, struct line *line)
3901         switch (request) {
3902         case REQ_REFRESH:
3903                 load_refs();
3904                 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
3905                 return REQ_NONE;
3906         default:
3907                 return pager_request(view, request, line);
3908         }
3911 static struct view_ops log_ops = {
3912         "line",
3913         log_argv,
3914         NULL,
3915         pager_read,
3916         pager_draw,
3917         log_request,
3918         pager_grep,
3919         pager_select,
3920 };
3922 static const char *diff_argv[SIZEOF_ARG] = {
3923         "git", "show", "--pretty=fuller", "--no-color", "--root",
3924                 "--patch-with-stat", "--find-copies-harder", "-C", "%(commit)", NULL
3925 };
3927 static struct view_ops diff_ops = {
3928         "line",
3929         diff_argv,
3930         NULL,
3931         pager_read,
3932         pager_draw,
3933         pager_request,
3934         pager_grep,
3935         pager_select,
3936 };
3938 /*
3939  * Help backend
3940  */
3942 static bool help_keymap_hidden[ARRAY_SIZE(keymap_table)];
3944 static char *
3945 help_name(char buf[SIZEOF_STR], const char *name, size_t namelen)
3947         int bufpos;
3949         for (bufpos = 0; bufpos <= namelen; bufpos++) {
3950                 buf[bufpos] = tolower(name[bufpos]);
3951                 if (buf[bufpos] == '_')
3952                         buf[bufpos] = '-';
3953         }
3955         buf[bufpos] = 0;
3956         return buf;
3959 #define help_keymap_name(buf, keymap) \
3960         help_name(buf, keymap_table[keymap].name, keymap_table[keymap].namelen)
3962 static bool
3963 help_open_keymap_title(struct view *view, enum keymap keymap)
3965         char buf[SIZEOF_STR];
3966         struct line *line;
3968         line = add_line_format(view, LINE_HELP_KEYMAP, "[%c] %s bindings",
3969                                help_keymap_hidden[keymap] ? '+' : '-',
3970                                help_keymap_name(buf, keymap));
3971         if (line)
3972                 line->other = keymap;
3974         return help_keymap_hidden[keymap];
3977 static void
3978 help_open_keymap(struct view *view, enum keymap keymap)
3980         const char *group = NULL;
3981         char buf[SIZEOF_STR];
3982         size_t bufpos;
3983         bool add_title = TRUE;
3984         int i;
3986         for (i = 0; i < ARRAY_SIZE(req_info); i++) {
3987                 const char *key = NULL;
3989                 if (req_info[i].request == REQ_NONE)
3990                         continue;
3992                 if (!req_info[i].request) {
3993                         group = req_info[i].help;
3994                         continue;
3995                 }
3997                 key = get_keys(keymap, req_info[i].request, TRUE);
3998                 if (!key || !*key)
3999                         continue;
4001                 if (add_title && help_open_keymap_title(view, keymap))
4002                         return;
4003                 add_title = false;
4005                 if (group) {
4006                         add_line_text(view, group, LINE_HELP_GROUP);
4007                         group = NULL;
4008                 }
4010                 add_line_format(view, LINE_DEFAULT, "    %-25s %-20s %s", key,
4011                                 help_name(buf, req_info[i].name, req_info[i].namelen),
4012                                 req_info[i].help);
4013         }
4015         group = "External commands:";
4017         for (i = 0; i < run_requests; i++) {
4018                 struct run_request *req = get_run_request(REQ_NONE + i + 1);
4019                 const char *key;
4020                 int argc;
4022                 if (!req || req->keymap != keymap)
4023                         continue;
4025                 key = get_key_name(req->key);
4026                 if (!*key)
4027                         key = "(no key defined)";
4029                 if (add_title && help_open_keymap_title(view, keymap))
4030                         return;
4031                 if (group) {
4032                         add_line_text(view, group, LINE_HELP_GROUP);
4033                         group = NULL;
4034                 }
4036                 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
4037                         if (!string_format_from(buf, &bufpos, "%s%s",
4038                                                 argc ? " " : "", req->argv[argc]))
4039                                 return;
4041                 add_line_format(view, LINE_DEFAULT, "    %-25s `%s`", key, buf);
4042         }
4045 static bool
4046 help_open(struct view *view)
4048         enum keymap keymap;
4050         reset_view(view);
4051         add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
4052         add_line_text(view, "", LINE_DEFAULT);
4054         for (keymap = 0; keymap < ARRAY_SIZE(keymap_table); keymap++)
4055                 help_open_keymap(view, keymap);
4057         return TRUE;
4060 static enum request
4061 help_request(struct view *view, enum request request, struct line *line)
4063         switch (request) {
4064         case REQ_ENTER:
4065                 if (line->type == LINE_HELP_KEYMAP) {
4066                         help_keymap_hidden[line->other] =
4067                                 !help_keymap_hidden[line->other];
4068                         view->p_restore = TRUE;
4069                         open_view(view, REQ_VIEW_HELP, OPEN_REFRESH);
4070                 }
4072                 return REQ_NONE;
4073         default:
4074                 return pager_request(view, request, line);
4075         }
4078 static struct view_ops help_ops = {
4079         "line",
4080         NULL,
4081         help_open,
4082         NULL,
4083         pager_draw,
4084         help_request,
4085         pager_grep,
4086         pager_select,
4087 };
4090 /*
4091  * Tree backend
4092  */
4094 struct tree_stack_entry {
4095         struct tree_stack_entry *prev;  /* Entry below this in the stack */
4096         unsigned long lineno;           /* Line number to restore */
4097         char *name;                     /* Position of name in opt_path */
4098 };
4100 /* The top of the path stack. */
4101 static struct tree_stack_entry *tree_stack = NULL;
4102 unsigned long tree_lineno = 0;
4104 static void
4105 pop_tree_stack_entry(void)
4107         struct tree_stack_entry *entry = tree_stack;
4109         tree_lineno = entry->lineno;
4110         entry->name[0] = 0;
4111         tree_stack = entry->prev;
4112         free(entry);
4115 static void
4116 push_tree_stack_entry(const char *name, unsigned long lineno)
4118         struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
4119         size_t pathlen = strlen(opt_path);
4121         if (!entry)
4122                 return;
4124         entry->prev = tree_stack;
4125         entry->name = opt_path + pathlen;
4126         tree_stack = entry;
4128         if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
4129                 pop_tree_stack_entry();
4130                 return;
4131         }
4133         /* Move the current line to the first tree entry. */
4134         tree_lineno = 1;
4135         entry->lineno = lineno;
4138 /* Parse output from git-ls-tree(1):
4139  *
4140  * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
4141  */
4143 #define SIZEOF_TREE_ATTR \
4144         STRING_SIZE("100644 blob f931e1d229c3e185caad4449bf5b66ed72462657\t")
4146 #define SIZEOF_TREE_MODE \
4147         STRING_SIZE("100644 ")
4149 #define TREE_ID_OFFSET \
4150         STRING_SIZE("100644 blob ")
4152 struct tree_entry {
4153         char id[SIZEOF_REV];
4154         mode_t mode;
4155         time_t time;                    /* Date from the author ident. */
4156         const char *author;             /* Author of the commit. */
4157         char name[1];
4158 };
4160 static const char *
4161 tree_path(const struct line *line)
4163         return ((struct tree_entry *) line->data)->name;
4166 static int
4167 tree_compare_entry(const struct line *line1, const struct line *line2)
4169         if (line1->type != line2->type)
4170                 return line1->type == LINE_TREE_DIR ? -1 : 1;
4171         return strcmp(tree_path(line1), tree_path(line2));
4174 static const enum sort_field tree_sort_fields[] = {
4175         ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
4176 };
4177 static struct sort_state tree_sort_state = SORT_STATE(tree_sort_fields);
4179 static int
4180 tree_compare(const void *l1, const void *l2)
4182         const struct line *line1 = (const struct line *) l1;
4183         const struct line *line2 = (const struct line *) l2;
4184         const struct tree_entry *entry1 = ((const struct line *) l1)->data;
4185         const struct tree_entry *entry2 = ((const struct line *) l2)->data;
4187         if (line1->type == LINE_TREE_HEAD)
4188                 return -1;
4189         if (line2->type == LINE_TREE_HEAD)
4190                 return 1;
4192         switch (get_sort_field(tree_sort_state)) {
4193         case ORDERBY_DATE:
4194                 return sort_order(tree_sort_state, entry1->time - entry2->time);
4196         case ORDERBY_AUTHOR:
4197                 return sort_order(tree_sort_state, strcmp(entry1->author, entry2->author));
4199         case ORDERBY_NAME:
4200         default:
4201                 return sort_order(tree_sort_state, tree_compare_entry(line1, line2));
4202         }
4206 static struct line *
4207 tree_entry(struct view *view, enum line_type type, const char *path,
4208            const char *mode, const char *id)
4210         struct tree_entry *entry = calloc(1, sizeof(*entry) + strlen(path));
4211         struct line *line = entry ? add_line_data(view, entry, type) : NULL;
4213         if (!entry || !line) {
4214                 free(entry);
4215                 return NULL;
4216         }
4218         strncpy(entry->name, path, strlen(path));
4219         if (mode)
4220                 entry->mode = strtoul(mode, NULL, 8);
4221         if (id)
4222                 string_copy_rev(entry->id, id);
4224         return line;
4227 static bool
4228 tree_read_date(struct view *view, char *text, bool *read_date)
4230         static const char *author_name;
4231         static time_t author_time;
4233         if (!text && *read_date) {
4234                 *read_date = FALSE;
4235                 return TRUE;
4237         } else if (!text) {
4238                 char *path = *opt_path ? opt_path : ".";
4239                 /* Find next entry to process */
4240                 const char *log_file[] = {
4241                         "git", "log", "--no-color", "--pretty=raw",
4242                                 "--cc", "--raw", view->id, "--", path, NULL
4243                 };
4244                 struct io io = {};
4246                 if (!view->lines) {
4247                         tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL);
4248                         report("Tree is empty");
4249                         return TRUE;
4250                 }
4252                 if (!run_io_rd(&io, log_file, FORMAT_NONE)) {
4253                         report("Failed to load tree data");
4254                         return TRUE;
4255                 }
4257                 done_io(view->pipe);
4258                 view->io = io;
4259                 *read_date = TRUE;
4260                 return FALSE;
4262         } else if (*text == 'a' && get_line_type(text) == LINE_AUTHOR) {
4263                 parse_author_line(text + STRING_SIZE("author "),
4264                                   &author_name, &author_time);
4266         } else if (*text == ':') {
4267                 char *pos;
4268                 size_t annotated = 1;
4269                 size_t i;
4271                 pos = strchr(text, '\t');
4272                 if (!pos)
4273                         return TRUE;
4274                 text = pos + 1;
4275                 if (*opt_prefix && !strncmp(text, opt_prefix, strlen(opt_prefix)))
4276                         text += strlen(opt_prefix);
4277                 if (*opt_path && !strncmp(text, opt_path, strlen(opt_path)))
4278                         text += strlen(opt_path);
4279                 pos = strchr(text, '/');
4280                 if (pos)
4281                         *pos = 0;
4283                 for (i = 1; i < view->lines; i++) {
4284                         struct line *line = &view->line[i];
4285                         struct tree_entry *entry = line->data;
4287                         annotated += !!entry->author;
4288                         if (entry->author || strcmp(entry->name, text))
4289                                 continue;
4291                         entry->author = author_name;
4292                         entry->time = author_time;
4293                         line->dirty = 1;
4294                         break;
4295                 }
4297                 if (annotated == view->lines)
4298                         kill_io(view->pipe);
4299         }
4300         return TRUE;
4303 static bool
4304 tree_read(struct view *view, char *text)
4306         static bool read_date = FALSE;
4307         struct tree_entry *data;
4308         struct line *entry, *line;
4309         enum line_type type;
4310         size_t textlen = text ? strlen(text) : 0;
4311         char *path = text + SIZEOF_TREE_ATTR;
4313         if (read_date || !text)
4314                 return tree_read_date(view, text, &read_date);
4316         if (textlen <= SIZEOF_TREE_ATTR)
4317                 return FALSE;
4318         if (view->lines == 0 &&
4319             !tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL))
4320                 return FALSE;
4322         /* Strip the path part ... */
4323         if (*opt_path) {
4324                 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
4325                 size_t striplen = strlen(opt_path);
4327                 if (pathlen > striplen)
4328                         memmove(path, path + striplen,
4329                                 pathlen - striplen + 1);
4331                 /* Insert "link" to parent directory. */
4332                 if (view->lines == 1 &&
4333                     !tree_entry(view, LINE_TREE_DIR, "..", "040000", view->ref))
4334                         return FALSE;
4335         }
4337         type = text[SIZEOF_TREE_MODE] == 't' ? LINE_TREE_DIR : LINE_TREE_FILE;
4338         entry = tree_entry(view, type, path, text, text + TREE_ID_OFFSET);
4339         if (!entry)
4340                 return FALSE;
4341         data = entry->data;
4343         /* Skip "Directory ..." and ".." line. */
4344         for (line = &view->line[1 + !!*opt_path]; line < entry; line++) {
4345                 if (tree_compare_entry(line, entry) <= 0)
4346                         continue;
4348                 memmove(line + 1, line, (entry - line) * sizeof(*entry));
4350                 line->data = data;
4351                 line->type = type;
4352                 for (; line <= entry; line++)
4353                         line->dirty = line->cleareol = 1;
4354                 return TRUE;
4355         }
4357         if (tree_lineno > view->lineno) {
4358                 view->lineno = tree_lineno;
4359                 tree_lineno = 0;
4360         }
4362         return TRUE;
4365 static bool
4366 tree_draw(struct view *view, struct line *line, unsigned int lineno)
4368         struct tree_entry *entry = line->data;
4370         if (line->type == LINE_TREE_HEAD) {
4371                 if (draw_text(view, line->type, "Directory path /", TRUE))
4372                         return TRUE;
4373         } else {
4374                 if (draw_mode(view, entry->mode))
4375                         return TRUE;
4377                 if (opt_author && draw_author(view, entry->author))
4378                         return TRUE;
4380                 if (opt_date && draw_date(view, entry->author ? &entry->time : NULL))
4381                         return TRUE;
4382         }
4383         if (draw_text(view, line->type, entry->name, TRUE))
4384                 return TRUE;
4385         return TRUE;
4388 static void
4389 open_blob_editor()
4391         char file[SIZEOF_STR] = "/tmp/tigblob.XXXXXX";
4392         int fd = mkstemp(file);
4394         if (fd == -1)
4395                 report("Failed to create temporary file");
4396         else if (!run_io_append(blob_ops.argv, FORMAT_ALL, fd))
4397                 report("Failed to save blob data to file");
4398         else
4399                 open_editor(FALSE, file);
4400         if (fd != -1)
4401                 unlink(file);
4404 static enum request
4405 tree_request(struct view *view, enum request request, struct line *line)
4407         enum open_flags flags;
4409         switch (request) {
4410         case REQ_VIEW_BLAME:
4411                 if (line->type != LINE_TREE_FILE) {
4412                         report("Blame only supported for files");
4413                         return REQ_NONE;
4414                 }
4416                 string_copy(opt_ref, view->vid);
4417                 return request;
4419         case REQ_EDIT:
4420                 if (line->type != LINE_TREE_FILE) {
4421                         report("Edit only supported for files");
4422                 } else if (!is_head_commit(view->vid)) {
4423                         open_blob_editor();
4424                 } else {
4425                         open_editor(TRUE, opt_file);
4426                 }
4427                 return REQ_NONE;
4429         case REQ_TOGGLE_SORT_FIELD:
4430         case REQ_TOGGLE_SORT_ORDER:
4431                 sort_view(view, request, &tree_sort_state, tree_compare);
4432                 return REQ_NONE;
4434         case REQ_PARENT:
4435                 if (!*opt_path) {
4436                         /* quit view if at top of tree */
4437                         return REQ_VIEW_CLOSE;
4438                 }
4439                 /* fake 'cd  ..' */
4440                 line = &view->line[1];
4441                 break;
4443         case REQ_ENTER:
4444                 break;
4446         default:
4447                 return request;
4448         }
4450         /* Cleanup the stack if the tree view is at a different tree. */
4451         while (!*opt_path && tree_stack)
4452                 pop_tree_stack_entry();
4454         switch (line->type) {
4455         case LINE_TREE_DIR:
4456                 /* Depending on whether it is a subdirectory or parent link
4457                  * mangle the path buffer. */
4458                 if (line == &view->line[1] && *opt_path) {
4459                         pop_tree_stack_entry();
4461                 } else {
4462                         const char *basename = tree_path(line);
4464                         push_tree_stack_entry(basename, view->lineno);
4465                 }
4467                 /* Trees and subtrees share the same ID, so they are not not
4468                  * unique like blobs. */
4469                 flags = OPEN_RELOAD;
4470                 request = REQ_VIEW_TREE;
4471                 break;
4473         case LINE_TREE_FILE:
4474                 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4475                 request = REQ_VIEW_BLOB;
4476                 break;
4478         default:
4479                 return REQ_NONE;
4480         }
4482         open_view(view, request, flags);
4483         if (request == REQ_VIEW_TREE)
4484                 view->lineno = tree_lineno;
4486         return REQ_NONE;
4489 static bool
4490 tree_grep(struct view *view, struct line *line)
4492         struct tree_entry *entry = line->data;
4493         const char *text[] = {
4494                 entry->name,
4495                 opt_author ? entry->author : "",
4496                 opt_date ? mkdate(&entry->time) : "",
4497                 NULL
4498         };
4500         return grep_text(view, text);
4503 static void
4504 tree_select(struct view *view, struct line *line)
4506         struct tree_entry *entry = line->data;
4508         if (line->type == LINE_TREE_FILE) {
4509                 string_copy_rev(ref_blob, entry->id);
4510                 string_format(opt_file, "%s%s", opt_path, tree_path(line));
4512         } else if (line->type != LINE_TREE_DIR) {
4513                 return;
4514         }
4516         string_copy_rev(view->ref, entry->id);
4519 static const char *tree_argv[SIZEOF_ARG] = {
4520         "git", "ls-tree", "%(commit)", "%(directory)", NULL
4521 };
4523 static struct view_ops tree_ops = {
4524         "file",
4525         tree_argv,
4526         NULL,
4527         tree_read,
4528         tree_draw,
4529         tree_request,
4530         tree_grep,
4531         tree_select,
4532 };
4534 static bool
4535 blob_read(struct view *view, char *line)
4537         if (!line)
4538                 return TRUE;
4539         return add_line_text(view, line, LINE_DEFAULT) != NULL;
4542 static enum request
4543 blob_request(struct view *view, enum request request, struct line *line)
4545         switch (request) {
4546         case REQ_EDIT:
4547                 open_blob_editor();
4548                 return REQ_NONE;
4549         default:
4550                 return pager_request(view, request, line);
4551         }
4554 static const char *blob_argv[SIZEOF_ARG] = {
4555         "git", "cat-file", "blob", "%(blob)", NULL
4556 };
4558 static struct view_ops blob_ops = {
4559         "line",
4560         blob_argv,
4561         NULL,
4562         blob_read,
4563         pager_draw,
4564         blob_request,
4565         pager_grep,
4566         pager_select,
4567 };
4569 /*
4570  * Blame backend
4571  *
4572  * Loading the blame view is a two phase job:
4573  *
4574  *  1. File content is read either using opt_file from the
4575  *     filesystem or using git-cat-file.
4576  *  2. Then blame information is incrementally added by
4577  *     reading output from git-blame.
4578  */
4580 static const char *blame_head_argv[] = {
4581         "git", "blame", "--incremental", "--", "%(file)", NULL
4582 };
4584 static const char *blame_ref_argv[] = {
4585         "git", "blame", "--incremental", "%(ref)", "--", "%(file)", NULL
4586 };
4588 static const char *blame_cat_file_argv[] = {
4589         "git", "cat-file", "blob", "%(ref):%(file)", NULL
4590 };
4592 struct blame_commit {
4593         char id[SIZEOF_REV];            /* SHA1 ID. */
4594         char title[128];                /* First line of the commit message. */
4595         const char *author;             /* Author of the commit. */
4596         time_t time;                    /* Date from the author ident. */
4597         char filename[128];             /* Name of file. */
4598         bool has_previous;              /* Was a "previous" line detected. */
4599 };
4601 struct blame {
4602         struct blame_commit *commit;
4603         unsigned long lineno;
4604         char text[1];
4605 };
4607 static bool
4608 blame_open(struct view *view)
4610         if (*opt_ref || !io_open(&view->io, opt_file)) {
4611                 if (!run_io_rd(&view->io, blame_cat_file_argv, FORMAT_ALL))
4612                         return FALSE;
4613         }
4615         setup_update(view, opt_file);
4616         string_format(view->ref, "%s ...", opt_file);
4618         return TRUE;
4621 static struct blame_commit *
4622 get_blame_commit(struct view *view, const char *id)
4624         size_t i;
4626         for (i = 0; i < view->lines; i++) {
4627                 struct blame *blame = view->line[i].data;
4629                 if (!blame->commit)
4630                         continue;
4632                 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
4633                         return blame->commit;
4634         }
4636         {
4637                 struct blame_commit *commit = calloc(1, sizeof(*commit));
4639                 if (commit)
4640                         string_ncopy(commit->id, id, SIZEOF_REV);
4641                 return commit;
4642         }
4645 static bool
4646 parse_number(const char **posref, size_t *number, size_t min, size_t max)
4648         const char *pos = *posref;
4650         *posref = NULL;
4651         pos = strchr(pos + 1, ' ');
4652         if (!pos || !isdigit(pos[1]))
4653                 return FALSE;
4654         *number = atoi(pos + 1);
4655         if (*number < min || *number > max)
4656                 return FALSE;
4658         *posref = pos;
4659         return TRUE;
4662 static struct blame_commit *
4663 parse_blame_commit(struct view *view, const char *text, int *blamed)
4665         struct blame_commit *commit;
4666         struct blame *blame;
4667         const char *pos = text + SIZEOF_REV - 2;
4668         size_t orig_lineno = 0;
4669         size_t lineno;
4670         size_t group;
4672         if (strlen(text) <= SIZEOF_REV || pos[1] != ' ')
4673                 return NULL;
4675         if (!parse_number(&pos, &orig_lineno, 1, 9999999) ||
4676             !parse_number(&pos, &lineno, 1, view->lines) ||
4677             !parse_number(&pos, &group, 1, view->lines - lineno + 1))
4678                 return NULL;
4680         commit = get_blame_commit(view, text);
4681         if (!commit)
4682                 return NULL;
4684         *blamed += group;
4685         while (group--) {
4686                 struct line *line = &view->line[lineno + group - 1];
4688                 blame = line->data;
4689                 blame->commit = commit;
4690                 blame->lineno = orig_lineno + group - 1;
4691                 line->dirty = 1;
4692         }
4694         return commit;
4697 static bool
4698 blame_read_file(struct view *view, const char *line, bool *read_file)
4700         if (!line) {
4701                 const char **argv = *opt_ref ? blame_ref_argv : blame_head_argv;
4702                 struct io io = {};
4704                 if (view->lines == 0 && !view->parent)
4705                         die("No blame exist for %s", view->vid);
4707                 if (view->lines == 0 || !run_io_rd(&io, argv, FORMAT_ALL)) {
4708                         report("Failed to load blame data");
4709                         return TRUE;
4710                 }
4712                 done_io(view->pipe);
4713                 view->io = io;
4714                 *read_file = FALSE;
4715                 return FALSE;
4717         } else {
4718                 size_t linelen = strlen(line);
4719                 struct blame *blame = malloc(sizeof(*blame) + linelen);
4721                 if (!blame)
4722                         return FALSE;
4724                 blame->commit = NULL;
4725                 strncpy(blame->text, line, linelen);
4726                 blame->text[linelen] = 0;
4727                 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
4728         }
4731 static bool
4732 match_blame_header(const char *name, char **line)
4734         size_t namelen = strlen(name);
4735         bool matched = !strncmp(name, *line, namelen);
4737         if (matched)
4738                 *line += namelen;
4740         return matched;
4743 static bool
4744 blame_read(struct view *view, char *line)
4746         static struct blame_commit *commit = NULL;
4747         static int blamed = 0;
4748         static bool read_file = TRUE;
4750         if (read_file)
4751                 return blame_read_file(view, line, &read_file);
4753         if (!line) {
4754                 /* Reset all! */
4755                 commit = NULL;
4756                 blamed = 0;
4757                 read_file = TRUE;
4758                 string_format(view->ref, "%s", view->vid);
4759                 if (view_is_displayed(view)) {
4760                         update_view_title(view);
4761                         redraw_view_from(view, 0);
4762                 }
4763                 return TRUE;
4764         }
4766         if (!commit) {
4767                 commit = parse_blame_commit(view, line, &blamed);
4768                 string_format(view->ref, "%s %2d%%", view->vid,
4769                               view->lines ? blamed * 100 / view->lines : 0);
4771         } else if (match_blame_header("author ", &line)) {
4772                 commit->author = get_author(line);
4774         } else if (match_blame_header("author-time ", &line)) {
4775                 commit->time = (time_t) atol(line);
4777         } else if (match_blame_header("author-tz ", &line)) {
4778                 parse_timezone(&commit->time, line);
4780         } else if (match_blame_header("summary ", &line)) {
4781                 string_ncopy(commit->title, line, strlen(line));
4783         } else if (match_blame_header("previous ", &line)) {
4784                 commit->has_previous = TRUE;
4786         } else if (match_blame_header("filename ", &line)) {
4787                 string_ncopy(commit->filename, line, strlen(line));
4788                 commit = NULL;
4789         }
4791         return TRUE;
4794 static bool
4795 blame_draw(struct view *view, struct line *line, unsigned int lineno)
4797         struct blame *blame = line->data;
4798         time_t *time = NULL;
4799         const char *id = NULL, *author = NULL;
4800         char text[SIZEOF_STR];
4802         if (blame->commit && *blame->commit->filename) {
4803                 id = blame->commit->id;
4804                 author = blame->commit->author;
4805                 time = &blame->commit->time;
4806         }
4808         if (opt_date && draw_date(view, time))
4809                 return TRUE;
4811         if (opt_author && draw_author(view, author))
4812                 return TRUE;
4814         if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
4815                 return TRUE;
4817         if (draw_lineno(view, lineno))
4818                 return TRUE;
4820         string_expand(text, sizeof(text), blame->text, opt_tab_size);
4821         draw_text(view, LINE_DEFAULT, text, TRUE);
4822         return TRUE;
4825 static bool
4826 check_blame_commit(struct blame *blame, bool check_null_id)
4828         if (!blame->commit)
4829                 report("Commit data not loaded yet");
4830         else if (check_null_id && !strcmp(blame->commit->id, NULL_ID))
4831                 report("No commit exist for the selected line");
4832         else
4833                 return TRUE;
4834         return FALSE;
4837 static void
4838 setup_blame_parent_line(struct view *view, struct blame *blame)
4840         const char *diff_tree_argv[] = {
4841                 "git", "diff-tree", "-U0", blame->commit->id,
4842                         "--", blame->commit->filename, NULL
4843         };
4844         struct io io = {};
4845         int parent_lineno = -1;
4846         int blamed_lineno = -1;
4847         char *line;
4849         if (!run_io(&io, diff_tree_argv, NULL, IO_RD))
4850                 return;
4852         while ((line = io_get(&io, '\n', TRUE))) {
4853                 if (*line == '@') {
4854                         char *pos = strchr(line, '+');
4856                         parent_lineno = atoi(line + 4);
4857                         if (pos)
4858                                 blamed_lineno = atoi(pos + 1);
4860                 } else if (*line == '+' && parent_lineno != -1) {
4861                         if (blame->lineno == blamed_lineno - 1 &&
4862                             !strcmp(blame->text, line + 1)) {
4863                                 view->lineno = parent_lineno ? parent_lineno - 1 : 0;
4864                                 break;
4865                         }
4866                         blamed_lineno++;
4867                 }
4868         }
4870         done_io(&io);
4873 static enum request
4874 blame_request(struct view *view, enum request request, struct line *line)
4876         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4877         struct blame *blame = line->data;
4879         switch (request) {
4880         case REQ_VIEW_BLAME:
4881                 if (check_blame_commit(blame, TRUE)) {
4882                         string_copy(opt_ref, blame->commit->id);
4883                         string_copy(opt_file, blame->commit->filename);
4884                         if (blame->lineno)
4885                                 view->lineno = blame->lineno;
4886                         open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4887                 }
4888                 break;
4890         case REQ_PARENT:
4891                 if (check_blame_commit(blame, TRUE) &&
4892                     select_commit_parent(blame->commit->id, opt_ref,
4893                                          blame->commit->filename)) {
4894                         string_copy(opt_file, blame->commit->filename);
4895                         setup_blame_parent_line(view, blame);
4896                         open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4897                 }
4898                 break;
4900         case REQ_ENTER:
4901                 if (!check_blame_commit(blame, FALSE))
4902                         break;
4904                 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
4905                     !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
4906                         break;
4908                 if (!strcmp(blame->commit->id, NULL_ID)) {
4909                         struct view *diff = VIEW(REQ_VIEW_DIFF);
4910                         const char *diff_index_argv[] = {
4911                                 "git", "diff-index", "--root", "--patch-with-stat",
4912                                         "-C", "-M", "HEAD", "--", view->vid, NULL
4913                         };
4915                         if (!blame->commit->has_previous) {
4916                                 diff_index_argv[1] = "diff";
4917                                 diff_index_argv[2] = "--no-color";
4918                                 diff_index_argv[6] = "--";
4919                                 diff_index_argv[7] = "/dev/null";
4920                         }
4922                         if (!prepare_update(diff, diff_index_argv, NULL, FORMAT_DASH)) {
4923                                 report("Failed to allocate diff command");
4924                                 break;
4925                         }
4926                         flags |= OPEN_PREPARED;
4927                 }
4929                 open_view(view, REQ_VIEW_DIFF, flags);
4930                 if (VIEW(REQ_VIEW_DIFF)->pipe && !strcmp(blame->commit->id, NULL_ID))
4931                         string_copy_rev(VIEW(REQ_VIEW_DIFF)->ref, NULL_ID);
4932                 break;
4934         default:
4935                 return request;
4936         }
4938         return REQ_NONE;
4941 static bool
4942 blame_grep(struct view *view, struct line *line)
4944         struct blame *blame = line->data;
4945         struct blame_commit *commit = blame->commit;
4946         const char *text[] = {
4947                 blame->text,
4948                 commit ? commit->title : "",
4949                 commit ? commit->id : "",
4950                 commit && opt_author ? commit->author : "",
4951                 commit && opt_date ? mkdate(&commit->time) : "",
4952                 NULL
4953         };
4955         return grep_text(view, text);
4958 static void
4959 blame_select(struct view *view, struct line *line)
4961         struct blame *blame = line->data;
4962         struct blame_commit *commit = blame->commit;
4964         if (!commit)
4965                 return;
4967         if (!strcmp(commit->id, NULL_ID))
4968                 string_ncopy(ref_commit, "HEAD", 4);
4969         else
4970                 string_copy_rev(ref_commit, commit->id);
4973 static struct view_ops blame_ops = {
4974         "line",
4975         NULL,
4976         blame_open,
4977         blame_read,
4978         blame_draw,
4979         blame_request,
4980         blame_grep,
4981         blame_select,
4982 };
4984 /*
4985  * Branch backend
4986  */
4988 struct branch {
4989         const char *author;             /* Author of the last commit. */
4990         time_t time;                    /* Date of the last activity. */
4991         struct ref *ref;                /* Name and commit ID information. */
4992 };
4994 static const enum sort_field branch_sort_fields[] = {
4995         ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
4996 };
4997 static struct sort_state branch_sort_state = SORT_STATE(branch_sort_fields);
4999 static int
5000 branch_compare(const void *l1, const void *l2)
5002         const struct branch *branch1 = ((const struct line *) l1)->data;
5003         const struct branch *branch2 = ((const struct line *) l2)->data;
5005         switch (get_sort_field(branch_sort_state)) {
5006         case ORDERBY_DATE:
5007                 return sort_order(branch_sort_state, branch1->time - branch2->time);
5009         case ORDERBY_AUTHOR:
5010                 return sort_order(branch_sort_state, strcmp(branch1->author, branch2->author));
5012         case ORDERBY_NAME:
5013         default:
5014                 return sort_order(branch_sort_state, strcmp(branch1->ref->name, branch2->ref->name));
5015         }
5018 static bool
5019 branch_draw(struct view *view, struct line *line, unsigned int lineno)
5021         struct branch *branch = line->data;
5022         enum line_type type = branch->ref->head ? LINE_MAIN_HEAD : LINE_DEFAULT;
5024         if (opt_date && draw_date(view, branch->author ? &branch->time : NULL))
5025                 return TRUE;
5027         if (opt_author && draw_author(view, branch->author))
5028                 return TRUE;
5030         draw_text(view, type, branch->ref->name, TRUE);
5031         return TRUE;
5034 static enum request
5035 branch_request(struct view *view, enum request request, struct line *line)
5037         switch (request) {
5038         case REQ_REFRESH:
5039                 load_refs();
5040                 open_view(view, REQ_VIEW_BRANCH, OPEN_REFRESH);
5041                 return REQ_NONE;
5043         case REQ_TOGGLE_SORT_FIELD:
5044         case REQ_TOGGLE_SORT_ORDER:
5045                 sort_view(view, request, &branch_sort_state, branch_compare);
5046                 return REQ_NONE;
5048         case REQ_ENTER:
5049                 open_view(view, REQ_VIEW_MAIN, OPEN_SPLIT);
5050                 return REQ_NONE;
5052         default:
5053                 return request;
5054         }
5057 static bool
5058 branch_read(struct view *view, char *line)
5060         static char id[SIZEOF_REV];
5061         struct branch *reference;
5062         size_t i;
5064         if (!line)
5065                 return TRUE;
5067         switch (get_line_type(line)) {
5068         case LINE_COMMIT:
5069                 string_copy_rev(id, line + STRING_SIZE("commit "));
5070                 return TRUE;
5072         case LINE_AUTHOR:
5073                 for (i = 0, reference = NULL; i < view->lines; i++) {
5074                         struct branch *branch = view->line[i].data;
5076                         if (strcmp(branch->ref->id, id))
5077                                 continue;
5079                         view->line[i].dirty = TRUE;
5080                         if (reference) {
5081                                 branch->author = reference->author;
5082                                 branch->time = reference->time;
5083                                 continue;
5084                         }
5086                         parse_author_line(line + STRING_SIZE("author "),
5087                                           &branch->author, &branch->time);
5088                         reference = branch;
5089                 }
5090                 return TRUE;
5092         default:
5093                 return TRUE;
5094         }
5098 static bool
5099 branch_open_visitor(void *data, struct ref *ref)
5101         struct view *view = data;
5102         struct branch *branch;
5104         if (ref->tag || ref->ltag || ref->remote)
5105                 return TRUE;
5107         branch = calloc(1, sizeof(*branch));
5108         if (!branch)
5109                 return FALSE;
5111         branch->ref = ref;
5112         return !!add_line_data(view, branch, LINE_DEFAULT);
5115 static bool
5116 branch_open(struct view *view)
5118         const char *branch_log[] = {
5119                 "git", "log", "--no-color", "--pretty=raw",
5120                         "--simplify-by-decoration", "--all", NULL
5121         };
5123         if (!run_io_rd(&view->io, branch_log, FORMAT_NONE)) {
5124                 report("Failed to load branch data");
5125                 return TRUE;
5126         }
5128         setup_update(view, view->id);
5129         foreach_ref(branch_open_visitor, view);
5130         view->p_restore = TRUE;
5132         return TRUE;
5135 static bool
5136 branch_grep(struct view *view, struct line *line)
5138         struct branch *branch = line->data;
5139         const char *text[] = {
5140                 branch->ref->name,
5141                 branch->author,
5142                 NULL
5143         };
5145         return grep_text(view, text);
5148 static void
5149 branch_select(struct view *view, struct line *line)
5151         struct branch *branch = line->data;
5153         string_copy_rev(view->ref, branch->ref->id);
5154         string_copy_rev(ref_commit, branch->ref->id);
5155         string_copy_rev(ref_head, branch->ref->id);
5158 static struct view_ops branch_ops = {
5159         "branch",
5160         NULL,
5161         branch_open,
5162         branch_read,
5163         branch_draw,
5164         branch_request,
5165         branch_grep,
5166         branch_select,
5167 };
5169 /*
5170  * Status backend
5171  */
5173 struct status {
5174         char status;
5175         struct {
5176                 mode_t mode;
5177                 char rev[SIZEOF_REV];
5178                 char name[SIZEOF_STR];
5179         } old;
5180         struct {
5181                 mode_t mode;
5182                 char rev[SIZEOF_REV];
5183                 char name[SIZEOF_STR];
5184         } new;
5185 };
5187 static char status_onbranch[SIZEOF_STR];
5188 static struct status stage_status;
5189 static enum line_type stage_line_type;
5190 static size_t stage_chunks;
5191 static int *stage_chunk;
5193 DEFINE_ALLOCATOR(realloc_ints, int, 32)
5195 /* This should work even for the "On branch" line. */
5196 static inline bool
5197 status_has_none(struct view *view, struct line *line)
5199         return line < view->line + view->lines && !line[1].data;
5202 /* Get fields from the diff line:
5203  * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
5204  */
5205 static inline bool
5206 status_get_diff(struct status *file, const char *buf, size_t bufsize)
5208         const char *old_mode = buf +  1;
5209         const char *new_mode = buf +  8;
5210         const char *old_rev  = buf + 15;
5211         const char *new_rev  = buf + 56;
5212         const char *status   = buf + 97;
5214         if (bufsize < 98 ||
5215             old_mode[-1] != ':' ||
5216             new_mode[-1] != ' ' ||
5217             old_rev[-1]  != ' ' ||
5218             new_rev[-1]  != ' ' ||
5219             status[-1]   != ' ')
5220                 return FALSE;
5222         file->status = *status;
5224         string_copy_rev(file->old.rev, old_rev);
5225         string_copy_rev(file->new.rev, new_rev);
5227         file->old.mode = strtoul(old_mode, NULL, 8);
5228         file->new.mode = strtoul(new_mode, NULL, 8);
5230         file->old.name[0] = file->new.name[0] = 0;
5232         return TRUE;
5235 static bool
5236 status_run(struct view *view, const char *argv[], char status, enum line_type type)
5238         struct status *unmerged = NULL;
5239         char *buf;
5240         struct io io = {};
5242         if (!run_io(&io, argv, NULL, IO_RD))
5243                 return FALSE;
5245         add_line_data(view, NULL, type);
5247         while ((buf = io_get(&io, 0, TRUE))) {
5248                 struct status *file = unmerged;
5250                 if (!file) {
5251                         file = calloc(1, sizeof(*file));
5252                         if (!file || !add_line_data(view, file, type))
5253                                 goto error_out;
5254                 }
5256                 /* Parse diff info part. */
5257                 if (status) {
5258                         file->status = status;
5259                         if (status == 'A')
5260                                 string_copy(file->old.rev, NULL_ID);
5262                 } else if (!file->status || file == unmerged) {
5263                         if (!status_get_diff(file, buf, strlen(buf)))
5264                                 goto error_out;
5266                         buf = io_get(&io, 0, TRUE);
5267                         if (!buf)
5268                                 break;
5270                         /* Collapse all modified entries that follow an
5271                          * associated unmerged entry. */
5272                         if (unmerged == file) {
5273                                 unmerged->status = 'U';
5274                                 unmerged = NULL;
5275                         } else if (file->status == 'U') {
5276                                 unmerged = file;
5277                         }
5278                 }
5280                 /* Grab the old name for rename/copy. */
5281                 if (!*file->old.name &&
5282                     (file->status == 'R' || file->status == 'C')) {
5283                         string_ncopy(file->old.name, buf, strlen(buf));
5285                         buf = io_get(&io, 0, TRUE);
5286                         if (!buf)
5287                                 break;
5288                 }
5290                 /* git-ls-files just delivers a NUL separated list of
5291                  * file names similar to the second half of the
5292                  * git-diff-* output. */
5293                 string_ncopy(file->new.name, buf, strlen(buf));
5294                 if (!*file->old.name)
5295                         string_copy(file->old.name, file->new.name);
5296                 file = NULL;
5297         }
5299         if (io_error(&io)) {
5300 error_out:
5301                 done_io(&io);
5302                 return FALSE;
5303         }
5305         if (!view->line[view->lines - 1].data)
5306                 add_line_data(view, NULL, LINE_STAT_NONE);
5308         done_io(&io);
5309         return TRUE;
5312 /* Don't show unmerged entries in the staged section. */
5313 static const char *status_diff_index_argv[] = {
5314         "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
5315                              "--cached", "-M", "HEAD", NULL
5316 };
5318 static const char *status_diff_files_argv[] = {
5319         "git", "diff-files", "-z", NULL
5320 };
5322 static const char *status_list_other_argv[] = {
5323         "git", "ls-files", "-z", "--others", "--exclude-standard", NULL
5324 };
5326 static const char *status_list_no_head_argv[] = {
5327         "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
5328 };
5330 static const char *update_index_argv[] = {
5331         "git", "update-index", "-q", "--unmerged", "--refresh", NULL
5332 };
5334 /* Restore the previous line number to stay in the context or select a
5335  * line with something that can be updated. */
5336 static void
5337 status_restore(struct view *view)
5339         if (view->p_lineno >= view->lines)
5340                 view->p_lineno = view->lines - 1;
5341         while (view->p_lineno < view->lines && !view->line[view->p_lineno].data)
5342                 view->p_lineno++;
5343         while (view->p_lineno > 0 && !view->line[view->p_lineno].data)
5344                 view->p_lineno--;
5346         /* If the above fails, always skip the "On branch" line. */
5347         if (view->p_lineno < view->lines)
5348                 view->lineno = view->p_lineno;
5349         else
5350                 view->lineno = 1;
5352         if (view->lineno < view->offset)
5353                 view->offset = view->lineno;
5354         else if (view->offset + view->height <= view->lineno)
5355                 view->offset = view->lineno - view->height + 1;
5357         view->p_restore = FALSE;
5360 static void
5361 status_update_onbranch(void)
5363         static const char *paths[][2] = {
5364                 { "rebase-apply/rebasing",      "Rebasing" },
5365                 { "rebase-apply/applying",      "Applying mailbox" },
5366                 { "rebase-apply/",              "Rebasing mailbox" },
5367                 { "rebase-merge/interactive",   "Interactive rebase" },
5368                 { "rebase-merge/",              "Rebase merge" },
5369                 { "MERGE_HEAD",                 "Merging" },
5370                 { "BISECT_LOG",                 "Bisecting" },
5371                 { "HEAD",                       "On branch" },
5372         };
5373         char buf[SIZEOF_STR];
5374         struct stat stat;
5375         int i;
5377         if (is_initial_commit()) {
5378                 string_copy(status_onbranch, "Initial commit");
5379                 return;
5380         }
5382         for (i = 0; i < ARRAY_SIZE(paths); i++) {
5383                 char *head = opt_head;
5385                 if (!string_format(buf, "%s/%s", opt_git_dir, paths[i][0]) ||
5386                     lstat(buf, &stat) < 0)
5387                         continue;
5389                 if (!*opt_head) {
5390                         struct io io = {};
5392                         if (string_format(buf, "%s/rebase-merge/head-name", opt_git_dir) &&
5393                             io_open(&io, buf) &&
5394                             io_read_buf(&io, buf, sizeof(buf))) {
5395                                 head = buf;
5396                                 if (!prefixcmp(head, "refs/heads/"))
5397                                         head += STRING_SIZE("refs/heads/");
5398                         }
5399                 }
5401                 if (!string_format(status_onbranch, "%s %s", paths[i][1], head))
5402                         string_copy(status_onbranch, opt_head);
5403                 return;
5404         }
5406         string_copy(status_onbranch, "Not currently on any branch");
5409 /* First parse staged info using git-diff-index(1), then parse unstaged
5410  * info using git-diff-files(1), and finally untracked files using
5411  * git-ls-files(1). */
5412 static bool
5413 status_open(struct view *view)
5415         reset_view(view);
5417         add_line_data(view, NULL, LINE_STAT_HEAD);
5418         status_update_onbranch();
5420         run_io_bg(update_index_argv);
5422         if (is_initial_commit()) {
5423                 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
5424                         return FALSE;
5425         } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
5426                 return FALSE;
5427         }
5429         if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
5430             !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
5431                 return FALSE;
5433         /* Restore the exact position or use the specialized restore
5434          * mode? */
5435         if (!view->p_restore)
5436                 status_restore(view);
5437         return TRUE;
5440 static bool
5441 status_draw(struct view *view, struct line *line, unsigned int lineno)
5443         struct status *status = line->data;
5444         enum line_type type;
5445         const char *text;
5447         if (!status) {
5448                 switch (line->type) {
5449                 case LINE_STAT_STAGED:
5450                         type = LINE_STAT_SECTION;
5451                         text = "Changes to be committed:";
5452                         break;
5454                 case LINE_STAT_UNSTAGED:
5455                         type = LINE_STAT_SECTION;
5456                         text = "Changed but not updated:";
5457                         break;
5459                 case LINE_STAT_UNTRACKED:
5460                         type = LINE_STAT_SECTION;
5461                         text = "Untracked files:";
5462                         break;
5464                 case LINE_STAT_NONE:
5465                         type = LINE_DEFAULT;
5466                         text = "  (no files)";
5467                         break;
5469                 case LINE_STAT_HEAD:
5470                         type = LINE_STAT_HEAD;
5471                         text = status_onbranch;
5472                         break;
5474                 default:
5475                         return FALSE;
5476                 }
5477         } else {
5478                 static char buf[] = { '?', ' ', ' ', ' ', 0 };
5480                 buf[0] = status->status;
5481                 if (draw_text(view, line->type, buf, TRUE))
5482                         return TRUE;
5483                 type = LINE_DEFAULT;
5484                 text = status->new.name;
5485         }
5487         draw_text(view, type, text, TRUE);
5488         return TRUE;
5491 static enum request
5492 status_load_error(struct view *view, struct view *stage, const char *path)
5494         if (displayed_views() == 2 || display[current_view] != view)
5495                 maximize_view(view);
5496         report("Failed to load '%s': %s", path, io_strerror(&stage->io));
5497         return REQ_NONE;
5500 static enum request
5501 status_enter(struct view *view, struct line *line)
5503         struct status *status = line->data;
5504         const char *oldpath = status ? status->old.name : NULL;
5505         /* Diffs for unmerged entries are empty when passing the new
5506          * path, so leave it empty. */
5507         const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
5508         const char *info;
5509         enum open_flags split;
5510         struct view *stage = VIEW(REQ_VIEW_STAGE);
5512         if (line->type == LINE_STAT_NONE ||
5513             (!status && line[1].type == LINE_STAT_NONE)) {
5514                 report("No file to diff");
5515                 return REQ_NONE;
5516         }
5518         switch (line->type) {
5519         case LINE_STAT_STAGED:
5520                 if (is_initial_commit()) {
5521                         const char *no_head_diff_argv[] = {
5522                                 "git", "diff", "--no-color", "--patch-with-stat",
5523                                         "--", "/dev/null", newpath, NULL
5524                         };
5526                         if (!prepare_update(stage, no_head_diff_argv, opt_cdup, FORMAT_DASH))
5527                                 return status_load_error(view, stage, newpath);
5528                 } else {
5529                         const char *index_show_argv[] = {
5530                                 "git", "diff-index", "--root", "--patch-with-stat",
5531                                         "-C", "-M", "--cached", "HEAD", "--",
5532                                         oldpath, newpath, NULL
5533                         };
5535                         if (!prepare_update(stage, index_show_argv, opt_cdup, FORMAT_DASH))
5536                                 return status_load_error(view, stage, newpath);
5537                 }
5539                 if (status)
5540                         info = "Staged changes to %s";
5541                 else
5542                         info = "Staged changes";
5543                 break;
5545         case LINE_STAT_UNSTAGED:
5546         {
5547                 const char *files_show_argv[] = {
5548                         "git", "diff-files", "--root", "--patch-with-stat",
5549                                 "-C", "-M", "--", oldpath, newpath, NULL
5550                 };
5552                 if (!prepare_update(stage, files_show_argv, opt_cdup, FORMAT_DASH))
5553                         return status_load_error(view, stage, newpath);
5554                 if (status)
5555                         info = "Unstaged changes to %s";
5556                 else
5557                         info = "Unstaged changes";
5558                 break;
5559         }
5560         case LINE_STAT_UNTRACKED:
5561                 if (!newpath) {
5562                         report("No file to show");
5563                         return REQ_NONE;
5564                 }
5566                 if (!suffixcmp(status->new.name, -1, "/")) {
5567                         report("Cannot display a directory");
5568                         return REQ_NONE;
5569                 }
5571                 if (!prepare_update_file(stage, newpath))
5572                         return status_load_error(view, stage, newpath);
5573                 info = "Untracked file %s";
5574                 break;
5576         case LINE_STAT_HEAD:
5577                 return REQ_NONE;
5579         default:
5580                 die("line type %d not handled in switch", line->type);
5581         }
5583         split = view_is_displayed(view) ? OPEN_SPLIT : 0;
5584         open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split);
5585         if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
5586                 if (status) {
5587                         stage_status = *status;
5588                 } else {
5589                         memset(&stage_status, 0, sizeof(stage_status));
5590                 }
5592                 stage_line_type = line->type;
5593                 stage_chunks = 0;
5594                 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
5595         }
5597         return REQ_NONE;
5600 static bool
5601 status_exists(struct status *status, enum line_type type)
5603         struct view *view = VIEW(REQ_VIEW_STATUS);
5604         unsigned long lineno;
5606         for (lineno = 0; lineno < view->lines; lineno++) {
5607                 struct line *line = &view->line[lineno];
5608                 struct status *pos = line->data;
5610                 if (line->type != type)
5611                         continue;
5612                 if (!pos && (!status || !status->status) && line[1].data) {
5613                         select_view_line(view, lineno);
5614                         return TRUE;
5615                 }
5616                 if (pos && !strcmp(status->new.name, pos->new.name)) {
5617                         select_view_line(view, lineno);
5618                         return TRUE;
5619                 }
5620         }
5622         return FALSE;
5626 static bool
5627 status_update_prepare(struct io *io, enum line_type type)
5629         const char *staged_argv[] = {
5630                 "git", "update-index", "-z", "--index-info", NULL
5631         };
5632         const char *others_argv[] = {
5633                 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
5634         };
5636         switch (type) {
5637         case LINE_STAT_STAGED:
5638                 return run_io(io, staged_argv, opt_cdup, IO_WR);
5640         case LINE_STAT_UNSTAGED:
5641                 return run_io(io, others_argv, opt_cdup, IO_WR);
5643         case LINE_STAT_UNTRACKED:
5644                 return run_io(io, others_argv, NULL, IO_WR);
5646         default:
5647                 die("line type %d not handled in switch", type);
5648                 return FALSE;
5649         }
5652 static bool
5653 status_update_write(struct io *io, struct status *status, enum line_type type)
5655         char buf[SIZEOF_STR];
5656         size_t bufsize = 0;
5658         switch (type) {
5659         case LINE_STAT_STAGED:
5660                 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
5661                                         status->old.mode,
5662                                         status->old.rev,
5663                                         status->old.name, 0))
5664                         return FALSE;
5665                 break;
5667         case LINE_STAT_UNSTAGED:
5668         case LINE_STAT_UNTRACKED:
5669                 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
5670                         return FALSE;
5671                 break;
5673         default:
5674                 die("line type %d not handled in switch", type);
5675         }
5677         return io_write(io, buf, bufsize);
5680 static bool
5681 status_update_file(struct status *status, enum line_type type)
5683         struct io io = {};
5684         bool result;
5686         if (!status_update_prepare(&io, type))
5687                 return FALSE;
5689         result = status_update_write(&io, status, type);
5690         return done_io(&io) && result;
5693 static bool
5694 status_update_files(struct view *view, struct line *line)
5696         char buf[sizeof(view->ref)];
5697         struct io io = {};
5698         bool result = TRUE;
5699         struct line *pos = view->line + view->lines;
5700         int files = 0;
5701         int file, done;
5702         int cursor_y = -1, cursor_x = -1;
5704         if (!status_update_prepare(&io, line->type))
5705                 return FALSE;
5707         for (pos = line; pos < view->line + view->lines && pos->data; pos++)
5708                 files++;
5710         string_copy(buf, view->ref);
5711         getsyx(cursor_y, cursor_x);
5712         for (file = 0, done = 5; result && file < files; line++, file++) {
5713                 int almost_done = file * 100 / files;
5715                 if (almost_done > done) {
5716                         done = almost_done;
5717                         string_format(view->ref, "updating file %u of %u (%d%% done)",
5718                                       file, files, done);
5719                         update_view_title(view);
5720                         setsyx(cursor_y, cursor_x);
5721                         doupdate();
5722                 }
5723                 result = status_update_write(&io, line->data, line->type);
5724         }
5725         string_copy(view->ref, buf);
5727         return done_io(&io) && result;
5730 static bool
5731 status_update(struct view *view)
5733         struct line *line = &view->line[view->lineno];
5735         assert(view->lines);
5737         if (!line->data) {
5738                 /* This should work even for the "On branch" line. */
5739                 if (line < view->line + view->lines && !line[1].data) {
5740                         report("Nothing to update");
5741                         return FALSE;
5742                 }
5744                 if (!status_update_files(view, line + 1)) {
5745                         report("Failed to update file status");
5746                         return FALSE;
5747                 }
5749         } else if (!status_update_file(line->data, line->type)) {
5750                 report("Failed to update file status");
5751                 return FALSE;
5752         }
5754         return TRUE;
5757 static bool
5758 status_revert(struct status *status, enum line_type type, bool has_none)
5760         if (!status || type != LINE_STAT_UNSTAGED) {
5761                 if (type == LINE_STAT_STAGED) {
5762                         report("Cannot revert changes to staged files");
5763                 } else if (type == LINE_STAT_UNTRACKED) {
5764                         report("Cannot revert changes to untracked files");
5765                 } else if (has_none) {
5766                         report("Nothing to revert");
5767                 } else {
5768                         report("Cannot revert changes to multiple files");
5769                 }
5770                 return FALSE;
5772         } else {
5773                 char mode[10] = "100644";
5774                 const char *reset_argv[] = {
5775                         "git", "update-index", "--cacheinfo", mode,
5776                                 status->old.rev, status->old.name, NULL
5777                 };
5778                 const char *checkout_argv[] = {
5779                         "git", "checkout", "--", status->old.name, NULL
5780                 };
5782                 if (!prompt_yesno("Are you sure you want to overwrite any changes?"))
5783                         return FALSE;
5784                 string_format(mode, "%o", status->old.mode);
5785                 return (status->status != 'U' || run_io_fg(reset_argv, opt_cdup)) &&
5786                         run_io_fg(checkout_argv, opt_cdup);
5787         }
5790 static enum request
5791 status_request(struct view *view, enum request request, struct line *line)
5793         struct status *status = line->data;
5795         switch (request) {
5796         case REQ_STATUS_UPDATE:
5797                 if (!status_update(view))
5798                         return REQ_NONE;
5799                 break;
5801         case REQ_STATUS_REVERT:
5802                 if (!status_revert(status, line->type, status_has_none(view, line)))
5803                         return REQ_NONE;
5804                 break;
5806         case REQ_STATUS_MERGE:
5807                 if (!status || status->status != 'U') {
5808                         report("Merging only possible for files with unmerged status ('U').");
5809                         return REQ_NONE;
5810                 }
5811                 open_mergetool(status->new.name);
5812                 break;
5814         case REQ_EDIT:
5815                 if (!status)
5816                         return request;
5817                 if (status->status == 'D') {
5818                         report("File has been deleted.");
5819                         return REQ_NONE;
5820                 }
5822                 open_editor(status->status != '?', status->new.name);
5823                 break;
5825         case REQ_VIEW_BLAME:
5826                 if (status) {
5827                         string_copy(opt_file, status->new.name);
5828                         opt_ref[0] = 0;
5829                 }
5830                 return request;
5832         case REQ_ENTER:
5833                 /* After returning the status view has been split to
5834                  * show the stage view. No further reloading is
5835                  * necessary. */
5836                 return status_enter(view, line);
5838         case REQ_REFRESH:
5839                 /* Simply reload the view. */
5840                 break;
5842         default:
5843                 return request;
5844         }
5846         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
5848         return REQ_NONE;
5851 static void
5852 status_select(struct view *view, struct line *line)
5854         struct status *status = line->data;
5855         char file[SIZEOF_STR] = "all files";
5856         const char *text;
5857         const char *key;
5859         if (status && !string_format(file, "'%s'", status->new.name))
5860                 return;
5862         if (!status && line[1].type == LINE_STAT_NONE)
5863                 line++;
5865         switch (line->type) {
5866         case LINE_STAT_STAGED:
5867                 text = "Press %s to unstage %s for commit";
5868                 break;
5870         case LINE_STAT_UNSTAGED:
5871                 text = "Press %s to stage %s for commit";
5872                 break;
5874         case LINE_STAT_UNTRACKED:
5875                 text = "Press %s to stage %s for addition";
5876                 break;
5878         case LINE_STAT_HEAD:
5879         case LINE_STAT_NONE:
5880                 text = "Nothing to update";
5881                 break;
5883         default:
5884                 die("line type %d not handled in switch", line->type);
5885         }
5887         if (status && status->status == 'U') {
5888                 text = "Press %s to resolve conflict in %s";
5889                 key = get_key(KEYMAP_STATUS, REQ_STATUS_MERGE);
5891         } else {
5892                 key = get_key(KEYMAP_STATUS, REQ_STATUS_UPDATE);
5893         }
5895         string_format(view->ref, text, key, file);
5898 static bool
5899 status_grep(struct view *view, struct line *line)
5901         struct status *status = line->data;
5903         if (status) {
5904                 const char buf[2] = { status->status, 0 };
5905                 const char *text[] = { status->new.name, buf, NULL };
5907                 return grep_text(view, text);
5908         }
5910         return FALSE;
5913 static struct view_ops status_ops = {
5914         "file",
5915         NULL,
5916         status_open,
5917         NULL,
5918         status_draw,
5919         status_request,
5920         status_grep,
5921         status_select,
5922 };
5925 static bool
5926 stage_diff_write(struct io *io, struct line *line, struct line *end)
5928         while (line < end) {
5929                 if (!io_write(io, line->data, strlen(line->data)) ||
5930                     !io_write(io, "\n", 1))
5931                         return FALSE;
5932                 line++;
5933                 if (line->type == LINE_DIFF_CHUNK ||
5934                     line->type == LINE_DIFF_HEADER)
5935                         break;
5936         }
5938         return TRUE;
5941 static struct line *
5942 stage_diff_find(struct view *view, struct line *line, enum line_type type)
5944         for (; view->line < line; line--)
5945                 if (line->type == type)
5946                         return line;
5948         return NULL;
5951 static bool
5952 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
5954         const char *apply_argv[SIZEOF_ARG] = {
5955                 "git", "apply", "--whitespace=nowarn", NULL
5956         };
5957         struct line *diff_hdr;
5958         struct io io = {};
5959         int argc = 3;
5961         diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
5962         if (!diff_hdr)
5963                 return FALSE;
5965         if (!revert)
5966                 apply_argv[argc++] = "--cached";
5967         if (revert || stage_line_type == LINE_STAT_STAGED)
5968                 apply_argv[argc++] = "-R";
5969         apply_argv[argc++] = "-";
5970         apply_argv[argc++] = NULL;
5971         if (!run_io(&io, apply_argv, opt_cdup, IO_WR))
5972                 return FALSE;
5974         if (!stage_diff_write(&io, diff_hdr, chunk) ||
5975             !stage_diff_write(&io, chunk, view->line + view->lines))
5976                 chunk = NULL;
5978         done_io(&io);
5979         run_io_bg(update_index_argv);
5981         return chunk ? TRUE : FALSE;
5984 static bool
5985 stage_update(struct view *view, struct line *line)
5987         struct line *chunk = NULL;
5989         if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
5990                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
5992         if (chunk) {
5993                 if (!stage_apply_chunk(view, chunk, FALSE)) {
5994                         report("Failed to apply chunk");
5995                         return FALSE;
5996                 }
5998         } else if (!stage_status.status) {
5999                 view = VIEW(REQ_VIEW_STATUS);
6001                 for (line = view->line; line < view->line + view->lines; line++)
6002                         if (line->type == stage_line_type)
6003                                 break;
6005                 if (!status_update_files(view, line + 1)) {
6006                         report("Failed to update files");
6007                         return FALSE;
6008                 }
6010         } else if (!status_update_file(&stage_status, stage_line_type)) {
6011                 report("Failed to update file");
6012                 return FALSE;
6013         }
6015         return TRUE;
6018 static bool
6019 stage_revert(struct view *view, struct line *line)
6021         struct line *chunk = NULL;
6023         if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
6024                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6026         if (chunk) {
6027                 if (!prompt_yesno("Are you sure you want to revert changes?"))
6028                         return FALSE;
6030                 if (!stage_apply_chunk(view, chunk, TRUE)) {
6031                         report("Failed to revert chunk");
6032                         return FALSE;
6033                 }
6034                 return TRUE;
6036         } else {
6037                 return status_revert(stage_status.status ? &stage_status : NULL,
6038                                      stage_line_type, FALSE);
6039         }
6043 static void
6044 stage_next(struct view *view, struct line *line)
6046         int i;
6048         if (!stage_chunks) {
6049                 for (line = view->line; line < view->line + view->lines; line++) {
6050                         if (line->type != LINE_DIFF_CHUNK)
6051                                 continue;
6053                         if (!realloc_ints(&stage_chunk, stage_chunks, 1)) {
6054                                 report("Allocation failure");
6055                                 return;
6056                         }
6058                         stage_chunk[stage_chunks++] = line - view->line;
6059                 }
6060         }
6062         for (i = 0; i < stage_chunks; i++) {
6063                 if (stage_chunk[i] > view->lineno) {
6064                         do_scroll_view(view, stage_chunk[i] - view->lineno);
6065                         report("Chunk %d of %d", i + 1, stage_chunks);
6066                         return;
6067                 }
6068         }
6070         report("No next chunk found");
6073 static enum request
6074 stage_request(struct view *view, enum request request, struct line *line)
6076         switch (request) {
6077         case REQ_STATUS_UPDATE:
6078                 if (!stage_update(view, line))
6079                         return REQ_NONE;
6080                 break;
6082         case REQ_STATUS_REVERT:
6083                 if (!stage_revert(view, line))
6084                         return REQ_NONE;
6085                 break;
6087         case REQ_STAGE_NEXT:
6088                 if (stage_line_type == LINE_STAT_UNTRACKED) {
6089                         report("File is untracked; press %s to add",
6090                                get_key(KEYMAP_STAGE, REQ_STATUS_UPDATE));
6091                         return REQ_NONE;
6092                 }
6093                 stage_next(view, line);
6094                 return REQ_NONE;
6096         case REQ_EDIT:
6097                 if (!stage_status.new.name[0])
6098                         return request;
6099                 if (stage_status.status == 'D') {
6100                         report("File has been deleted.");
6101                         return REQ_NONE;
6102                 }
6104                 open_editor(stage_status.status != '?', stage_status.new.name);
6105                 break;
6107         case REQ_REFRESH:
6108                 /* Reload everything ... */
6109                 break;
6111         case REQ_VIEW_BLAME:
6112                 if (stage_status.new.name[0]) {
6113                         string_copy(opt_file, stage_status.new.name);
6114                         opt_ref[0] = 0;
6115                 }
6116                 return request;
6118         case REQ_ENTER:
6119                 return pager_request(view, request, line);
6121         default:
6122                 return request;
6123         }
6125         VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
6126         open_view(view, REQ_VIEW_STATUS, OPEN_REFRESH);
6128         /* Check whether the staged entry still exists, and close the
6129          * stage view if it doesn't. */
6130         if (!status_exists(&stage_status, stage_line_type)) {
6131                 status_restore(VIEW(REQ_VIEW_STATUS));
6132                 return REQ_VIEW_CLOSE;
6133         }
6135         if (stage_line_type == LINE_STAT_UNTRACKED) {
6136                 if (!suffixcmp(stage_status.new.name, -1, "/")) {
6137                         report("Cannot display a directory");
6138                         return REQ_NONE;
6139                 }
6141                 if (!prepare_update_file(view, stage_status.new.name)) {
6142                         report("Failed to open file: %s", strerror(errno));
6143                         return REQ_NONE;
6144                 }
6145         }
6146         open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
6148         return REQ_NONE;
6151 static struct view_ops stage_ops = {
6152         "line",
6153         NULL,
6154         NULL,
6155         pager_read,
6156         pager_draw,
6157         stage_request,
6158         pager_grep,
6159         pager_select,
6160 };
6163 /*
6164  * Revision graph
6165  */
6167 struct commit {
6168         char id[SIZEOF_REV];            /* SHA1 ID. */
6169         char title[128];                /* First line of the commit message. */
6170         const char *author;             /* Author of the commit. */
6171         time_t time;                    /* Date from the author ident. */
6172         struct ref_list *refs;          /* Repository references. */
6173         chtype graph[SIZEOF_REVGRAPH];  /* Ancestry chain graphics. */
6174         size_t graph_size;              /* The width of the graph array. */
6175         bool has_parents;               /* Rewritten --parents seen. */
6176 };
6178 /* Size of rev graph with no  "padding" columns */
6179 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
6181 struct rev_graph {
6182         struct rev_graph *prev, *next, *parents;
6183         char rev[SIZEOF_REVITEMS][SIZEOF_REV];
6184         size_t size;
6185         struct commit *commit;
6186         size_t pos;
6187         unsigned int boundary:1;
6188 };
6190 /* Parents of the commit being visualized. */
6191 static struct rev_graph graph_parents[4];
6193 /* The current stack of revisions on the graph. */
6194 static struct rev_graph graph_stacks[4] = {
6195         { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
6196         { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
6197         { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
6198         { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
6199 };
6201 static inline bool
6202 graph_parent_is_merge(struct rev_graph *graph)
6204         return graph->parents->size > 1;
6207 static inline void
6208 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
6210         struct commit *commit = graph->commit;
6212         if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
6213                 commit->graph[commit->graph_size++] = symbol;
6216 static void
6217 clear_rev_graph(struct rev_graph *graph)
6219         graph->boundary = 0;
6220         graph->size = graph->pos = 0;
6221         graph->commit = NULL;
6222         memset(graph->parents, 0, sizeof(*graph->parents));
6225 static void
6226 done_rev_graph(struct rev_graph *graph)
6228         if (graph_parent_is_merge(graph) &&
6229             graph->pos < graph->size - 1 &&
6230             graph->next->size == graph->size + graph->parents->size - 1) {
6231                 size_t i = graph->pos + graph->parents->size - 1;
6233                 graph->commit->graph_size = i * 2;
6234                 while (i < graph->next->size - 1) {
6235                         append_to_rev_graph(graph, ' ');
6236                         append_to_rev_graph(graph, '\\');
6237                         i++;
6238                 }
6239         }
6241         clear_rev_graph(graph);
6244 static void
6245 push_rev_graph(struct rev_graph *graph, const char *parent)
6247         int i;
6249         /* "Collapse" duplicate parents lines.
6250          *
6251          * FIXME: This needs to also update update the drawn graph but
6252          * for now it just serves as a method for pruning graph lines. */
6253         for (i = 0; i < graph->size; i++)
6254                 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
6255                         return;
6257         if (graph->size < SIZEOF_REVITEMS) {
6258                 string_copy_rev(graph->rev[graph->size++], parent);
6259         }
6262 static chtype
6263 get_rev_graph_symbol(struct rev_graph *graph)
6265         chtype symbol;
6267         if (graph->boundary)
6268                 symbol = REVGRAPH_BOUND;
6269         else if (graph->parents->size == 0)
6270                 symbol = REVGRAPH_INIT;
6271         else if (graph_parent_is_merge(graph))
6272                 symbol = REVGRAPH_MERGE;
6273         else if (graph->pos >= graph->size)
6274                 symbol = REVGRAPH_BRANCH;
6275         else
6276                 symbol = REVGRAPH_COMMIT;
6278         return symbol;
6281 static void
6282 draw_rev_graph(struct rev_graph *graph)
6284         struct rev_filler {
6285                 chtype separator, line;
6286         };
6287         enum { DEFAULT, RSHARP, RDIAG, LDIAG };
6288         static struct rev_filler fillers[] = {
6289                 { ' ',  '|' },
6290                 { '`',  '.' },
6291                 { '\'', ' ' },
6292                 { '/',  ' ' },
6293         };
6294         chtype symbol = get_rev_graph_symbol(graph);
6295         struct rev_filler *filler;
6296         size_t i;
6298         if (opt_line_graphics)
6299                 fillers[DEFAULT].line = line_graphics[LINE_GRAPHIC_VLINE];
6301         filler = &fillers[DEFAULT];
6303         for (i = 0; i < graph->pos; i++) {
6304                 append_to_rev_graph(graph, filler->line);
6305                 if (graph_parent_is_merge(graph->prev) &&
6306                     graph->prev->pos == i)
6307                         filler = &fillers[RSHARP];
6309                 append_to_rev_graph(graph, filler->separator);
6310         }
6312         /* Place the symbol for this revision. */
6313         append_to_rev_graph(graph, symbol);
6315         if (graph->prev->size > graph->size)
6316                 filler = &fillers[RDIAG];
6317         else
6318                 filler = &fillers[DEFAULT];
6320         i++;
6322         for (; i < graph->size; i++) {
6323                 append_to_rev_graph(graph, filler->separator);
6324                 append_to_rev_graph(graph, filler->line);
6325                 if (graph_parent_is_merge(graph->prev) &&
6326                     i < graph->prev->pos + graph->parents->size)
6327                         filler = &fillers[RSHARP];
6328                 if (graph->prev->size > graph->size)
6329                         filler = &fillers[LDIAG];
6330         }
6332         if (graph->prev->size > graph->size) {
6333                 append_to_rev_graph(graph, filler->separator);
6334                 if (filler->line != ' ')
6335                         append_to_rev_graph(graph, filler->line);
6336         }
6339 /* Prepare the next rev graph */
6340 static void
6341 prepare_rev_graph(struct rev_graph *graph)
6343         size_t i;
6345         /* First, traverse all lines of revisions up to the active one. */
6346         for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
6347                 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
6348                         break;
6350                 push_rev_graph(graph->next, graph->rev[graph->pos]);
6351         }
6353         /* Interleave the new revision parent(s). */
6354         for (i = 0; !graph->boundary && i < graph->parents->size; i++)
6355                 push_rev_graph(graph->next, graph->parents->rev[i]);
6357         /* Lastly, put any remaining revisions. */
6358         for (i = graph->pos + 1; i < graph->size; i++)
6359                 push_rev_graph(graph->next, graph->rev[i]);
6362 static void
6363 update_rev_graph(struct view *view, struct rev_graph *graph)
6365         /* If this is the finalizing update ... */
6366         if (graph->commit)
6367                 prepare_rev_graph(graph);
6369         /* Graph visualization needs a one rev look-ahead,
6370          * so the first update doesn't visualize anything. */
6371         if (!graph->prev->commit)
6372                 return;
6374         if (view->lines > 2)
6375                 view->line[view->lines - 3].dirty = 1;
6376         if (view->lines > 1)
6377                 view->line[view->lines - 2].dirty = 1;
6378         draw_rev_graph(graph->prev);
6379         done_rev_graph(graph->prev->prev);
6383 /*
6384  * Main view backend
6385  */
6387 static const char *main_argv[SIZEOF_ARG] = {
6388         "git", "log", "--no-color", "--pretty=raw", "--parents",
6389                       "--topo-order", "%(head)", NULL
6390 };
6392 static bool
6393 main_draw(struct view *view, struct line *line, unsigned int lineno)
6395         struct commit *commit = line->data;
6397         if (!commit->author)
6398                 return FALSE;
6400         if (opt_date && draw_date(view, &commit->time))
6401                 return TRUE;
6403         if (opt_author && draw_author(view, commit->author))
6404                 return TRUE;
6406         if (opt_rev_graph && commit->graph_size &&
6407             draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
6408                 return TRUE;
6410         if (opt_show_refs && commit->refs) {
6411                 size_t i;
6413                 for (i = 0; i < commit->refs->size; i++) {
6414                         struct ref *ref = commit->refs->refs[i];
6415                         enum line_type type;
6417                         if (ref->head)
6418                                 type = LINE_MAIN_HEAD;
6419                         else if (ref->ltag)
6420                                 type = LINE_MAIN_LOCAL_TAG;
6421                         else if (ref->tag)
6422                                 type = LINE_MAIN_TAG;
6423                         else if (ref->tracked)
6424                                 type = LINE_MAIN_TRACKED;
6425                         else if (ref->remote)
6426                                 type = LINE_MAIN_REMOTE;
6427                         else
6428                                 type = LINE_MAIN_REF;
6430                         if (draw_text(view, type, "[", TRUE) ||
6431                             draw_text(view, type, ref->name, TRUE) ||
6432                             draw_text(view, type, "]", TRUE))
6433                                 return TRUE;
6435                         if (draw_text(view, LINE_DEFAULT, " ", TRUE))
6436                                 return TRUE;
6437                 }
6438         }
6440         draw_text(view, LINE_DEFAULT, commit->title, TRUE);
6441         return TRUE;
6444 /* Reads git log --pretty=raw output and parses it into the commit struct. */
6445 static bool
6446 main_read(struct view *view, char *line)
6448         static struct rev_graph *graph = graph_stacks;
6449         enum line_type type;
6450         struct commit *commit;
6452         if (!line) {
6453                 int i;
6455                 if (!view->lines && !view->parent)
6456                         die("No revisions match the given arguments.");
6457                 if (view->lines > 0) {
6458                         commit = view->line[view->lines - 1].data;
6459                         view->line[view->lines - 1].dirty = 1;
6460                         if (!commit->author) {
6461                                 view->lines--;
6462                                 free(commit);
6463                                 graph->commit = NULL;
6464                         }
6465                 }
6466                 update_rev_graph(view, graph);
6468                 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
6469                         clear_rev_graph(&graph_stacks[i]);
6470                 return TRUE;
6471         }
6473         type = get_line_type(line);
6474         if (type == LINE_COMMIT) {
6475                 commit = calloc(1, sizeof(struct commit));
6476                 if (!commit)
6477                         return FALSE;
6479                 line += STRING_SIZE("commit ");
6480                 if (*line == '-') {
6481                         graph->boundary = 1;
6482                         line++;
6483                 }
6485                 string_copy_rev(commit->id, line);
6486                 commit->refs = get_ref_list(commit->id);
6487                 graph->commit = commit;
6488                 add_line_data(view, commit, LINE_MAIN_COMMIT);
6490                 while ((line = strchr(line, ' '))) {
6491                         line++;
6492                         push_rev_graph(graph->parents, line);
6493                         commit->has_parents = TRUE;
6494                 }
6495                 return TRUE;
6496         }
6498         if (!view->lines)
6499                 return TRUE;
6500         commit = view->line[view->lines - 1].data;
6502         switch (type) {
6503         case LINE_PARENT:
6504                 if (commit->has_parents)
6505                         break;
6506                 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
6507                 break;
6509         case LINE_AUTHOR:
6510                 parse_author_line(line + STRING_SIZE("author "),
6511                                   &commit->author, &commit->time);
6512                 update_rev_graph(view, graph);
6513                 graph = graph->next;
6514                 break;
6516         default:
6517                 /* Fill in the commit title if it has not already been set. */
6518                 if (commit->title[0])
6519                         break;
6521                 /* Require titles to start with a non-space character at the
6522                  * offset used by git log. */
6523                 if (strncmp(line, "    ", 4))
6524                         break;
6525                 line += 4;
6526                 /* Well, if the title starts with a whitespace character,
6527                  * try to be forgiving.  Otherwise we end up with no title. */
6528                 while (isspace(*line))
6529                         line++;
6530                 if (*line == '\0')
6531                         break;
6532                 /* FIXME: More graceful handling of titles; append "..." to
6533                  * shortened titles, etc. */
6535                 string_expand(commit->title, sizeof(commit->title), line, 1);
6536                 view->line[view->lines - 1].dirty = 1;
6537         }
6539         return TRUE;
6542 static enum request
6543 main_request(struct view *view, enum request request, struct line *line)
6545         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
6547         switch (request) {
6548         case REQ_ENTER:
6549                 open_view(view, REQ_VIEW_DIFF, flags);
6550                 break;
6551         case REQ_REFRESH:
6552                 load_refs();
6553                 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
6554                 break;
6555         default:
6556                 return request;
6557         }
6559         return REQ_NONE;
6562 static bool
6563 grep_refs(struct ref_list *list, regex_t *regex)
6565         regmatch_t pmatch;
6566         size_t i;
6568         if (!opt_show_refs || !list)
6569                 return FALSE;
6571         for (i = 0; i < list->size; i++) {
6572                 if (regexec(regex, list->refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
6573                         return TRUE;
6574         }
6576         return FALSE;
6579 static bool
6580 main_grep(struct view *view, struct line *line)
6582         struct commit *commit = line->data;
6583         const char *text[] = {
6584                 commit->title,
6585                 opt_author ? commit->author : "",
6586                 opt_date ? mkdate(&commit->time) : "",
6587                 NULL
6588         };
6590         return grep_text(view, text) || grep_refs(commit->refs, view->regex);
6593 static void
6594 main_select(struct view *view, struct line *line)
6596         struct commit *commit = line->data;
6598         string_copy_rev(view->ref, commit->id);
6599         string_copy_rev(ref_commit, view->ref);
6602 static struct view_ops main_ops = {
6603         "commit",
6604         main_argv,
6605         NULL,
6606         main_read,
6607         main_draw,
6608         main_request,
6609         main_grep,
6610         main_select,
6611 };
6614 /*
6615  * Unicode / UTF-8 handling
6616  *
6617  * NOTE: Much of the following code for dealing with Unicode is derived from
6618  * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
6619  * src/intl/charset.c from the UTF-8 branch commit elinks-0.11.0-g31f2c28.
6620  */
6622 static inline int
6623 unicode_width(unsigned long c)
6625         if (c >= 0x1100 &&
6626            (c <= 0x115f                         /* Hangul Jamo */
6627             || c == 0x2329
6628             || c == 0x232a
6629             || (c >= 0x2e80  && c <= 0xa4cf && c != 0x303f)
6630                                                 /* CJK ... Yi */
6631             || (c >= 0xac00  && c <= 0xd7a3)    /* Hangul Syllables */
6632             || (c >= 0xf900  && c <= 0xfaff)    /* CJK Compatibility Ideographs */
6633             || (c >= 0xfe30  && c <= 0xfe6f)    /* CJK Compatibility Forms */
6634             || (c >= 0xff00  && c <= 0xff60)    /* Fullwidth Forms */
6635             || (c >= 0xffe0  && c <= 0xffe6)
6636             || (c >= 0x20000 && c <= 0x2fffd)
6637             || (c >= 0x30000 && c <= 0x3fffd)))
6638                 return 2;
6640         if (c == '\t')
6641                 return opt_tab_size;
6643         return 1;
6646 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
6647  * Illegal bytes are set one. */
6648 static const unsigned char utf8_bytes[256] = {
6649         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,
6650         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,
6651         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,
6652         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,
6653         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,
6654         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,
6655         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,
6656         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,
6657 };
6659 /* Decode UTF-8 multi-byte representation into a Unicode character. */
6660 static inline unsigned long
6661 utf8_to_unicode(const char *string, size_t length)
6663         unsigned long unicode;
6665         switch (length) {
6666         case 1:
6667                 unicode  =   string[0];
6668                 break;
6669         case 2:
6670                 unicode  =  (string[0] & 0x1f) << 6;
6671                 unicode +=  (string[1] & 0x3f);
6672                 break;
6673         case 3:
6674                 unicode  =  (string[0] & 0x0f) << 12;
6675                 unicode += ((string[1] & 0x3f) << 6);
6676                 unicode +=  (string[2] & 0x3f);
6677                 break;
6678         case 4:
6679                 unicode  =  (string[0] & 0x0f) << 18;
6680                 unicode += ((string[1] & 0x3f) << 12);
6681                 unicode += ((string[2] & 0x3f) << 6);
6682                 unicode +=  (string[3] & 0x3f);
6683                 break;
6684         case 5:
6685                 unicode  =  (string[0] & 0x0f) << 24;
6686                 unicode += ((string[1] & 0x3f) << 18);
6687                 unicode += ((string[2] & 0x3f) << 12);
6688                 unicode += ((string[3] & 0x3f) << 6);
6689                 unicode +=  (string[4] & 0x3f);
6690                 break;
6691         case 6:
6692                 unicode  =  (string[0] & 0x01) << 30;
6693                 unicode += ((string[1] & 0x3f) << 24);
6694                 unicode += ((string[2] & 0x3f) << 18);
6695                 unicode += ((string[3] & 0x3f) << 12);
6696                 unicode += ((string[4] & 0x3f) << 6);
6697                 unicode +=  (string[5] & 0x3f);
6698                 break;
6699         default:
6700                 die("Invalid Unicode length");
6701         }
6703         /* Invalid characters could return the special 0xfffd value but NUL
6704          * should be just as good. */
6705         return unicode > 0xffff ? 0 : unicode;
6708 /* Calculates how much of string can be shown within the given maximum width
6709  * and sets trimmed parameter to non-zero value if all of string could not be
6710  * shown. If the reserve flag is TRUE, it will reserve at least one
6711  * trailing character, which can be useful when drawing a delimiter.
6712  *
6713  * Returns the number of bytes to output from string to satisfy max_width. */
6714 static size_t
6715 utf8_length(const char **start, size_t skip, int *width, size_t max_width, int *trimmed, bool reserve)
6717         const char *string = *start;
6718         const char *end = strchr(string, '\0');
6719         unsigned char last_bytes = 0;
6720         size_t last_ucwidth = 0;
6722         *width = 0;
6723         *trimmed = 0;
6725         while (string < end) {
6726                 int c = *(unsigned char *) string;
6727                 unsigned char bytes = utf8_bytes[c];
6728                 size_t ucwidth;
6729                 unsigned long unicode;
6731                 if (string + bytes > end)
6732                         break;
6734                 /* Change representation to figure out whether
6735                  * it is a single- or double-width character. */
6737                 unicode = utf8_to_unicode(string, bytes);
6738                 /* FIXME: Graceful handling of invalid Unicode character. */
6739                 if (!unicode)
6740                         break;
6742                 ucwidth = unicode_width(unicode);
6743                 if (skip > 0) {
6744                         skip -= ucwidth <= skip ? ucwidth : skip;
6745                         *start += bytes;
6746                 }
6747                 *width  += ucwidth;
6748                 if (*width > max_width) {
6749                         *trimmed = 1;
6750                         *width -= ucwidth;
6751                         if (reserve && *width == max_width) {
6752                                 string -= last_bytes;
6753                                 *width -= last_ucwidth;
6754                         }
6755                         break;
6756                 }
6758                 string  += bytes;
6759                 last_bytes = ucwidth ? bytes : 0;
6760                 last_ucwidth = ucwidth;
6761         }
6763         return string - *start;
6767 /*
6768  * Status management
6769  */
6771 /* Whether or not the curses interface has been initialized. */
6772 static bool cursed = FALSE;
6774 /* Terminal hacks and workarounds. */
6775 static bool use_scroll_redrawwin;
6776 static bool use_scroll_status_wclear;
6778 /* The status window is used for polling keystrokes. */
6779 static WINDOW *status_win;
6781 /* Reading from the prompt? */
6782 static bool input_mode = FALSE;
6784 static bool status_empty = FALSE;
6786 /* Update status and title window. */
6787 static void
6788 report(const char *msg, ...)
6790         struct view *view = display[current_view];
6792         if (input_mode)
6793                 return;
6795         if (!view) {
6796                 char buf[SIZEOF_STR];
6797                 va_list args;
6799                 va_start(args, msg);
6800                 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
6801                         buf[sizeof(buf) - 1] = 0;
6802                         buf[sizeof(buf) - 2] = '.';
6803                         buf[sizeof(buf) - 3] = '.';
6804                         buf[sizeof(buf) - 4] = '.';
6805                 }
6806                 va_end(args);
6807                 die("%s", buf);
6808         }
6810         if (!status_empty || *msg) {
6811                 va_list args;
6813                 va_start(args, msg);
6815                 wmove(status_win, 0, 0);
6816                 if (view->has_scrolled && use_scroll_status_wclear)
6817                         wclear(status_win);
6818                 if (*msg) {
6819                         vwprintw(status_win, msg, args);
6820                         status_empty = FALSE;
6821                 } else {
6822                         status_empty = TRUE;
6823                 }
6824                 wclrtoeol(status_win);
6825                 wnoutrefresh(status_win);
6827                 va_end(args);
6828         }
6830         update_view_title(view);
6833 /* Controls when nodelay should be in effect when polling user input. */
6834 static void
6835 set_nonblocking_input(bool loading)
6837         static unsigned int loading_views;
6839         if ((loading == FALSE && loading_views-- == 1) ||
6840             (loading == TRUE  && loading_views++ == 0))
6841                 nodelay(status_win, loading);
6844 static void
6845 init_display(void)
6847         const char *term;
6848         int x, y;
6850         /* Initialize the curses library */
6851         if (isatty(STDIN_FILENO)) {
6852                 cursed = !!initscr();
6853                 opt_tty = stdin;
6854         } else {
6855                 /* Leave stdin and stdout alone when acting as a pager. */
6856                 opt_tty = fopen("/dev/tty", "r+");
6857                 if (!opt_tty)
6858                         die("Failed to open /dev/tty");
6859                 cursed = !!newterm(NULL, opt_tty, opt_tty);
6860         }
6862         if (!cursed)
6863                 die("Failed to initialize curses");
6865         nonl();         /* Disable conversion and detect newlines from input. */
6866         cbreak();       /* Take input chars one at a time, no wait for \n */
6867         noecho();       /* Don't echo input */
6868         leaveok(stdscr, FALSE);
6870         if (has_colors())
6871                 init_colors();
6873         getmaxyx(stdscr, y, x);
6874         status_win = newwin(1, 0, y - 1, 0);
6875         if (!status_win)
6876                 die("Failed to create status window");
6878         /* Enable keyboard mapping */
6879         keypad(status_win, TRUE);
6880         wbkgdset(status_win, get_line_attr(LINE_STATUS));
6882         TABSIZE = opt_tab_size;
6883         if (opt_line_graphics) {
6884                 line_graphics[LINE_GRAPHIC_VLINE] = ACS_VLINE;
6885         }
6887         term = getenv("XTERM_VERSION") ? NULL : getenv("COLORTERM");
6888         if (term && !strcmp(term, "gnome-terminal")) {
6889                 /* In the gnome-terminal-emulator, the message from
6890                  * scrolling up one line when impossible followed by
6891                  * scrolling down one line causes corruption of the
6892                  * status line. This is fixed by calling wclear. */
6893                 use_scroll_status_wclear = TRUE;
6894                 use_scroll_redrawwin = FALSE;
6896         } else if (term && !strcmp(term, "xrvt-xpm")) {
6897                 /* No problems with full optimizations in xrvt-(unicode)
6898                  * and aterm. */
6899                 use_scroll_status_wclear = use_scroll_redrawwin = FALSE;
6901         } else {
6902                 /* When scrolling in (u)xterm the last line in the
6903                  * scrolling direction will update slowly. */
6904                 use_scroll_redrawwin = TRUE;
6905                 use_scroll_status_wclear = FALSE;
6906         }
6909 static int
6910 get_input(int prompt_position)
6912         struct view *view;
6913         int i, key, cursor_y, cursor_x;
6915         if (prompt_position)
6916                 input_mode = TRUE;
6918         while (TRUE) {
6919                 foreach_view (view, i) {
6920                         update_view(view);
6921                         if (view_is_displayed(view) && view->has_scrolled &&
6922                             use_scroll_redrawwin)
6923                                 redrawwin(view->win);
6924                         view->has_scrolled = FALSE;
6925                 }
6927                 /* Update the cursor position. */
6928                 if (prompt_position) {
6929                         getbegyx(status_win, cursor_y, cursor_x);
6930                         cursor_x = prompt_position;
6931                 } else {
6932                         view = display[current_view];
6933                         getbegyx(view->win, cursor_y, cursor_x);
6934                         cursor_x = view->width - 1;
6935                         cursor_y += view->lineno - view->offset;
6936                 }
6937                 setsyx(cursor_y, cursor_x);
6939                 /* Refresh, accept single keystroke of input */
6940                 doupdate();
6941                 key = wgetch(status_win);
6943                 /* wgetch() with nodelay() enabled returns ERR when
6944                  * there's no input. */
6945                 if (key == ERR) {
6947                 } else if (key == KEY_RESIZE) {
6948                         int height, width;
6950                         getmaxyx(stdscr, height, width);
6952                         wresize(status_win, 1, width);
6953                         mvwin(status_win, height - 1, 0);
6954                         wnoutrefresh(status_win);
6955                         resize_display();
6956                         redraw_display(TRUE);
6958                 } else {
6959                         input_mode = FALSE;
6960                         return key;
6961                 }
6962         }
6965 static char *
6966 prompt_input(const char *prompt, input_handler handler, void *data)
6968         enum input_status status = INPUT_OK;
6969         static char buf[SIZEOF_STR];
6970         size_t pos = 0;
6972         buf[pos] = 0;
6974         while (status == INPUT_OK || status == INPUT_SKIP) {
6975                 int key;
6977                 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
6978                 wclrtoeol(status_win);
6980                 key = get_input(pos + 1);
6981                 switch (key) {
6982                 case KEY_RETURN:
6983                 case KEY_ENTER:
6984                 case '\n':
6985                         status = pos ? INPUT_STOP : INPUT_CANCEL;
6986                         break;
6988                 case KEY_BACKSPACE:
6989                         if (pos > 0)
6990                                 buf[--pos] = 0;
6991                         else
6992                                 status = INPUT_CANCEL;
6993                         break;
6995                 case KEY_ESC:
6996                         status = INPUT_CANCEL;
6997                         break;
6999                 default:
7000                         if (pos >= sizeof(buf)) {
7001                                 report("Input string too long");
7002                                 return NULL;
7003                         }
7005                         status = handler(data, buf, key);
7006                         if (status == INPUT_OK)
7007                                 buf[pos++] = (char) key;
7008                 }
7009         }
7011         /* Clear the status window */
7012         status_empty = FALSE;
7013         report("");
7015         if (status == INPUT_CANCEL)
7016                 return NULL;
7018         buf[pos++] = 0;
7020         return buf;
7023 static enum input_status
7024 prompt_yesno_handler(void *data, char *buf, int c)
7026         if (c == 'y' || c == 'Y')
7027                 return INPUT_STOP;
7028         if (c == 'n' || c == 'N')
7029                 return INPUT_CANCEL;
7030         return INPUT_SKIP;
7033 static bool
7034 prompt_yesno(const char *prompt)
7036         char prompt2[SIZEOF_STR];
7038         if (!string_format(prompt2, "%s [Yy/Nn]", prompt))
7039                 return FALSE;
7041         return !!prompt_input(prompt2, prompt_yesno_handler, NULL);
7044 static enum input_status
7045 read_prompt_handler(void *data, char *buf, int c)
7047         return isprint(c) ? INPUT_OK : INPUT_SKIP;
7050 static char *
7051 read_prompt(const char *prompt)
7053         return prompt_input(prompt, read_prompt_handler, NULL);
7056 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected)
7058         enum input_status status = INPUT_OK;
7059         int size = 0;
7061         while (items[size].text)
7062                 size++;
7064         while (status == INPUT_OK) {
7065                 const struct menu_item *item = &items[*selected];
7066                 int key;
7067                 int i;
7069                 mvwprintw(status_win, 0, 0, "%s (%d of %d) ",
7070                           prompt, *selected + 1, size);
7071                 if (item->hotkey)
7072                         wprintw(status_win, "[%c] ", (char) item->hotkey);
7073                 wprintw(status_win, "%s", item->text);
7074                 wclrtoeol(status_win);
7076                 key = get_input(COLS - 1);
7077                 switch (key) {
7078                 case KEY_RETURN:
7079                 case KEY_ENTER:
7080                 case '\n':
7081                         status = INPUT_STOP;
7082                         break;
7084                 case KEY_LEFT:
7085                 case KEY_UP:
7086                         *selected = *selected - 1;
7087                         if (*selected < 0)
7088                                 *selected = size - 1;
7089                         break;
7091                 case KEY_RIGHT:
7092                 case KEY_DOWN:
7093                         *selected = (*selected + 1) % size;
7094                         break;
7096                 case KEY_ESC:
7097                         status = INPUT_CANCEL;
7098                         break;
7100                 default:
7101                         for (i = 0; items[i].text; i++)
7102                                 if (items[i].hotkey == key) {
7103                                         *selected = i;
7104                                         status = INPUT_STOP;
7105                                         break;
7106                                 }
7107                 }
7108         }
7110         /* Clear the status window */
7111         status_empty = FALSE;
7112         report("");
7114         return status != INPUT_CANCEL;
7117 /*
7118  * Repository properties
7119  */
7121 static struct ref **refs = NULL;
7122 static size_t refs_size = 0;
7124 static struct ref_list **ref_lists = NULL;
7125 static size_t ref_lists_size = 0;
7127 DEFINE_ALLOCATOR(realloc_refs, struct ref *, 256)
7128 DEFINE_ALLOCATOR(realloc_refs_list, struct ref *, 8)
7129 DEFINE_ALLOCATOR(realloc_ref_lists, struct ref_list *, 8)
7131 static int
7132 compare_refs(const void *ref1_, const void *ref2_)
7134         const struct ref *ref1 = *(const struct ref **)ref1_;
7135         const struct ref *ref2 = *(const struct ref **)ref2_;
7137         if (ref1->tag != ref2->tag)
7138                 return ref2->tag - ref1->tag;
7139         if (ref1->ltag != ref2->ltag)
7140                 return ref2->ltag - ref2->ltag;
7141         if (ref1->head != ref2->head)
7142                 return ref2->head - ref1->head;
7143         if (ref1->tracked != ref2->tracked)
7144                 return ref2->tracked - ref1->tracked;
7145         if (ref1->remote != ref2->remote)
7146                 return ref2->remote - ref1->remote;
7147         return strcmp(ref1->name, ref2->name);
7150 static void
7151 foreach_ref(bool (*visitor)(void *data, struct ref *ref), void *data)
7153         size_t i;
7155         for (i = 0; i < refs_size; i++)
7156                 if (!visitor(data, refs[i]))
7157                         break;
7160 static struct ref_list *
7161 get_ref_list(const char *id)
7163         struct ref_list *list;
7164         size_t i;
7166         for (i = 0; i < ref_lists_size; i++)
7167                 if (!strcmp(id, ref_lists[i]->id))
7168                         return ref_lists[i];
7170         if (!realloc_ref_lists(&ref_lists, ref_lists_size, 1))
7171                 return NULL;
7172         list = calloc(1, sizeof(*list));
7173         if (!list)
7174                 return NULL;
7176         for (i = 0; i < refs_size; i++) {
7177                 if (!strcmp(id, refs[i]->id) &&
7178                     realloc_refs_list(&list->refs, list->size, 1))
7179                         list->refs[list->size++] = refs[i];
7180         }
7182         if (!list->refs) {
7183                 free(list);
7184                 return NULL;
7185         }
7187         qsort(list->refs, list->size, sizeof(*list->refs), compare_refs);
7188         ref_lists[ref_lists_size++] = list;
7189         return list;
7192 static int
7193 read_ref(char *id, size_t idlen, char *name, size_t namelen)
7195         struct ref *ref = NULL;
7196         bool tag = FALSE;
7197         bool ltag = FALSE;
7198         bool remote = FALSE;
7199         bool tracked = FALSE;
7200         bool head = FALSE;
7201         int from = 0, to = refs_size - 1;
7203         if (!prefixcmp(name, "refs/tags/")) {
7204                 if (!suffixcmp(name, namelen, "^{}")) {
7205                         namelen -= 3;
7206                         name[namelen] = 0;
7207                 } else {
7208                         ltag = TRUE;
7209                 }
7211                 tag = TRUE;
7212                 namelen -= STRING_SIZE("refs/tags/");
7213                 name    += STRING_SIZE("refs/tags/");
7215         } else if (!prefixcmp(name, "refs/remotes/")) {
7216                 remote = TRUE;
7217                 namelen -= STRING_SIZE("refs/remotes/");
7218                 name    += STRING_SIZE("refs/remotes/");
7219                 tracked  = !strcmp(opt_remote, name);
7221         } else if (!prefixcmp(name, "refs/heads/")) {
7222                 namelen -= STRING_SIZE("refs/heads/");
7223                 name    += STRING_SIZE("refs/heads/");
7224                 head     = !strncmp(opt_head, name, namelen);
7226         } else if (!strcmp(name, "HEAD")) {
7227                 string_ncopy(opt_head_rev, id, idlen);
7228                 return OK;
7229         }
7231         /* If we are reloading or it's an annotated tag, replace the
7232          * previous SHA1 with the resolved commit id; relies on the fact
7233          * git-ls-remote lists the commit id of an annotated tag right
7234          * before the commit id it points to. */
7235         while (from <= to) {
7236                 size_t pos = (to + from) / 2;
7237                 int cmp = strcmp(name, refs[pos]->name);
7239                 if (!cmp) {
7240                         ref = refs[pos];
7241                         break;
7242                 }
7244                 if (cmp < 0)
7245                         to = pos - 1;
7246                 else
7247                         from = pos + 1;
7248         }
7250         if (!ref) {
7251                 if (!realloc_refs(&refs, refs_size, 1))
7252                         return ERR;
7253                 ref = calloc(1, sizeof(*ref) + namelen);
7254                 if (!ref)
7255                         return ERR;
7256                 memmove(refs + from + 1, refs + from,
7257                         (refs_size - from) * sizeof(*refs));
7258                 refs[from] = ref;
7259                 strncpy(ref->name, name, namelen);
7260                 refs_size++;
7261         }
7263         ref->head = head;
7264         ref->tag = tag;
7265         ref->ltag = ltag;
7266         ref->remote = remote;
7267         ref->tracked = tracked;
7268         string_copy_rev(ref->id, id);
7270         return OK;
7273 static int
7274 load_refs(void)
7276         const char *head_argv[] = {
7277                 "git", "symbolic-ref", "HEAD", NULL
7278         };
7279         static const char *ls_remote_argv[SIZEOF_ARG] = {
7280                 "git", "ls-remote", opt_git_dir, NULL
7281         };
7282         static bool init = FALSE;
7283         size_t i;
7285         if (!init) {
7286                 argv_from_env(ls_remote_argv, "TIG_LS_REMOTE");
7287                 init = TRUE;
7288         }
7290         if (!*opt_git_dir)
7291                 return OK;
7293         if (run_io_buf(head_argv, opt_head, sizeof(opt_head)) &&
7294             !prefixcmp(opt_head, "refs/heads/")) {
7295                 char *offset = opt_head + STRING_SIZE("refs/heads/");
7297                 memmove(opt_head, offset, strlen(offset) + 1);
7298         }
7300         for (i = 0; i < refs_size; i++)
7301                 refs[i]->id[0] = 0;
7303         if (run_io_load(ls_remote_argv, "\t", read_ref) == ERR)
7304                 return ERR;
7306         /* Update the ref lists to reflect changes. */
7307         for (i = 0; i < ref_lists_size; i++) {
7308                 struct ref_list *list = ref_lists[i];
7309                 size_t old, new;
7311                 for (old = new = 0; old < list->size; old++)
7312                         if (!strcmp(list->id, list->refs[old]->id))
7313                                 list->refs[new++] = list->refs[old];
7314                 list->size = new;
7315         }
7317         return OK;
7320 static void
7321 set_remote_branch(const char *name, const char *value, size_t valuelen)
7323         if (!strcmp(name, ".remote")) {
7324                 string_ncopy(opt_remote, value, valuelen);
7326         } else if (*opt_remote && !strcmp(name, ".merge")) {
7327                 size_t from = strlen(opt_remote);
7329                 if (!prefixcmp(value, "refs/heads/"))
7330                         value += STRING_SIZE("refs/heads/");
7332                 if (!string_format_from(opt_remote, &from, "/%s", value))
7333                         opt_remote[0] = 0;
7334         }
7337 static void
7338 set_repo_config_option(char *name, char *value, int (*cmd)(int, const char **))
7340         const char *argv[SIZEOF_ARG] = { name, "=" };
7341         int argc = 1 + (cmd == option_set_command);
7342         int error = ERR;
7344         if (!argv_from_string(argv, &argc, value))
7345                 config_msg = "Too many option arguments";
7346         else
7347                 error = cmd(argc, argv);
7349         if (error == ERR)
7350                 warn("Option 'tig.%s': %s", name, config_msg);
7353 static bool
7354 set_environment_variable(const char *name, const char *value)
7356         size_t len = strlen(name) + 1 + strlen(value) + 1;
7357         char *env = malloc(len);
7359         if (env &&
7360             string_nformat(env, len, NULL, "%s=%s", name, value) &&
7361             putenv(env) == 0)
7362                 return TRUE;
7363         free(env);
7364         return FALSE;
7367 static void
7368 set_work_tree(const char *value)
7370         char cwd[SIZEOF_STR];
7372         if (!getcwd(cwd, sizeof(cwd)))
7373                 die("Failed to get cwd path: %s", strerror(errno));
7374         if (chdir(opt_git_dir) < 0)
7375                 die("Failed to chdir(%s): %s", strerror(errno));
7376         if (!getcwd(opt_git_dir, sizeof(opt_git_dir)))
7377                 die("Failed to get git path: %s", strerror(errno));
7378         if (chdir(cwd) < 0)
7379                 die("Failed to chdir(%s): %s", cwd, strerror(errno));
7380         if (chdir(value) < 0)
7381                 die("Failed to chdir(%s): %s", value, strerror(errno));
7382         if (!getcwd(cwd, sizeof(cwd)))
7383                 die("Failed to get cwd path: %s", strerror(errno));
7384         if (!set_environment_variable("GIT_WORK_TREE", cwd))
7385                 die("Failed to set GIT_WORK_TREE to '%s'", cwd);
7386         if (!set_environment_variable("GIT_DIR", opt_git_dir))
7387                 die("Failed to set GIT_DIR to '%s'", opt_git_dir);
7388         opt_is_inside_work_tree = TRUE;
7391 static int
7392 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
7394         if (!strcmp(name, "i18n.commitencoding"))
7395                 string_ncopy(opt_encoding, value, valuelen);
7397         else if (!strcmp(name, "core.editor"))
7398                 string_ncopy(opt_editor, value, valuelen);
7400         else if (!strcmp(name, "core.worktree"))
7401                 set_work_tree(value);
7403         else if (!prefixcmp(name, "tig.color."))
7404                 set_repo_config_option(name + 10, value, option_color_command);
7406         else if (!prefixcmp(name, "tig.bind."))
7407                 set_repo_config_option(name + 9, value, option_bind_command);
7409         else if (!prefixcmp(name, "tig."))
7410                 set_repo_config_option(name + 4, value, option_set_command);
7412         else if (*opt_head && !prefixcmp(name, "branch.") &&
7413                  !strncmp(name + 7, opt_head, strlen(opt_head)))
7414                 set_remote_branch(name + 7 + strlen(opt_head), value, valuelen);
7416         return OK;
7419 static int
7420 load_git_config(void)
7422         const char *config_list_argv[] = { "git", GIT_CONFIG, "--list", NULL };
7424         return run_io_load(config_list_argv, "=", read_repo_config_option);
7427 static int
7428 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
7430         if (!opt_git_dir[0]) {
7431                 string_ncopy(opt_git_dir, name, namelen);
7433         } else if (opt_is_inside_work_tree == -1) {
7434                 /* This can be 3 different values depending on the
7435                  * version of git being used. If git-rev-parse does not
7436                  * understand --is-inside-work-tree it will simply echo
7437                  * the option else either "true" or "false" is printed.
7438                  * Default to true for the unknown case. */
7439                 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
7441         } else if (*name == '.') {
7442                 string_ncopy(opt_cdup, name, namelen);
7444         } else {
7445                 string_ncopy(opt_prefix, name, namelen);
7446         }
7448         return OK;
7451 static int
7452 load_repo_info(void)
7454         const char *rev_parse_argv[] = {
7455                 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
7456                         "--show-cdup", "--show-prefix", NULL
7457         };
7459         return run_io_load(rev_parse_argv, "=", read_repo_info);
7463 /*
7464  * Main
7465  */
7467 static const char usage[] =
7468 "tig " TIG_VERSION " (" __DATE__ ")\n"
7469 "\n"
7470 "Usage: tig        [options] [revs] [--] [paths]\n"
7471 "   or: tig show   [options] [revs] [--] [paths]\n"
7472 "   or: tig blame  [rev] path\n"
7473 "   or: tig status\n"
7474 "   or: tig <      [git command output]\n"
7475 "\n"
7476 "Options:\n"
7477 "  -v, --version   Show version and exit\n"
7478 "  -h, --help      Show help message and exit";
7480 static void __NORETURN
7481 quit(int sig)
7483         /* XXX: Restore tty modes and let the OS cleanup the rest! */
7484         if (cursed)
7485                 endwin();
7486         exit(0);
7489 static void __NORETURN
7490 die(const char *err, ...)
7492         va_list args;
7494         endwin();
7496         va_start(args, err);
7497         fputs("tig: ", stderr);
7498         vfprintf(stderr, err, args);
7499         fputs("\n", stderr);
7500         va_end(args);
7502         exit(1);
7505 static void
7506 warn(const char *msg, ...)
7508         va_list args;
7510         va_start(args, msg);
7511         fputs("tig warning: ", stderr);
7512         vfprintf(stderr, msg, args);
7513         fputs("\n", stderr);
7514         va_end(args);
7517 static enum request
7518 parse_options(int argc, const char *argv[])
7520         enum request request = REQ_VIEW_MAIN;
7521         const char *subcommand;
7522         bool seen_dashdash = FALSE;
7523         /* XXX: This is vulnerable to the user overriding options
7524          * required for the main view parser. */
7525         const char *custom_argv[SIZEOF_ARG] = {
7526                 "git", "log", "--no-color", "--pretty=raw", "--parents",
7527                         "--topo-order", NULL
7528         };
7529         int i, j = 6;
7531         if (!isatty(STDIN_FILENO)) {
7532                 io_open(&VIEW(REQ_VIEW_PAGER)->io, "");
7533                 return REQ_VIEW_PAGER;
7534         }
7536         if (argc <= 1)
7537                 return REQ_NONE;
7539         subcommand = argv[1];
7540         if (!strcmp(subcommand, "status")) {
7541                 if (argc > 2)
7542                         warn("ignoring arguments after `%s'", subcommand);
7543                 return REQ_VIEW_STATUS;
7545         } else if (!strcmp(subcommand, "blame")) {
7546                 if (argc <= 2 || argc > 4)
7547                         die("invalid number of options to blame\n\n%s", usage);
7549                 i = 2;
7550                 if (argc == 4) {
7551                         string_ncopy(opt_ref, argv[i], strlen(argv[i]));
7552                         i++;
7553                 }
7555                 string_ncopy(opt_file, argv[i], strlen(argv[i]));
7556                 return REQ_VIEW_BLAME;
7558         } else if (!strcmp(subcommand, "show")) {
7559                 request = REQ_VIEW_DIFF;
7561         } else {
7562                 subcommand = NULL;
7563         }
7565         if (subcommand) {
7566                 custom_argv[1] = subcommand;
7567                 j = 2;
7568         }
7570         for (i = 1 + !!subcommand; i < argc; i++) {
7571                 const char *opt = argv[i];
7573                 if (seen_dashdash || !strcmp(opt, "--")) {
7574                         seen_dashdash = TRUE;
7576                 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
7577                         printf("tig version %s\n", TIG_VERSION);
7578                         quit(0);
7580                 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
7581                         printf("%s\n", usage);
7582                         quit(0);
7583                 }
7585                 custom_argv[j++] = opt;
7586                 if (j >= ARRAY_SIZE(custom_argv))
7587                         die("command too long");
7588         }
7590         if (!prepare_update(VIEW(request), custom_argv, NULL, FORMAT_NONE))                                                                        
7591                 die("Failed to format arguments"); 
7593         return request;
7596 int
7597 main(int argc, const char *argv[])
7599         enum request request = parse_options(argc, argv);
7600         struct view *view;
7601         size_t i;
7603         signal(SIGINT, quit);
7604         signal(SIGPIPE, SIG_IGN);
7606         if (setlocale(LC_ALL, "")) {
7607                 char *codeset = nl_langinfo(CODESET);
7609                 string_ncopy(opt_codeset, codeset, strlen(codeset));
7610         }
7612         if (load_repo_info() == ERR)
7613                 die("Failed to load repo info.");
7615         if (load_options() == ERR)
7616                 die("Failed to load user config.");
7618         if (load_git_config() == ERR)
7619                 die("Failed to load repo config.");
7621         /* Require a git repository unless when running in pager mode. */
7622         if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
7623                 die("Not a git repository");
7625         if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
7626                 opt_utf8 = FALSE;
7628         if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
7629                 opt_iconv = iconv_open(opt_codeset, opt_encoding);
7630                 if (opt_iconv == ICONV_NONE)
7631                         die("Failed to initialize character set conversion");
7632         }
7634         if (load_refs() == ERR)
7635                 die("Failed to load refs.");
7637         foreach_view (view, i)
7638                 argv_from_env(view->ops->argv, view->cmd_env);
7640         init_display();
7642         if (request != REQ_NONE)
7643                 open_view(NULL, request, OPEN_PREPARED);
7644         request = request == REQ_NONE ? REQ_VIEW_MAIN : REQ_NONE;
7646         while (view_driver(display[current_view], request)) {
7647                 int key = get_input(0);
7649                 view = display[current_view];
7650                 request = get_keybinding(view->keymap, key);
7652                 /* Some low-level request handling. This keeps access to
7653                  * status_win restricted. */
7654                 switch (request) {
7655                 case REQ_PROMPT:
7656                 {
7657                         char *cmd = read_prompt(":");
7659                         if (cmd && isdigit(*cmd)) {
7660                                 int lineno = view->lineno + 1;
7662                                 if (parse_int(&lineno, cmd, 1, view->lines + 1) == OK) {
7663                                         select_view_line(view, lineno - 1);
7664                                         report("");
7665                                 } else {
7666                                         report("Unable to parse '%s' as a line number", cmd);
7667                                 }
7669                         } else if (cmd) {
7670                                 struct view *next = VIEW(REQ_VIEW_PAGER);
7671                                 const char *argv[SIZEOF_ARG] = { "git" };
7672                                 int argc = 1;
7674                                 /* When running random commands, initially show the
7675                                  * command in the title. However, it maybe later be
7676                                  * overwritten if a commit line is selected. */
7677                                 string_ncopy(next->ref, cmd, strlen(cmd));
7679                                 if (!argv_from_string(argv, &argc, cmd)) {
7680                                         report("Too many arguments");
7681                                 } else if (!prepare_update(next, argv, NULL, FORMAT_DASH)) {
7682                                         report("Failed to format command");
7683                                 } else {
7684                                         open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
7685                                 }
7686                         }
7688                         request = REQ_NONE;
7689                         break;
7690                 }
7691                 case REQ_SEARCH:
7692                 case REQ_SEARCH_BACK:
7693                 {
7694                         const char *prompt = request == REQ_SEARCH ? "/" : "?";
7695                         char *search = read_prompt(prompt);
7697                         if (search)
7698                                 string_ncopy(opt_search, search, strlen(search));
7699                         else if (*opt_search)
7700                                 request = request == REQ_SEARCH ?
7701                                         REQ_FIND_NEXT :
7702                                         REQ_FIND_PREV;
7703                         else
7704                                 request = REQ_NONE;
7705                         break;
7706                 }
7707                 default:
7708                         break;
7709                 }
7710         }
7712         quit(0);
7714         return 0;