Code

Encode everything internally as UTF-8
[tig.git] / tig.c
1 /* Copyright (c) 2006-2009 Jonas Fonseca <fonseca@diku.dk>
2  *
3  * This program is free software; you can redistribute it and/or
4  * modify it under the terms of the GNU General Public License as
5  * published by the Free Software Foundation; either version 2 of
6  * the License, or (at your option) any later version.
7  *
8  * This program is distributed in the hope that it will be useful,
9  * but WITHOUT ANY WARRANTY; without even the implied warranty of
10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11  * GNU General Public License for more details.
12  */
14 #ifdef HAVE_CONFIG_H
15 #include "config.h"
16 #endif
18 #ifndef TIG_VERSION
19 #define TIG_VERSION "unknown-version"
20 #endif
22 #ifndef DEBUG
23 #define NDEBUG
24 #endif
26 #include <assert.h>
27 #include <errno.h>
28 #include <ctype.h>
29 #include <signal.h>
30 #include <stdarg.h>
31 #include <stdio.h>
32 #include <stdlib.h>
33 #include <string.h>
34 #include <sys/types.h>
35 #include <sys/wait.h>
36 #include <sys/stat.h>
37 #include <sys/select.h>
38 #include <unistd.h>
39 #include <sys/time.h>
40 #include <time.h>
41 #include <fcntl.h>
43 #include <regex.h>
45 #include <locale.h>
46 #include <langinfo.h>
47 #include <iconv.h>
49 /* ncurses(3): Must be defined to have extended wide-character functions. */
50 #define _XOPEN_SOURCE_EXTENDED
52 #ifdef HAVE_NCURSESW_NCURSES_H
53 #include <ncursesw/ncurses.h>
54 #else
55 #ifdef HAVE_NCURSES_NCURSES_H
56 #include <ncurses/ncurses.h>
57 #else
58 #include <ncurses.h>
59 #endif
60 #endif
62 #if __GNUC__ >= 3
63 #define __NORETURN __attribute__((__noreturn__))
64 #else
65 #define __NORETURN
66 #endif
68 static void __NORETURN die(const char *err, ...);
69 static void warn(const char *msg, ...);
70 static void report(const char *msg, ...);
71 static void set_nonblocking_input(bool loading);
72 static size_t utf8_length(const char **string, size_t col, int *width, size_t max_width, int *trimmed, bool reserve);
74 #define ABS(x)          ((x) >= 0  ? (x) : -(x))
75 #define MIN(x, y)       ((x) < (y) ? (x) :  (y))
76 #define MAX(x, y)       ((x) > (y) ? (x) :  (y))
78 #define ARRAY_SIZE(x)   (sizeof(x) / sizeof(x[0]))
79 #define STRING_SIZE(x)  (sizeof(x) - 1)
81 #define SIZEOF_STR      1024    /* Default string size. */
82 #define SIZEOF_REF      256     /* Size of symbolic or SHA1 ID. */
83 #define SIZEOF_REV      41      /* Holds a SHA-1 and an ending NUL. */
84 #define SIZEOF_ARG      32      /* Default argument array size. */
86 /* Revision graph */
88 #define REVGRAPH_INIT   'I'
89 #define REVGRAPH_MERGE  'M'
90 #define REVGRAPH_BRANCH '+'
91 #define REVGRAPH_COMMIT '*'
92 #define REVGRAPH_BOUND  '^'
94 #define SIZEOF_REVGRAPH 19      /* Size of revision ancestry graphics. */
96 /* This color name can be used to refer to the default term colors. */
97 #define COLOR_DEFAULT   (-1)
99 #define ICONV_NONE      ((iconv_t) -1)
100 #ifndef ICONV_CONST
101 #define ICONV_CONST     /* nothing */
102 #endif
104 /* The format and size of the date column in the main view. */
105 #define DATE_FORMAT     "%Y-%m-%d %H:%M"
106 #define DATE_COLS       STRING_SIZE("2006-04-29 14:21 ")
107 #define DATE_SHORT_COLS STRING_SIZE("2006-04-29 ")
109 #define ID_COLS         8
111 #define MIN_VIEW_HEIGHT 4
113 #define NULL_ID         "0000000000000000000000000000000000000000"
115 #define S_ISGITLINK(mode) (((mode) & S_IFMT) == 0160000)
117 /* Some ASCII-shorthands fitted into the ncurses namespace. */
118 #define KEY_TAB         '\t'
119 #define KEY_RETURN      '\r'
120 #define KEY_ESC         27
123 struct ref {
124         char id[SIZEOF_REV];    /* Commit SHA1 ID */
125         unsigned int head:1;    /* Is it the current HEAD? */
126         unsigned int tag:1;     /* Is it a tag? */
127         unsigned int ltag:1;    /* If so, is the tag local? */
128         unsigned int remote:1;  /* Is it a remote ref? */
129         unsigned int tracked:1; /* Is it the remote for the current HEAD? */
130         char name[1];           /* Ref name; tag or head names are shortened. */
131 };
133 struct ref_list {
134         char id[SIZEOF_REV];    /* Commit SHA1 ID */
135         size_t size;            /* Number of refs. */
136         struct ref **refs;      /* References for this ID. */
137 };
139 static struct ref_list *get_ref_list(const char *id);
140 static void foreach_ref(bool (*visitor)(void *data, const struct ref *ref), void *data);
141 static int load_refs(void);
143 enum format_flags {
144         FORMAT_ALL,             /* Perform replacement in all arguments. */
145         FORMAT_DASH,            /* Perform replacement up until "--". */
146         FORMAT_NONE             /* No replacement should be performed. */
147 };
149 static bool format_argv(const char *dst[], const char *src[], enum format_flags flags);
151 enum input_status {
152         INPUT_OK,
153         INPUT_SKIP,
154         INPUT_STOP,
155         INPUT_CANCEL
156 };
158 typedef enum input_status (*input_handler)(void *data, char *buf, int c);
160 static char *prompt_input(const char *prompt, input_handler handler, void *data);
161 static bool prompt_yesno(const char *prompt);
163 struct menu_item {
164         int hotkey;
165         const char *text;
166         void *data;
167 };
169 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected);
171 /*
172  * Allocation helpers ... Entering macro hell to never be seen again.
173  */
175 #define DEFINE_ALLOCATOR(name, type, chunk_size)                                \
176 static type *                                                                   \
177 name(type **mem, size_t size, size_t increase)                                  \
178 {                                                                               \
179         size_t num_chunks = (size + chunk_size - 1) / chunk_size;               \
180         size_t num_chunks_new = (size + increase + chunk_size - 1) / chunk_size;\
181         type *tmp = *mem;                                                       \
182                                                                                 \
183         if (mem == NULL || num_chunks != num_chunks_new) {                      \
184                 tmp = realloc(tmp, num_chunks_new * chunk_size * sizeof(type)); \
185                 if (tmp)                                                        \
186                         *mem = tmp;                                             \
187         }                                                                       \
188                                                                                 \
189         return tmp;                                                             \
192 /*
193  * String helpers
194  */
196 static inline void
197 string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t srclen)
199         if (srclen > dstlen - 1)
200                 srclen = dstlen - 1;
202         strncpy(dst, src, srclen);
203         dst[srclen] = 0;
206 /* Shorthands for safely copying into a fixed buffer. */
208 #define string_copy(dst, src) \
209         string_ncopy_do(dst, sizeof(dst), src, sizeof(src))
211 #define string_ncopy(dst, src, srclen) \
212         string_ncopy_do(dst, sizeof(dst), src, srclen)
214 #define string_copy_rev(dst, src) \
215         string_ncopy_do(dst, SIZEOF_REV, src, SIZEOF_REV - 1)
217 #define string_add(dst, from, src) \
218         string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src))
220 static void
221 string_expand(char *dst, size_t dstlen, const char *src, int tabsize)
223         size_t size, pos;
225         for (size = pos = 0; size < dstlen - 1 && src[pos]; pos++) {
226                 if (src[pos] == '\t') {
227                         size_t expanded = tabsize - (size % tabsize);
229                         if (expanded + size >= dstlen - 1)
230                                 expanded = dstlen - size - 1;
231                         memcpy(dst + size, "        ", expanded);
232                         size += expanded;
233                 } else {
234                         dst[size++] = src[pos];
235                 }
236         }
238         dst[size] = 0;
241 static char *
242 chomp_string(char *name)
244         int namelen;
246         while (isspace(*name))
247                 name++;
249         namelen = strlen(name) - 1;
250         while (namelen > 0 && isspace(name[namelen]))
251                 name[namelen--] = 0;
253         return name;
256 static bool
257 string_nformat(char *buf, size_t bufsize, size_t *bufpos, const char *fmt, ...)
259         va_list args;
260         size_t pos = bufpos ? *bufpos : 0;
262         va_start(args, fmt);
263         pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
264         va_end(args);
266         if (bufpos)
267                 *bufpos = pos;
269         return pos >= bufsize ? FALSE : TRUE;
272 #define string_format(buf, fmt, args...) \
273         string_nformat(buf, sizeof(buf), NULL, fmt, args)
275 #define string_format_from(buf, from, fmt, args...) \
276         string_nformat(buf, sizeof(buf), from, fmt, args)
278 static int
279 string_enum_compare(const char *str1, const char *str2, int len)
281         size_t i;
283 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
285         /* Diff-Header == DIFF_HEADER */
286         for (i = 0; i < len; i++) {
287                 if (toupper(str1[i]) == toupper(str2[i]))
288                         continue;
290                 if (string_enum_sep(str1[i]) &&
291                     string_enum_sep(str2[i]))
292                         continue;
294                 return str1[i] - str2[i];
295         }
297         return 0;
300 struct enum_map {
301         const char *name;
302         int namelen;
303         int value;
304 };
306 #define ENUM_MAP(name, value) { name, STRING_SIZE(name), value }
308 static bool
309 map_enum_do(const struct enum_map *map, size_t map_size, int *value, const char *name)
311         size_t namelen = strlen(name);
312         int i;
314         for (i = 0; i < map_size; i++)
315                 if (namelen == map[i].namelen &&
316                     !string_enum_compare(name, map[i].name, namelen)) {
317                         *value = map[i].value;
318                         return TRUE;
319                 }
321         return FALSE;
324 #define map_enum(attr, map, name) \
325         map_enum_do(map, ARRAY_SIZE(map), attr, name)
327 #define prefixcmp(str1, str2) \
328         strncmp(str1, str2, STRING_SIZE(str2))
330 static inline int
331 suffixcmp(const char *str, int slen, const char *suffix)
333         size_t len = slen >= 0 ? slen : strlen(str);
334         size_t suffixlen = strlen(suffix);
336         return suffixlen < len ? strcmp(str + len - suffixlen, suffix) : -1;
340 /*
341  * What value of "tz" was in effect back then at "time" in the
342  * local timezone?
343  */
344 static int local_tzoffset(time_t time)
346         time_t t, t_local;
347         struct tm tm;
348         int offset, eastwest; 
350         t = time;
351         localtime_r(&t, &tm);
352         t_local = mktime(&tm);
354         if (t_local < t) {
355                 eastwest = -1;
356                 offset = t - t_local;
357         } else {
358                 eastwest = 1;
359                 offset = t_local - t;
360         }
361         offset /= 60; /* in minutes */
362         offset = (offset % 60) + ((offset / 60) * 100);
363         return offset * eastwest;
366 enum date {
367         DATE_NONE = 0,
368         DATE_DEFAULT,
369         DATE_RELATIVE,
370         DATE_SHORT
371 };
373 static char *
374 string_date(const time_t *time, enum date date)
376         static char buf[DATE_COLS + 1];
377         static const struct enum_map reldate[] = {
378                 { "second", 1,                  60 * 2 },
379                 { "minute", 60,                 60 * 60 * 2 },
380                 { "hour",   60 * 60,            60 * 60 * 24 * 2 },
381                 { "day",    60 * 60 * 24,       60 * 60 * 24 * 7 * 2 },
382                 { "week",   60 * 60 * 24 * 7,   60 * 60 * 24 * 7 * 5 },
383                 { "month",  60 * 60 * 24 * 30,  60 * 60 * 24 * 30 * 12 },
384         };
385         struct tm tm;
387         if (date == DATE_RELATIVE) {
388                 struct timeval now;
389                 time_t date = *time + local_tzoffset(*time);
390                 time_t seconds;
391                 int i;
393                 gettimeofday(&now, NULL);
394                 seconds = now.tv_sec < date ? date - now.tv_sec : now.tv_sec - date;
395                 for (i = 0; i < ARRAY_SIZE(reldate); i++) {
396                         if (seconds >= reldate[i].value)
397                                 continue;
399                         seconds /= reldate[i].namelen;
400                         if (!string_format(buf, "%ld %s%s %s",
401                                            seconds, reldate[i].name,
402                                            seconds > 1 ? "s" : "",
403                                            now.tv_sec >= date ? "ago" : "ahead"))
404                                 break;
405                         return buf;
406                 }
407         }
409         gmtime_r(time, &tm);
410         return strftime(buf, sizeof(buf), DATE_FORMAT, &tm) ? buf : NULL;
414 static bool
415 argv_from_string(const char *argv[SIZEOF_ARG], int *argc, char *cmd)
417         int valuelen;
419         while (*cmd && *argc < SIZEOF_ARG && (valuelen = strcspn(cmd, " \t"))) {
420                 bool advance = cmd[valuelen] != 0;
422                 cmd[valuelen] = 0;
423                 argv[(*argc)++] = chomp_string(cmd);
424                 cmd = chomp_string(cmd + valuelen + advance);
425         }
427         if (*argc < SIZEOF_ARG)
428                 argv[*argc] = NULL;
429         return *argc < SIZEOF_ARG;
432 static void
433 argv_from_env(const char **argv, const char *name)
435         char *env = argv ? getenv(name) : NULL;
436         int argc = 0;
438         if (env && *env)
439                 env = strdup(env);
440         if (env && !argv_from_string(argv, &argc, env))
441                 die("Too many arguments in the `%s` environment variable", name);
445 /*
446  * Executing external commands.
447  */
449 enum io_type {
450         IO_FD,                  /* File descriptor based IO. */
451         IO_BG,                  /* Execute command in the background. */
452         IO_FG,                  /* Execute command with same std{in,out,err}. */
453         IO_RD,                  /* Read only fork+exec IO. */
454         IO_WR,                  /* Write only fork+exec IO. */
455         IO_AP,                  /* Append fork+exec output to file. */
456 };
458 struct io {
459         enum io_type type;      /* The requested type of pipe. */
460         const char *dir;        /* Directory from which to execute. */
461         pid_t pid;              /* Pipe for reading or writing. */
462         int pipe;               /* Pipe end for reading or writing. */
463         int error;              /* Error status. */
464         const char *argv[SIZEOF_ARG];   /* Shell command arguments. */
465         char *buf;              /* Read buffer. */
466         size_t bufalloc;        /* Allocated buffer size. */
467         size_t bufsize;         /* Buffer content size. */
468         char *bufpos;           /* Current buffer position. */
469         unsigned int eof:1;     /* Has end of file been reached. */
470 };
472 static void
473 reset_io(struct io *io)
475         io->pipe = -1;
476         io->pid = 0;
477         io->buf = io->bufpos = NULL;
478         io->bufalloc = io->bufsize = 0;
479         io->error = 0;
480         io->eof = 0;
483 static void
484 init_io(struct io *io, const char *dir, enum io_type type)
486         reset_io(io);
487         io->type = type;
488         io->dir = dir;
491 static bool
492 init_io_rd(struct io *io, const char *argv[], const char *dir,
493                 enum format_flags flags)
495         init_io(io, dir, IO_RD);
496         return format_argv(io->argv, argv, flags);
499 static bool
500 io_open(struct io *io, const char *fmt, ...)
502         char name[SIZEOF_STR] = "";
503         bool fits;
504         va_list args;
506         init_io(io, NULL, IO_FD);
508         va_start(args, fmt);
509         fits = vsnprintf(name, sizeof(name), fmt, args) < sizeof(name);
510         va_end(args);
512         if (!fits) {
513                 io->error = ENAMETOOLONG;
514                 return FALSE;
515         }
516         io->pipe = *name ? open(name, O_RDONLY) : STDIN_FILENO;
517         if (io->pipe == -1)
518                 io->error = errno;
519         return io->pipe != -1;
522 static bool
523 kill_io(struct io *io)
525         return io->pid == 0 || kill(io->pid, SIGKILL) != -1;
528 static bool
529 done_io(struct io *io)
531         pid_t pid = io->pid;
533         if (io->pipe != -1)
534                 close(io->pipe);
535         free(io->buf);
536         reset_io(io);
538         while (pid > 0) {
539                 int status;
540                 pid_t waiting = waitpid(pid, &status, 0);
542                 if (waiting < 0) {
543                         if (errno == EINTR)
544                                 continue;
545                         report("waitpid failed (%s)", strerror(errno));
546                         return FALSE;
547                 }
549                 return waiting == pid &&
550                        !WIFSIGNALED(status) &&
551                        WIFEXITED(status) &&
552                        !WEXITSTATUS(status);
553         }
555         return TRUE;
558 static bool
559 start_io(struct io *io)
561         int pipefds[2] = { -1, -1 };
563         if (io->type == IO_FD)
564                 return TRUE;
566         if ((io->type == IO_RD || io->type == IO_WR) &&
567             pipe(pipefds) < 0)
568                 return FALSE;
569         else if (io->type == IO_AP)
570                 pipefds[1] = io->pipe;
572         if ((io->pid = fork())) {
573                 if (pipefds[!(io->type == IO_WR)] != -1)
574                         close(pipefds[!(io->type == IO_WR)]);
575                 if (io->pid != -1) {
576                         io->pipe = pipefds[!!(io->type == IO_WR)];
577                         return TRUE;
578                 }
580         } else {
581                 if (io->type != IO_FG) {
582                         int devnull = open("/dev/null", O_RDWR);
583                         int readfd  = io->type == IO_WR ? pipefds[0] : devnull;
584                         int writefd = (io->type == IO_RD || io->type == IO_AP)
585                                                         ? pipefds[1] : devnull;
587                         dup2(readfd,  STDIN_FILENO);
588                         dup2(writefd, STDOUT_FILENO);
589                         dup2(devnull, STDERR_FILENO);
591                         close(devnull);
592                         if (pipefds[0] != -1)
593                                 close(pipefds[0]);
594                         if (pipefds[1] != -1)
595                                 close(pipefds[1]);
596                 }
598                 if (io->dir && *io->dir && chdir(io->dir) == -1)
599                         die("Failed to change directory: %s", strerror(errno));
601                 execvp(io->argv[0], (char *const*) io->argv);
602                 die("Failed to execute program: %s", strerror(errno));
603         }
605         if (pipefds[!!(io->type == IO_WR)] != -1)
606                 close(pipefds[!!(io->type == IO_WR)]);
607         return FALSE;
610 static bool
611 run_io(struct io *io, const char **argv, const char *dir, enum io_type type)
613         init_io(io, dir, type);
614         if (!format_argv(io->argv, argv, FORMAT_NONE))
615                 return FALSE;
616         return start_io(io);
619 static int
620 run_io_do(struct io *io)
622         return start_io(io) && done_io(io);
625 static int
626 run_io_bg(const char **argv)
628         struct io io = {};
630         init_io(&io, NULL, IO_BG);
631         if (!format_argv(io.argv, argv, FORMAT_NONE))
632                 return FALSE;
633         return run_io_do(&io);
636 static bool
637 run_io_fg(const char **argv, const char *dir)
639         struct io io = {};
641         init_io(&io, dir, IO_FG);
642         if (!format_argv(io.argv, argv, FORMAT_NONE))
643                 return FALSE;
644         return run_io_do(&io);
647 static bool
648 run_io_append(const char **argv, enum format_flags flags, int fd)
650         struct io io = {};
652         init_io(&io, NULL, IO_AP);
653         io.pipe = fd;
654         if (format_argv(io.argv, argv, flags))
655                 return run_io_do(&io);
656         close(fd);
657         return FALSE;
660 static bool
661 run_io_rd(struct io *io, const char **argv, const char *dir, enum format_flags flags)
663         return init_io_rd(io, argv, dir, flags) && start_io(io);
666 static bool
667 io_eof(struct io *io)
669         return io->eof;
672 static int
673 io_error(struct io *io)
675         return io->error;
678 static char *
679 io_strerror(struct io *io)
681         return strerror(io->error);
684 static bool
685 io_can_read(struct io *io)
687         struct timeval tv = { 0, 500 };
688         fd_set fds;
690         FD_ZERO(&fds);
691         FD_SET(io->pipe, &fds);
693         return select(io->pipe + 1, &fds, NULL, NULL, &tv) > 0;
696 static ssize_t
697 io_read(struct io *io, void *buf, size_t bufsize)
699         do {
700                 ssize_t readsize = read(io->pipe, buf, bufsize);
702                 if (readsize < 0 && (errno == EAGAIN || errno == EINTR))
703                         continue;
704                 else if (readsize == -1)
705                         io->error = errno;
706                 else if (readsize == 0)
707                         io->eof = 1;
708                 return readsize;
709         } while (1);
712 DEFINE_ALLOCATOR(realloc_io_buf, char, BUFSIZ)
714 static char *
715 io_get(struct io *io, int c, bool can_read)
717         char *eol;
718         ssize_t readsize;
720         while (TRUE) {
721                 if (io->bufsize > 0) {
722                         eol = memchr(io->bufpos, c, io->bufsize);
723                         if (eol) {
724                                 char *line = io->bufpos;
726                                 *eol = 0;
727                                 io->bufpos = eol + 1;
728                                 io->bufsize -= io->bufpos - line;
729                                 return line;
730                         }
731                 }
733                 if (io_eof(io)) {
734                         if (io->bufsize) {
735                                 io->bufpos[io->bufsize] = 0;
736                                 io->bufsize = 0;
737                                 return io->bufpos;
738                         }
739                         return NULL;
740                 }
742                 if (!can_read)
743                         return NULL;
745                 if (io->bufsize > 0 && io->bufpos > io->buf)
746                         memmove(io->buf, io->bufpos, io->bufsize);
748                 if (io->bufalloc == io->bufsize) {
749                         if (!realloc_io_buf(&io->buf, io->bufalloc, BUFSIZ))
750                                 return NULL;
751                         io->bufalloc += BUFSIZ;
752                 }
754                 io->bufpos = io->buf;
755                 readsize = io_read(io, io->buf + io->bufsize, io->bufalloc - io->bufsize);
756                 if (io_error(io))
757                         return NULL;
758                 io->bufsize += readsize;
759         }
762 static bool
763 io_write(struct io *io, const void *buf, size_t bufsize)
765         size_t written = 0;
767         while (!io_error(io) && written < bufsize) {
768                 ssize_t size;
770                 size = write(io->pipe, buf + written, bufsize - written);
771                 if (size < 0 && (errno == EAGAIN || errno == EINTR))
772                         continue;
773                 else if (size == -1)
774                         io->error = errno;
775                 else
776                         written += size;
777         }
779         return written == bufsize;
782 static bool
783 io_read_buf(struct io *io, char buf[], size_t bufsize)
785         char *result = io_get(io, '\n', TRUE);
787         if (result) {
788                 result = chomp_string(result);
789                 string_ncopy_do(buf, bufsize, result, strlen(result));
790         }
792         return done_io(io) && result;
795 static bool
796 run_io_buf(const char **argv, char buf[], size_t bufsize)
798         struct io io = {};
800         return run_io_rd(&io, argv, NULL, FORMAT_NONE)
801             && io_read_buf(&io, buf, bufsize);
804 static int
805 io_load(struct io *io, const char *separators,
806         int (*read_property)(char *, size_t, char *, size_t))
808         char *name;
809         int state = OK;
811         if (!start_io(io))
812                 return ERR;
814         while (state == OK && (name = io_get(io, '\n', TRUE))) {
815                 char *value;
816                 size_t namelen;
817                 size_t valuelen;
819                 name = chomp_string(name);
820                 namelen = strcspn(name, separators);
822                 if (name[namelen]) {
823                         name[namelen] = 0;
824                         value = chomp_string(name + namelen + 1);
825                         valuelen = strlen(value);
827                 } else {
828                         value = "";
829                         valuelen = 0;
830                 }
832                 state = read_property(name, namelen, value, valuelen);
833         }
835         if (state != ERR && io_error(io))
836                 state = ERR;
837         done_io(io);
839         return state;
842 static int
843 run_io_load(const char **argv, const char *separators,
844             int (*read_property)(char *, size_t, char *, size_t))
846         struct io io = {};
848         return init_io_rd(&io, argv, NULL, FORMAT_NONE)
849                 ? io_load(&io, separators, read_property) : ERR;
853 /*
854  * User requests
855  */
857 #define REQ_INFO \
858         /* XXX: Keep the view request first and in sync with views[]. */ \
859         REQ_GROUP("View switching") \
860         REQ_(VIEW_MAIN,         "Show main view"), \
861         REQ_(VIEW_DIFF,         "Show diff view"), \
862         REQ_(VIEW_LOG,          "Show log view"), \
863         REQ_(VIEW_TREE,         "Show tree view"), \
864         REQ_(VIEW_BLOB,         "Show blob view"), \
865         REQ_(VIEW_BLAME,        "Show blame view"), \
866         REQ_(VIEW_BRANCH,       "Show branch view"), \
867         REQ_(VIEW_HELP,         "Show help page"), \
868         REQ_(VIEW_PAGER,        "Show pager view"), \
869         REQ_(VIEW_STATUS,       "Show status view"), \
870         REQ_(VIEW_STAGE,        "Show stage view"), \
871         \
872         REQ_GROUP("View manipulation") \
873         REQ_(ENTER,             "Enter current line and scroll"), \
874         REQ_(NEXT,              "Move to next"), \
875         REQ_(PREVIOUS,          "Move to previous"), \
876         REQ_(PARENT,            "Move to parent"), \
877         REQ_(VIEW_NEXT,         "Move focus to next view"), \
878         REQ_(REFRESH,           "Reload and refresh"), \
879         REQ_(MAXIMIZE,          "Maximize the current view"), \
880         REQ_(VIEW_CLOSE,        "Close the current view"), \
881         REQ_(QUIT,              "Close all views and quit"), \
882         \
883         REQ_GROUP("View specific requests") \
884         REQ_(STATUS_UPDATE,     "Update file status"), \
885         REQ_(STATUS_REVERT,     "Revert file changes"), \
886         REQ_(STATUS_MERGE,      "Merge file using external tool"), \
887         REQ_(STAGE_NEXT,        "Find next chunk to stage"), \
888         \
889         REQ_GROUP("Cursor navigation") \
890         REQ_(MOVE_UP,           "Move cursor one line up"), \
891         REQ_(MOVE_DOWN,         "Move cursor one line down"), \
892         REQ_(MOVE_PAGE_DOWN,    "Move cursor one page down"), \
893         REQ_(MOVE_PAGE_UP,      "Move cursor one page up"), \
894         REQ_(MOVE_FIRST_LINE,   "Move cursor to first line"), \
895         REQ_(MOVE_LAST_LINE,    "Move cursor to last line"), \
896         \
897         REQ_GROUP("Scrolling") \
898         REQ_(SCROLL_LEFT,       "Scroll two columns left"), \
899         REQ_(SCROLL_RIGHT,      "Scroll two columns right"), \
900         REQ_(SCROLL_LINE_UP,    "Scroll one line up"), \
901         REQ_(SCROLL_LINE_DOWN,  "Scroll one line down"), \
902         REQ_(SCROLL_PAGE_UP,    "Scroll one page up"), \
903         REQ_(SCROLL_PAGE_DOWN,  "Scroll one page down"), \
904         \
905         REQ_GROUP("Searching") \
906         REQ_(SEARCH,            "Search the view"), \
907         REQ_(SEARCH_BACK,       "Search backwards in the view"), \
908         REQ_(FIND_NEXT,         "Find next search match"), \
909         REQ_(FIND_PREV,         "Find previous search match"), \
910         \
911         REQ_GROUP("Option manipulation") \
912         REQ_(OPTIONS,           "Open option menu"), \
913         REQ_(TOGGLE_LINENO,     "Toggle line numbers"), \
914         REQ_(TOGGLE_DATE,       "Toggle date display"), \
915         REQ_(TOGGLE_DATE_SHORT, "Toggle short (date-only) dates"), \
916         REQ_(TOGGLE_AUTHOR,     "Toggle author display"), \
917         REQ_(TOGGLE_REV_GRAPH,  "Toggle revision graph visualization"), \
918         REQ_(TOGGLE_REFS,       "Toggle reference display (tags/branches)"), \
919         REQ_(TOGGLE_SORT_ORDER, "Toggle ascending/descending sort order"), \
920         REQ_(TOGGLE_SORT_FIELD, "Toggle field to sort by"), \
921         \
922         REQ_GROUP("Misc") \
923         REQ_(PROMPT,            "Bring up the prompt"), \
924         REQ_(SCREEN_REDRAW,     "Redraw the screen"), \
925         REQ_(SHOW_VERSION,      "Show version information"), \
926         REQ_(STOP_LOADING,      "Stop all loading views"), \
927         REQ_(EDIT,              "Open in editor"), \
928         REQ_(NONE,              "Do nothing")
931 /* User action requests. */
932 enum request {
933 #define REQ_GROUP(help)
934 #define REQ_(req, help) REQ_##req
936         /* Offset all requests to avoid conflicts with ncurses getch values. */
937         REQ_OFFSET = KEY_MAX + 1,
938         REQ_INFO
940 #undef  REQ_GROUP
941 #undef  REQ_
942 };
944 struct request_info {
945         enum request request;
946         const char *name;
947         int namelen;
948         const char *help;
949 };
951 static const struct request_info req_info[] = {
952 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
953 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
954         REQ_INFO
955 #undef  REQ_GROUP
956 #undef  REQ_
957 };
959 static enum request
960 get_request(const char *name)
962         int namelen = strlen(name);
963         int i;
965         for (i = 0; i < ARRAY_SIZE(req_info); i++)
966                 if (req_info[i].namelen == namelen &&
967                     !string_enum_compare(req_info[i].name, name, namelen))
968                         return req_info[i].request;
970         return REQ_NONE;
974 /*
975  * Options
976  */
978 /* Option and state variables. */
979 static enum date opt_date               = DATE_DEFAULT;
980 static bool opt_author                  = TRUE;
981 static bool opt_line_number             = FALSE;
982 static bool opt_line_graphics           = TRUE;
983 static bool opt_rev_graph               = FALSE;
984 static bool opt_show_refs               = TRUE;
985 static int opt_num_interval             = 5;
986 static double opt_hscroll               = 0.50;
987 static double opt_scale_split_view      = 2.0 / 3.0;
988 static int opt_tab_size                 = 8;
989 static int opt_author_cols              = 19;
990 static char opt_path[SIZEOF_STR]        = "";
991 static char opt_file[SIZEOF_STR]        = "";
992 static char opt_ref[SIZEOF_REF]         = "";
993 static char opt_head[SIZEOF_REF]        = "";
994 static char opt_head_rev[SIZEOF_REV]    = "";
995 static char opt_remote[SIZEOF_REF]      = "";
996 static char opt_encoding[20]            = "UTF-8";
997 static char opt_codeset[20]             = "UTF-8";
998 static iconv_t opt_iconv_in             = ICONV_NONE;
999 static iconv_t opt_iconv_out            = ICONV_NONE;
1000 static char opt_search[SIZEOF_STR]      = "";
1001 static char opt_cdup[SIZEOF_STR]        = "";
1002 static char opt_prefix[SIZEOF_STR]      = "";
1003 static char opt_git_dir[SIZEOF_STR]     = "";
1004 static signed char opt_is_inside_work_tree      = -1; /* set to TRUE or FALSE */
1005 static char opt_editor[SIZEOF_STR]      = "";
1006 static FILE *opt_tty                    = NULL;
1008 #define is_initial_commit()     (!*opt_head_rev)
1009 #define is_head_commit(rev)     (!strcmp((rev), "HEAD") || !strcmp(opt_head_rev, (rev)))
1010 #define mkdate(time)            string_date(time, opt_date)
1013 /*
1014  * Line-oriented content detection.
1015  */
1017 #define LINE_INFO \
1018 LINE(DIFF_HEADER,  "diff --git ",       COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1019 LINE(DIFF_CHUNK,   "@@",                COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1020 LINE(DIFF_ADD,     "+",                 COLOR_GREEN,    COLOR_DEFAULT,  0), \
1021 LINE(DIFF_DEL,     "-",                 COLOR_RED,      COLOR_DEFAULT,  0), \
1022 LINE(DIFF_INDEX,        "index ",         COLOR_BLUE,   COLOR_DEFAULT,  0), \
1023 LINE(DIFF_OLDMODE,      "old file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
1024 LINE(DIFF_NEWMODE,      "new file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
1025 LINE(DIFF_COPY_FROM,    "copy from",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
1026 LINE(DIFF_COPY_TO,      "copy to",        COLOR_YELLOW, COLOR_DEFAULT,  0), \
1027 LINE(DIFF_RENAME_FROM,  "rename from",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
1028 LINE(DIFF_RENAME_TO,    "rename to",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
1029 LINE(DIFF_SIMILARITY,   "similarity ",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
1030 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
1031 LINE(DIFF_TREE,         "diff-tree ",     COLOR_BLUE,   COLOR_DEFAULT,  0), \
1032 LINE(PP_AUTHOR,    "Author: ",          COLOR_CYAN,     COLOR_DEFAULT,  0), \
1033 LINE(PP_COMMIT,    "Commit: ",          COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1034 LINE(PP_MERGE,     "Merge: ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
1035 LINE(PP_DATE,      "Date:   ",          COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1036 LINE(PP_ADATE,     "AuthorDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1037 LINE(PP_CDATE,     "CommitDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1038 LINE(PP_REFS,      "Refs: ",            COLOR_RED,      COLOR_DEFAULT,  0), \
1039 LINE(COMMIT,       "commit ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
1040 LINE(PARENT,       "parent ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
1041 LINE(TREE,         "tree ",             COLOR_BLUE,     COLOR_DEFAULT,  0), \
1042 LINE(AUTHOR,       "author ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
1043 LINE(COMMITTER,    "committer ",        COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1044 LINE(SIGNOFF,      "    Signed-off-by", COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1045 LINE(ACKED,        "    Acked-by",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1046 LINE(DEFAULT,      "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
1047 LINE(CURSOR,       "",                  COLOR_WHITE,    COLOR_GREEN,    A_BOLD), \
1048 LINE(STATUS,       "",                  COLOR_GREEN,    COLOR_DEFAULT,  0), \
1049 LINE(DELIMITER,    "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1050 LINE(DATE,         "",                  COLOR_BLUE,     COLOR_DEFAULT,  0), \
1051 LINE(MODE,         "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1052 LINE(LINE_NUMBER,  "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1053 LINE(TITLE_BLUR,   "",                  COLOR_WHITE,    COLOR_BLUE,     0), \
1054 LINE(TITLE_FOCUS,  "",                  COLOR_WHITE,    COLOR_BLUE,     A_BOLD), \
1055 LINE(MAIN_COMMIT,  "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
1056 LINE(MAIN_TAG,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  A_BOLD), \
1057 LINE(MAIN_LOCAL_TAG,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1058 LINE(MAIN_REMOTE,  "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1059 LINE(MAIN_TRACKED, "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_BOLD), \
1060 LINE(MAIN_REF,     "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1061 LINE(MAIN_HEAD,    "",                  COLOR_CYAN,     COLOR_DEFAULT,  A_BOLD), \
1062 LINE(MAIN_REVGRAPH,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1063 LINE(TREE_HEAD,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_BOLD), \
1064 LINE(TREE_DIR,     "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_NORMAL), \
1065 LINE(TREE_FILE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
1066 LINE(STAT_HEAD,    "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1067 LINE(STAT_SECTION, "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1068 LINE(STAT_NONE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
1069 LINE(STAT_STAGED,  "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1070 LINE(STAT_UNSTAGED,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1071 LINE(STAT_UNTRACKED,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1072 LINE(HELP_KEYMAP,  "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1073 LINE(HELP_GROUP,   "",                  COLOR_BLUE,     COLOR_DEFAULT,  0), \
1074 LINE(BLAME_ID,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0)
1076 enum line_type {
1077 #define LINE(type, line, fg, bg, attr) \
1078         LINE_##type
1079         LINE_INFO,
1080         LINE_NONE
1081 #undef  LINE
1082 };
1084 struct line_info {
1085         const char *name;       /* Option name. */
1086         int namelen;            /* Size of option name. */
1087         const char *line;       /* The start of line to match. */
1088         int linelen;            /* Size of string to match. */
1089         int fg, bg, attr;       /* Color and text attributes for the lines. */
1090 };
1092 static struct line_info line_info[] = {
1093 #define LINE(type, line, fg, bg, attr) \
1094         { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
1095         LINE_INFO
1096 #undef  LINE
1097 };
1099 static enum line_type
1100 get_line_type(const char *line)
1102         int linelen = strlen(line);
1103         enum line_type type;
1105         for (type = 0; type < ARRAY_SIZE(line_info); type++)
1106                 /* Case insensitive search matches Signed-off-by lines better. */
1107                 if (linelen >= line_info[type].linelen &&
1108                     !strncasecmp(line_info[type].line, line, line_info[type].linelen))
1109                         return type;
1111         return LINE_DEFAULT;
1114 static inline int
1115 get_line_attr(enum line_type type)
1117         assert(type < ARRAY_SIZE(line_info));
1118         return COLOR_PAIR(type) | line_info[type].attr;
1121 static struct line_info *
1122 get_line_info(const char *name)
1124         size_t namelen = strlen(name);
1125         enum line_type type;
1127         for (type = 0; type < ARRAY_SIZE(line_info); type++)
1128                 if (namelen == line_info[type].namelen &&
1129                     !string_enum_compare(line_info[type].name, name, namelen))
1130                         return &line_info[type];
1132         return NULL;
1135 static void
1136 init_colors(void)
1138         int default_bg = line_info[LINE_DEFAULT].bg;
1139         int default_fg = line_info[LINE_DEFAULT].fg;
1140         enum line_type type;
1142         start_color();
1144         if (assume_default_colors(default_fg, default_bg) == ERR) {
1145                 default_bg = COLOR_BLACK;
1146                 default_fg = COLOR_WHITE;
1147         }
1149         for (type = 0; type < ARRAY_SIZE(line_info); type++) {
1150                 struct line_info *info = &line_info[type];
1151                 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
1152                 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
1154                 init_pair(type, fg, bg);
1155         }
1158 struct line {
1159         enum line_type type;
1161         /* State flags */
1162         unsigned int selected:1;
1163         unsigned int dirty:1;
1164         unsigned int cleareol:1;
1165         unsigned int other:16;
1167         void *data;             /* User data */
1168 };
1171 /*
1172  * Keys
1173  */
1175 struct keybinding {
1176         int alias;
1177         enum request request;
1178 };
1180 static const struct keybinding default_keybindings[] = {
1181         /* View switching */
1182         { 'm',          REQ_VIEW_MAIN },
1183         { 'd',          REQ_VIEW_DIFF },
1184         { 'l',          REQ_VIEW_LOG },
1185         { 't',          REQ_VIEW_TREE },
1186         { 'f',          REQ_VIEW_BLOB },
1187         { 'B',          REQ_VIEW_BLAME },
1188         { 'H',          REQ_VIEW_BRANCH },
1189         { 'p',          REQ_VIEW_PAGER },
1190         { 'h',          REQ_VIEW_HELP },
1191         { 'S',          REQ_VIEW_STATUS },
1192         { 'c',          REQ_VIEW_STAGE },
1194         /* View manipulation */
1195         { 'q',          REQ_VIEW_CLOSE },
1196         { KEY_TAB,      REQ_VIEW_NEXT },
1197         { KEY_RETURN,   REQ_ENTER },
1198         { KEY_UP,       REQ_PREVIOUS },
1199         { KEY_DOWN,     REQ_NEXT },
1200         { 'R',          REQ_REFRESH },
1201         { KEY_F(5),     REQ_REFRESH },
1202         { 'O',          REQ_MAXIMIZE },
1204         /* Cursor navigation */
1205         { 'k',          REQ_MOVE_UP },
1206         { 'j',          REQ_MOVE_DOWN },
1207         { KEY_HOME,     REQ_MOVE_FIRST_LINE },
1208         { KEY_END,      REQ_MOVE_LAST_LINE },
1209         { KEY_NPAGE,    REQ_MOVE_PAGE_DOWN },
1210         { ' ',          REQ_MOVE_PAGE_DOWN },
1211         { KEY_PPAGE,    REQ_MOVE_PAGE_UP },
1212         { 'b',          REQ_MOVE_PAGE_UP },
1213         { '-',          REQ_MOVE_PAGE_UP },
1215         /* Scrolling */
1216         { KEY_LEFT,     REQ_SCROLL_LEFT },
1217         { KEY_RIGHT,    REQ_SCROLL_RIGHT },
1218         { KEY_IC,       REQ_SCROLL_LINE_UP },
1219         { KEY_DC,       REQ_SCROLL_LINE_DOWN },
1220         { 'w',          REQ_SCROLL_PAGE_UP },
1221         { 's',          REQ_SCROLL_PAGE_DOWN },
1223         /* Searching */
1224         { '/',          REQ_SEARCH },
1225         { '?',          REQ_SEARCH_BACK },
1226         { 'n',          REQ_FIND_NEXT },
1227         { 'N',          REQ_FIND_PREV },
1229         /* Misc */
1230         { 'Q',          REQ_QUIT },
1231         { 'z',          REQ_STOP_LOADING },
1232         { 'v',          REQ_SHOW_VERSION },
1233         { 'r',          REQ_SCREEN_REDRAW },
1234         { 'o',          REQ_OPTIONS },
1235         { '.',          REQ_TOGGLE_LINENO },
1236         { 'D',          REQ_TOGGLE_DATE },
1237         { 'A',          REQ_TOGGLE_AUTHOR },
1238         { 'g',          REQ_TOGGLE_REV_GRAPH },
1239         { 'F',          REQ_TOGGLE_REFS },
1240         { 'I',          REQ_TOGGLE_SORT_ORDER },
1241         { 'i',          REQ_TOGGLE_SORT_FIELD },
1242         { ':',          REQ_PROMPT },
1243         { 'u',          REQ_STATUS_UPDATE },
1244         { '!',          REQ_STATUS_REVERT },
1245         { 'M',          REQ_STATUS_MERGE },
1246         { '@',          REQ_STAGE_NEXT },
1247         { ',',          REQ_PARENT },
1248         { 'e',          REQ_EDIT },
1249 };
1251 #define KEYMAP_INFO \
1252         KEYMAP_(GENERIC), \
1253         KEYMAP_(MAIN), \
1254         KEYMAP_(DIFF), \
1255         KEYMAP_(LOG), \
1256         KEYMAP_(TREE), \
1257         KEYMAP_(BLOB), \
1258         KEYMAP_(BLAME), \
1259         KEYMAP_(BRANCH), \
1260         KEYMAP_(PAGER), \
1261         KEYMAP_(HELP), \
1262         KEYMAP_(STATUS), \
1263         KEYMAP_(STAGE)
1265 enum keymap {
1266 #define KEYMAP_(name) KEYMAP_##name
1267         KEYMAP_INFO
1268 #undef  KEYMAP_
1269 };
1271 static const struct enum_map keymap_table[] = {
1272 #define KEYMAP_(name) ENUM_MAP(#name, KEYMAP_##name)
1273         KEYMAP_INFO
1274 #undef  KEYMAP_
1275 };
1277 #define set_keymap(map, name) map_enum(map, keymap_table, name)
1279 struct keybinding_table {
1280         struct keybinding *data;
1281         size_t size;
1282 };
1284 static struct keybinding_table keybindings[ARRAY_SIZE(keymap_table)];
1286 static void
1287 add_keybinding(enum keymap keymap, enum request request, int key)
1289         struct keybinding_table *table = &keybindings[keymap];
1291         table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data));
1292         if (!table->data)
1293                 die("Failed to allocate keybinding");
1294         table->data[table->size].alias = key;
1295         table->data[table->size++].request = request;
1298 /* Looks for a key binding first in the given map, then in the generic map, and
1299  * lastly in the default keybindings. */
1300 static enum request
1301 get_keybinding(enum keymap keymap, int key)
1303         size_t i;
1305         for (i = 0; i < keybindings[keymap].size; i++)
1306                 if (keybindings[keymap].data[i].alias == key)
1307                         return keybindings[keymap].data[i].request;
1309         for (i = 0; i < keybindings[KEYMAP_GENERIC].size; i++)
1310                 if (keybindings[KEYMAP_GENERIC].data[i].alias == key)
1311                         return keybindings[KEYMAP_GENERIC].data[i].request;
1313         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1314                 if (default_keybindings[i].alias == key)
1315                         return default_keybindings[i].request;
1317         return (enum request) key;
1321 struct key {
1322         const char *name;
1323         int value;
1324 };
1326 static const struct key key_table[] = {
1327         { "Enter",      KEY_RETURN },
1328         { "Space",      ' ' },
1329         { "Backspace",  KEY_BACKSPACE },
1330         { "Tab",        KEY_TAB },
1331         { "Escape",     KEY_ESC },
1332         { "Left",       KEY_LEFT },
1333         { "Right",      KEY_RIGHT },
1334         { "Up",         KEY_UP },
1335         { "Down",       KEY_DOWN },
1336         { "Insert",     KEY_IC },
1337         { "Delete",     KEY_DC },
1338         { "Hash",       '#' },
1339         { "Home",       KEY_HOME },
1340         { "End",        KEY_END },
1341         { "PageUp",     KEY_PPAGE },
1342         { "PageDown",   KEY_NPAGE },
1343         { "F1",         KEY_F(1) },
1344         { "F2",         KEY_F(2) },
1345         { "F3",         KEY_F(3) },
1346         { "F4",         KEY_F(4) },
1347         { "F5",         KEY_F(5) },
1348         { "F6",         KEY_F(6) },
1349         { "F7",         KEY_F(7) },
1350         { "F8",         KEY_F(8) },
1351         { "F9",         KEY_F(9) },
1352         { "F10",        KEY_F(10) },
1353         { "F11",        KEY_F(11) },
1354         { "F12",        KEY_F(12) },
1355 };
1357 static int
1358 get_key_value(const char *name)
1360         int i;
1362         for (i = 0; i < ARRAY_SIZE(key_table); i++)
1363                 if (!strcasecmp(key_table[i].name, name))
1364                         return key_table[i].value;
1366         if (strlen(name) == 1 && isprint(*name))
1367                 return (int) *name;
1369         return ERR;
1372 static const char *
1373 get_key_name(int key_value)
1375         static char key_char[] = "'X'";
1376         const char *seq = NULL;
1377         int key;
1379         for (key = 0; key < ARRAY_SIZE(key_table); key++)
1380                 if (key_table[key].value == key_value)
1381                         seq = key_table[key].name;
1383         if (seq == NULL &&
1384             key_value < 127 &&
1385             isprint(key_value)) {
1386                 key_char[1] = (char) key_value;
1387                 seq = key_char;
1388         }
1390         return seq ? seq : "(no key)";
1393 static bool
1394 append_key(char *buf, size_t *pos, const struct keybinding *keybinding)
1396         const char *sep = *pos > 0 ? ", " : "";
1397         const char *keyname = get_key_name(keybinding->alias);
1399         return string_nformat(buf, BUFSIZ, pos, "%s%s", sep, keyname);
1402 static bool
1403 append_keymap_request_keys(char *buf, size_t *pos, enum request request,
1404                            enum keymap keymap, bool all)
1406         int i;
1408         for (i = 0; i < keybindings[keymap].size; i++) {
1409                 if (keybindings[keymap].data[i].request == request) {
1410                         if (!append_key(buf, pos, &keybindings[keymap].data[i]))
1411                                 return FALSE;
1412                         if (!all)
1413                                 break;
1414                 }
1415         }
1417         return TRUE;
1420 #define get_key(keymap, request) get_keys(keymap, request, FALSE)
1422 static const char *
1423 get_keys(enum keymap keymap, enum request request, bool all)
1425         static char buf[BUFSIZ];
1426         size_t pos = 0;
1427         int i;
1429         buf[pos] = 0;
1431         if (!append_keymap_request_keys(buf, &pos, request, keymap, all))
1432                 return "Too many keybindings!";
1433         if (pos > 0 && !all)
1434                 return buf;
1436         if (keymap != KEYMAP_GENERIC) {
1437                 /* Only the generic keymap includes the default keybindings when
1438                  * listing all keys. */
1439                 if (all)
1440                         return buf;
1442                 if (!append_keymap_request_keys(buf, &pos, request, KEYMAP_GENERIC, all))
1443                         return "Too many keybindings!";
1444                 if (pos)
1445                         return buf;
1446         }
1448         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1449                 if (default_keybindings[i].request == request) {
1450                         if (!append_key(buf, &pos, &default_keybindings[i]))
1451                                 return "Too many keybindings!";
1452                         if (!all)
1453                                 return buf;
1454                 }
1455         }
1457         return buf;
1460 struct run_request {
1461         enum keymap keymap;
1462         int key;
1463         const char *argv[SIZEOF_ARG];
1464 };
1466 static struct run_request *run_request;
1467 static size_t run_requests;
1469 DEFINE_ALLOCATOR(realloc_run_requests, struct run_request, 8)
1471 static enum request
1472 add_run_request(enum keymap keymap, int key, int argc, const char **argv)
1474         struct run_request *req;
1476         if (argc >= ARRAY_SIZE(req->argv) - 1)
1477                 return REQ_NONE;
1479         if (!realloc_run_requests(&run_request, run_requests, 1))
1480                 return REQ_NONE;
1482         req = &run_request[run_requests];
1483         req->keymap = keymap;
1484         req->key = key;
1485         req->argv[0] = NULL;
1487         if (!format_argv(req->argv, argv, FORMAT_NONE))
1488                 return REQ_NONE;
1490         return REQ_NONE + ++run_requests;
1493 static struct run_request *
1494 get_run_request(enum request request)
1496         if (request <= REQ_NONE)
1497                 return NULL;
1498         return &run_request[request - REQ_NONE - 1];
1501 static void
1502 add_builtin_run_requests(void)
1504         const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1505         const char *commit[] = { "git", "commit", NULL };
1506         const char *gc[] = { "git", "gc", NULL };
1507         struct {
1508                 enum keymap keymap;
1509                 int key;
1510                 int argc;
1511                 const char **argv;
1512         } reqs[] = {
1513                 { KEYMAP_MAIN,    'C', ARRAY_SIZE(cherry_pick) - 1, cherry_pick },
1514                 { KEYMAP_STATUS,  'C', ARRAY_SIZE(commit) - 1, commit },
1515                 { KEYMAP_GENERIC, 'G', ARRAY_SIZE(gc) - 1, gc },
1516         };
1517         int i;
1519         for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1520                 enum request req;
1522                 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argc, reqs[i].argv);
1523                 if (req != REQ_NONE)
1524                         add_keybinding(reqs[i].keymap, req, reqs[i].key);
1525         }
1528 /*
1529  * User config file handling.
1530  */
1532 static int   config_lineno;
1533 static bool  config_errors;
1534 static const char *config_msg;
1536 static const struct enum_map color_map[] = {
1537 #define COLOR_MAP(name) ENUM_MAP(#name, COLOR_##name)
1538         COLOR_MAP(DEFAULT),
1539         COLOR_MAP(BLACK),
1540         COLOR_MAP(BLUE),
1541         COLOR_MAP(CYAN),
1542         COLOR_MAP(GREEN),
1543         COLOR_MAP(MAGENTA),
1544         COLOR_MAP(RED),
1545         COLOR_MAP(WHITE),
1546         COLOR_MAP(YELLOW),
1547 };
1549 static const struct enum_map attr_map[] = {
1550 #define ATTR_MAP(name) ENUM_MAP(#name, A_##name)
1551         ATTR_MAP(NORMAL),
1552         ATTR_MAP(BLINK),
1553         ATTR_MAP(BOLD),
1554         ATTR_MAP(DIM),
1555         ATTR_MAP(REVERSE),
1556         ATTR_MAP(STANDOUT),
1557         ATTR_MAP(UNDERLINE),
1558 };
1560 #define set_attribute(attr, name)       map_enum(attr, attr_map, name)
1562 static int parse_step(double *opt, const char *arg)
1564         *opt = atoi(arg);
1565         if (!strchr(arg, '%'))
1566                 return OK;
1568         /* "Shift down" so 100% and 1 does not conflict. */
1569         *opt = (*opt - 1) / 100;
1570         if (*opt >= 1.0) {
1571                 *opt = 0.99;
1572                 config_msg = "Step value larger than 100%";
1573                 return ERR;
1574         }
1575         if (*opt < 0.0) {
1576                 *opt = 1;
1577                 config_msg = "Invalid step value";
1578                 return ERR;
1579         }
1580         return OK;
1583 static int
1584 parse_int(int *opt, const char *arg, int min, int max)
1586         int value = atoi(arg);
1588         if (min <= value && value <= max) {
1589                 *opt = value;
1590                 return OK;
1591         }
1593         config_msg = "Integer value out of bound";
1594         return ERR;
1597 static bool
1598 set_color(int *color, const char *name)
1600         if (map_enum(color, color_map, name))
1601                 return TRUE;
1602         if (!prefixcmp(name, "color"))
1603                 return parse_int(color, name + 5, 0, 255) == OK;
1604         return FALSE;
1607 /* Wants: object fgcolor bgcolor [attribute] */
1608 static int
1609 option_color_command(int argc, const char *argv[])
1611         struct line_info *info;
1613         if (argc < 3) {
1614                 config_msg = "Wrong number of arguments given to color command";
1615                 return ERR;
1616         }
1618         info = get_line_info(argv[0]);
1619         if (!info) {
1620                 static const struct enum_map obsolete[] = {
1621                         ENUM_MAP("main-delim",  LINE_DELIMITER),
1622                         ENUM_MAP("main-date",   LINE_DATE),
1623                         ENUM_MAP("main-author", LINE_AUTHOR),
1624                 };
1625                 int index;
1627                 if (!map_enum(&index, obsolete, argv[0])) {
1628                         config_msg = "Unknown color name";
1629                         return ERR;
1630                 }
1631                 info = &line_info[index];
1632         }
1634         if (!set_color(&info->fg, argv[1]) ||
1635             !set_color(&info->bg, argv[2])) {
1636                 config_msg = "Unknown color";
1637                 return ERR;
1638         }
1640         info->attr = 0;
1641         while (argc-- > 3) {
1642                 int attr;
1644                 if (!set_attribute(&attr, argv[argc])) {
1645                         config_msg = "Unknown attribute";
1646                         return ERR;
1647                 }
1648                 info->attr |= attr;
1649         }
1651         return OK;
1654 static int parse_bool(bool *opt, const char *arg)
1656         *opt = (!strcmp(arg, "1") || !strcmp(arg, "true") || !strcmp(arg, "yes"))
1657                 ? TRUE : FALSE;
1658         return OK;
1661 static int
1662 parse_string(char *opt, const char *arg, size_t optsize)
1664         int arglen = strlen(arg);
1666         switch (arg[0]) {
1667         case '\"':
1668         case '\'':
1669                 if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1670                         config_msg = "Unmatched quotation";
1671                         return ERR;
1672                 }
1673                 arg += 1; arglen -= 2;
1674         default:
1675                 string_ncopy_do(opt, optsize, arg, arglen);
1676                 return OK;
1677         }
1680 /* Wants: name = value */
1681 static int
1682 option_set_command(int argc, const char *argv[])
1684         if (argc != 3) {
1685                 config_msg = "Wrong number of arguments given to set command";
1686                 return ERR;
1687         }
1689         if (strcmp(argv[1], "=")) {
1690                 config_msg = "No value assigned";
1691                 return ERR;
1692         }
1694         if (!strcmp(argv[0], "show-author"))
1695                 return parse_bool(&opt_author, argv[2]);
1697         if (!strcmp(argv[0], "show-date")) {
1698                 bool show_date;
1700                 if (!strcmp(argv[2], "relative")) {
1701                         opt_date = DATE_RELATIVE;
1702                         return OK;
1703                 } else if (!strcmp(argv[2], "short")) {
1704                         opt_date = DATE_SHORT;
1705                         return OK;
1706                 } else if (parse_bool(&show_date, argv[2])) {
1707                         opt_date = show_date ? DATE_DEFAULT : DATE_NONE;
1708                 }
1709                 return ERR;
1710         }
1712         if (!strcmp(argv[0], "show-rev-graph"))
1713                 return parse_bool(&opt_rev_graph, argv[2]);
1715         if (!strcmp(argv[0], "show-refs"))
1716                 return parse_bool(&opt_show_refs, argv[2]);
1718         if (!strcmp(argv[0], "show-line-numbers"))
1719                 return parse_bool(&opt_line_number, argv[2]);
1721         if (!strcmp(argv[0], "line-graphics"))
1722                 return parse_bool(&opt_line_graphics, argv[2]);
1724         if (!strcmp(argv[0], "line-number-interval"))
1725                 return parse_int(&opt_num_interval, argv[2], 1, 1024);
1727         if (!strcmp(argv[0], "author-width"))
1728                 return parse_int(&opt_author_cols, argv[2], 0, 1024);
1730         if (!strcmp(argv[0], "horizontal-scroll"))
1731                 return parse_step(&opt_hscroll, argv[2]);
1733         if (!strcmp(argv[0], "split-view-height"))
1734                 return parse_step(&opt_scale_split_view, argv[2]);
1736         if (!strcmp(argv[0], "tab-size"))
1737                 return parse_int(&opt_tab_size, argv[2], 1, 1024);
1739         if (!strcmp(argv[0], "commit-encoding"))
1740                 return parse_string(opt_encoding, argv[2], sizeof(opt_encoding));
1742         config_msg = "Unknown variable name";
1743         return ERR;
1746 /* Wants: mode request key */
1747 static int
1748 option_bind_command(int argc, const char *argv[])
1750         enum request request;
1751         int keymap = -1;
1752         int key;
1754         if (argc < 3) {
1755                 config_msg = "Wrong number of arguments given to bind command";
1756                 return ERR;
1757         }
1759         if (set_keymap(&keymap, argv[0]) == ERR) {
1760                 config_msg = "Unknown key map";
1761                 return ERR;
1762         }
1764         key = get_key_value(argv[1]);
1765         if (key == ERR) {
1766                 config_msg = "Unknown key";
1767                 return ERR;
1768         }
1770         request = get_request(argv[2]);
1771         if (request == REQ_NONE) {
1772                 static const struct enum_map obsolete[] = {
1773                         ENUM_MAP("cherry-pick",         REQ_NONE),
1774                         ENUM_MAP("screen-resize",       REQ_NONE),
1775                         ENUM_MAP("tree-parent",         REQ_PARENT),
1776                 };
1777                 int alias;
1779                 if (map_enum(&alias, obsolete, argv[2])) {
1780                         if (alias != REQ_NONE)
1781                                 add_keybinding(keymap, alias, key);
1782                         config_msg = "Obsolete request name";
1783                         return ERR;
1784                 }
1785         }
1786         if (request == REQ_NONE && *argv[2]++ == '!')
1787                 request = add_run_request(keymap, key, argc - 2, argv + 2);
1788         if (request == REQ_NONE) {
1789                 config_msg = "Unknown request name";
1790                 return ERR;
1791         }
1793         add_keybinding(keymap, request, key);
1795         return OK;
1798 static int
1799 set_option(const char *opt, char *value)
1801         const char *argv[SIZEOF_ARG];
1802         int argc = 0;
1804         if (!argv_from_string(argv, &argc, value)) {
1805                 config_msg = "Too many option arguments";
1806                 return ERR;
1807         }
1809         if (!strcmp(opt, "color"))
1810                 return option_color_command(argc, argv);
1812         if (!strcmp(opt, "set"))
1813                 return option_set_command(argc, argv);
1815         if (!strcmp(opt, "bind"))
1816                 return option_bind_command(argc, argv);
1818         config_msg = "Unknown option command";
1819         return ERR;
1822 static int
1823 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
1825         int status = OK;
1827         config_lineno++;
1828         config_msg = "Internal error";
1830         /* Check for comment markers, since read_properties() will
1831          * only ensure opt and value are split at first " \t". */
1832         optlen = strcspn(opt, "#");
1833         if (optlen == 0)
1834                 return OK;
1836         if (opt[optlen] != 0) {
1837                 config_msg = "No option value";
1838                 status = ERR;
1840         }  else {
1841                 /* Look for comment endings in the value. */
1842                 size_t len = strcspn(value, "#");
1844                 if (len < valuelen) {
1845                         valuelen = len;
1846                         value[valuelen] = 0;
1847                 }
1849                 status = set_option(opt, value);
1850         }
1852         if (status == ERR) {
1853                 warn("Error on line %d, near '%.*s': %s",
1854                      config_lineno, (int) optlen, opt, config_msg);
1855                 config_errors = TRUE;
1856         }
1858         /* Always keep going if errors are encountered. */
1859         return OK;
1862 static void
1863 load_option_file(const char *path)
1865         struct io io = {};
1867         /* It's OK that the file doesn't exist. */
1868         if (!io_open(&io, "%s", path))
1869                 return;
1871         config_lineno = 0;
1872         config_errors = FALSE;
1874         if (io_load(&io, " \t", read_option) == ERR ||
1875             config_errors == TRUE)
1876                 warn("Errors while loading %s.", path);
1879 static int
1880 load_options(void)
1882         const char *home = getenv("HOME");
1883         const char *tigrc_user = getenv("TIGRC_USER");
1884         const char *tigrc_system = getenv("TIGRC_SYSTEM");
1885         char buf[SIZEOF_STR];
1887         add_builtin_run_requests();
1889         if (!tigrc_system)
1890                 tigrc_system = SYSCONFDIR "/tigrc";
1891         load_option_file(tigrc_system);
1893         if (!tigrc_user) {
1894                 if (!home || !string_format(buf, "%s/.tigrc", home))
1895                         return ERR;
1896                 tigrc_user = buf;
1897         }
1898         load_option_file(tigrc_user);
1900         return OK;
1904 /*
1905  * The viewer
1906  */
1908 struct view;
1909 struct view_ops;
1911 /* The display array of active views and the index of the current view. */
1912 static struct view *display[2];
1913 static unsigned int current_view;
1915 #define foreach_displayed_view(view, i) \
1916         for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1918 #define displayed_views()       (display[1] != NULL ? 2 : 1)
1920 /* Current head and commit ID */
1921 static char ref_blob[SIZEOF_REF]        = "";
1922 static char ref_commit[SIZEOF_REF]      = "HEAD";
1923 static char ref_head[SIZEOF_REF]        = "HEAD";
1925 struct view {
1926         const char *name;       /* View name */
1927         const char *cmd_env;    /* Command line set via environment */
1928         const char *id;         /* Points to either of ref_{head,commit,blob} */
1930         struct view_ops *ops;   /* View operations */
1932         enum keymap keymap;     /* What keymap does this view have */
1933         bool git_dir;           /* Whether the view requires a git directory. */
1935         char ref[SIZEOF_REF];   /* Hovered commit reference */
1936         char vid[SIZEOF_REF];   /* View ID. Set to id member when updating. */
1938         int height, width;      /* The width and height of the main window */
1939         WINDOW *win;            /* The main window */
1940         WINDOW *title;          /* The title window living below the main window */
1942         /* Navigation */
1943         unsigned long offset;   /* Offset of the window top */
1944         unsigned long yoffset;  /* Offset from the window side. */
1945         unsigned long lineno;   /* Current line number */
1946         unsigned long p_offset; /* Previous offset of the window top */
1947         unsigned long p_yoffset;/* Previous offset from the window side */
1948         unsigned long p_lineno; /* Previous current line number */
1949         bool p_restore;         /* Should the previous position be restored. */
1951         /* Searching */
1952         char grep[SIZEOF_STR];  /* Search string */
1953         regex_t *regex;         /* Pre-compiled regexp */
1955         /* If non-NULL, points to the view that opened this view. If this view
1956          * is closed tig will switch back to the parent view. */
1957         struct view *parent;
1959         /* Buffering */
1960         size_t lines;           /* Total number of lines */
1961         struct line *line;      /* Line index */
1962         unsigned int digits;    /* Number of digits in the lines member. */
1964         /* Drawing */
1965         struct line *curline;   /* Line currently being drawn. */
1966         enum line_type curtype; /* Attribute currently used for drawing. */
1967         unsigned long col;      /* Column when drawing. */
1968         bool has_scrolled;      /* View was scrolled. */
1970         /* Loading */
1971         struct io io;
1972         struct io *pipe;
1973         time_t start_time;
1974         time_t update_secs;
1975 };
1977 struct view_ops {
1978         /* What type of content being displayed. Used in the title bar. */
1979         const char *type;
1980         /* Default command arguments. */
1981         const char **argv;
1982         /* Open and reads in all view content. */
1983         bool (*open)(struct view *view);
1984         /* Read one line; updates view->line. */
1985         bool (*read)(struct view *view, char *data);
1986         /* Draw one line; @lineno must be < view->height. */
1987         bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
1988         /* Depending on view handle a special requests. */
1989         enum request (*request)(struct view *view, enum request request, struct line *line);
1990         /* Search for regexp in a line. */
1991         bool (*grep)(struct view *view, struct line *line);
1992         /* Select line */
1993         void (*select)(struct view *view, struct line *line);
1994         /* Prepare view for loading */
1995         bool (*prepare)(struct view *view);
1996 };
1998 static struct view_ops blame_ops;
1999 static struct view_ops blob_ops;
2000 static struct view_ops diff_ops;
2001 static struct view_ops help_ops;
2002 static struct view_ops log_ops;
2003 static struct view_ops main_ops;
2004 static struct view_ops pager_ops;
2005 static struct view_ops stage_ops;
2006 static struct view_ops status_ops;
2007 static struct view_ops tree_ops;
2008 static struct view_ops branch_ops;
2010 #define VIEW_STR(name, env, ref, ops, map, git) \
2011         { name, #env, ref, ops, map, git }
2013 #define VIEW_(id, name, ops, git, ref) \
2014         VIEW_STR(name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
2017 static struct view views[] = {
2018         VIEW_(MAIN,   "main",   &main_ops,   TRUE,  ref_head),
2019         VIEW_(DIFF,   "diff",   &diff_ops,   TRUE,  ref_commit),
2020         VIEW_(LOG,    "log",    &log_ops,    TRUE,  ref_head),
2021         VIEW_(TREE,   "tree",   &tree_ops,   TRUE,  ref_commit),
2022         VIEW_(BLOB,   "blob",   &blob_ops,   TRUE,  ref_blob),
2023         VIEW_(BLAME,  "blame",  &blame_ops,  TRUE,  ref_commit),
2024         VIEW_(BRANCH, "branch", &branch_ops, TRUE,  ref_head),
2025         VIEW_(HELP,   "help",   &help_ops,   FALSE, ""),
2026         VIEW_(PAGER,  "pager",  &pager_ops,  FALSE, "stdin"),
2027         VIEW_(STATUS, "status", &status_ops, TRUE,  ""),
2028         VIEW_(STAGE,  "stage",  &stage_ops,  TRUE,  ""),
2029 };
2031 #define VIEW(req)       (&views[(req) - REQ_OFFSET - 1])
2032 #define VIEW_REQ(view)  ((view) - views + REQ_OFFSET + 1)
2034 #define foreach_view(view, i) \
2035         for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
2037 #define view_is_displayed(view) \
2038         (view == display[0] || view == display[1])
2041 enum line_graphic {
2042         LINE_GRAPHIC_VLINE
2043 };
2045 static chtype line_graphics[] = {
2046         /* LINE_GRAPHIC_VLINE: */ '|'
2047 };
2049 static inline void
2050 set_view_attr(struct view *view, enum line_type type)
2052         if (!view->curline->selected && view->curtype != type) {
2053                 wattrset(view->win, get_line_attr(type));
2054                 wchgat(view->win, -1, 0, type, NULL);
2055                 view->curtype = type;
2056         }
2059 static int
2060 draw_chars(struct view *view, enum line_type type, const char *string,
2061            int max_len, bool use_tilde)
2063         static char out_buffer[BUFSIZ * 2];
2064         int len = 0;
2065         int col = 0;
2066         int trimmed = FALSE;
2067         size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2069         if (max_len <= 0)
2070                 return 0;
2072         len = utf8_length(&string, skip, &col, max_len, &trimmed, use_tilde);
2074         set_view_attr(view, type);
2075         if (len > 0) {
2076                 if (opt_iconv_out != ICONV_NONE) {
2077                         ICONV_CONST char *inbuf = (ICONV_CONST char *) string;
2078                         size_t inlen = len + 1;
2080                         char *outbuf = out_buffer;
2081                         size_t outlen = sizeof(out_buffer);
2083                         size_t ret;
2085                         ret = iconv(opt_iconv_out, &inbuf, &inlen, &outbuf, &outlen);
2086                         if (ret != (size_t) -1) {
2087                                 string = out_buffer;
2088                                 len = sizeof(out_buffer) - outlen;
2089                         }
2090                 }
2092                 waddnstr(view->win, string, len);
2093         }
2094         if (trimmed && use_tilde) {
2095                 set_view_attr(view, LINE_DELIMITER);
2096                 waddch(view->win, '~');
2097                 col++;
2098         }
2100         return col;
2103 static int
2104 draw_space(struct view *view, enum line_type type, int max, int spaces)
2106         static char space[] = "                    ";
2107         int col = 0;
2109         spaces = MIN(max, spaces);
2111         while (spaces > 0) {
2112                 int len = MIN(spaces, sizeof(space) - 1);
2114                 col += draw_chars(view, type, space, len, FALSE);
2115                 spaces -= len;
2116         }
2118         return col;
2121 static bool
2122 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
2124         view->col += draw_chars(view, type, string, view->width + view->yoffset - view->col, trim);
2125         return view->width + view->yoffset <= view->col;
2128 static bool
2129 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
2131         size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2132         int max = view->width + view->yoffset - view->col;
2133         int i;
2135         if (max < size)
2136                 size = max;
2138         set_view_attr(view, type);
2139         /* Using waddch() instead of waddnstr() ensures that
2140          * they'll be rendered correctly for the cursor line. */
2141         for (i = skip; i < size; i++)
2142                 waddch(view->win, graphic[i]);
2144         view->col += size;
2145         if (size < max && skip <= size)
2146                 waddch(view->win, ' ');
2147         view->col++;
2149         return view->width + view->yoffset <= view->col;
2152 static bool
2153 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
2155         int max = MIN(view->width + view->yoffset - view->col, len);
2156         int col;
2158         if (text)
2159                 col = draw_chars(view, type, text, max - 1, trim);
2160         else
2161                 col = draw_space(view, type, max - 1, max - 1);
2163         view->col += col;
2164         view->col += draw_space(view, LINE_DEFAULT, max - col, max - col);
2165         return view->width + view->yoffset <= view->col;
2168 static bool
2169 draw_date(struct view *view, time_t *time)
2171         const char *date = time ? mkdate(time) : "";
2172         int cols = opt_date == DATE_SHORT ? DATE_SHORT_COLS : DATE_COLS;
2174         return draw_field(view, LINE_DATE, date, cols, FALSE);
2177 static bool
2178 draw_author(struct view *view, const char *author)
2180         bool trim = opt_author_cols == 0 || opt_author_cols > 5 || !author;
2182         if (!trim) {
2183                 static char initials[10];
2184                 size_t pos;
2186 #define is_initial_sep(c) (isspace(c) || ispunct(c) || (c) == '@')
2188                 memset(initials, 0, sizeof(initials));
2189                 for (pos = 0; *author && pos < opt_author_cols - 1; author++, pos++) {
2190                         while (is_initial_sep(*author))
2191                                 author++;
2192                         strncpy(&initials[pos], author, sizeof(initials) - 1 - pos);
2193                         while (*author && !is_initial_sep(author[1]))
2194                                 author++;
2195                 }
2197                 author = initials;
2198         }
2200         return draw_field(view, LINE_AUTHOR, author, opt_author_cols, trim);
2203 static bool
2204 draw_mode(struct view *view, mode_t mode)
2206         const char *str;
2208         if (S_ISDIR(mode))
2209                 str = "drwxr-xr-x";
2210         else if (S_ISLNK(mode))
2211                 str = "lrwxrwxrwx";
2212         else if (S_ISGITLINK(mode))
2213                 str = "m---------";
2214         else if (S_ISREG(mode) && mode & S_IXUSR)
2215                 str = "-rwxr-xr-x";
2216         else if (S_ISREG(mode))
2217                 str = "-rw-r--r--";
2218         else
2219                 str = "----------";
2221         return draw_field(view, LINE_MODE, str, STRING_SIZE("-rw-r--r-- "), FALSE);
2224 static bool
2225 draw_lineno(struct view *view, unsigned int lineno)
2227         char number[10];
2228         int digits3 = view->digits < 3 ? 3 : view->digits;
2229         int max = MIN(view->width + view->yoffset - view->col, digits3);
2230         char *text = NULL;
2232         lineno += view->offset + 1;
2233         if (lineno == 1 || (lineno % opt_num_interval) == 0) {
2234                 static char fmt[] = "%1ld";
2236                 fmt[1] = '0' + (view->digits <= 9 ? digits3 : 1);
2237                 if (string_format(number, fmt, lineno))
2238                         text = number;
2239         }
2240         if (text)
2241                 view->col += draw_chars(view, LINE_LINE_NUMBER, text, max, TRUE);
2242         else
2243                 view->col += draw_space(view, LINE_LINE_NUMBER, max, digits3);
2244         return draw_graphic(view, LINE_DEFAULT, &line_graphics[LINE_GRAPHIC_VLINE], 1);
2247 static bool
2248 draw_view_line(struct view *view, unsigned int lineno)
2250         struct line *line;
2251         bool selected = (view->offset + lineno == view->lineno);
2253         assert(view_is_displayed(view));
2255         if (view->offset + lineno >= view->lines)
2256                 return FALSE;
2258         line = &view->line[view->offset + lineno];
2260         wmove(view->win, lineno, 0);
2261         if (line->cleareol)
2262                 wclrtoeol(view->win);
2263         view->col = 0;
2264         view->curline = line;
2265         view->curtype = LINE_NONE;
2266         line->selected = FALSE;
2267         line->dirty = line->cleareol = 0;
2269         if (selected) {
2270                 set_view_attr(view, LINE_CURSOR);
2271                 line->selected = TRUE;
2272                 view->ops->select(view, line);
2273         }
2275         return view->ops->draw(view, line, lineno);
2278 static void
2279 redraw_view_dirty(struct view *view)
2281         bool dirty = FALSE;
2282         int lineno;
2284         for (lineno = 0; lineno < view->height; lineno++) {
2285                 if (view->offset + lineno >= view->lines)
2286                         break;
2287                 if (!view->line[view->offset + lineno].dirty)
2288                         continue;
2289                 dirty = TRUE;
2290                 if (!draw_view_line(view, lineno))
2291                         break;
2292         }
2294         if (!dirty)
2295                 return;
2296         wnoutrefresh(view->win);
2299 static void
2300 redraw_view_from(struct view *view, int lineno)
2302         assert(0 <= lineno && lineno < view->height);
2304         for (; lineno < view->height; lineno++) {
2305                 if (!draw_view_line(view, lineno))
2306                         break;
2307         }
2309         wnoutrefresh(view->win);
2312 static void
2313 redraw_view(struct view *view)
2315         werase(view->win);
2316         redraw_view_from(view, 0);
2320 static void
2321 update_view_title(struct view *view)
2323         char buf[SIZEOF_STR];
2324         char state[SIZEOF_STR];
2325         size_t bufpos = 0, statelen = 0;
2327         assert(view_is_displayed(view));
2329         if (view != VIEW(REQ_VIEW_STATUS) && view->lines) {
2330                 unsigned int view_lines = view->offset + view->height;
2331                 unsigned int lines = view->lines
2332                                    ? MIN(view_lines, view->lines) * 100 / view->lines
2333                                    : 0;
2335                 string_format_from(state, &statelen, " - %s %d of %d (%d%%)",
2336                                    view->ops->type,
2337                                    view->lineno + 1,
2338                                    view->lines,
2339                                    lines);
2341         }
2343         if (view->pipe) {
2344                 time_t secs = time(NULL) - view->start_time;
2346                 /* Three git seconds are a long time ... */
2347                 if (secs > 2)
2348                         string_format_from(state, &statelen, " loading %lds", secs);
2349         }
2351         string_format_from(buf, &bufpos, "[%s]", view->name);
2352         if (*view->ref && bufpos < view->width) {
2353                 size_t refsize = strlen(view->ref);
2354                 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2356                 if (minsize < view->width)
2357                         refsize = view->width - minsize + 7;
2358                 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2359         }
2361         if (statelen && bufpos < view->width) {
2362                 string_format_from(buf, &bufpos, "%s", state);
2363         }
2365         if (view == display[current_view])
2366                 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2367         else
2368                 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2370         mvwaddnstr(view->title, 0, 0, buf, bufpos);
2371         wclrtoeol(view->title);
2372         wnoutrefresh(view->title);
2375 static int
2376 apply_step(double step, int value)
2378         if (step >= 1)
2379                 return (int) step;
2380         value *= step + 0.01;
2381         return value ? value : 1;
2384 static void
2385 resize_display(void)
2387         int offset, i;
2388         struct view *base = display[0];
2389         struct view *view = display[1] ? display[1] : display[0];
2391         /* Setup window dimensions */
2393         getmaxyx(stdscr, base->height, base->width);
2395         /* Make room for the status window. */
2396         base->height -= 1;
2398         if (view != base) {
2399                 /* Horizontal split. */
2400                 view->width   = base->width;
2401                 view->height  = apply_step(opt_scale_split_view, base->height);
2402                 view->height  = MAX(view->height, MIN_VIEW_HEIGHT);
2403                 view->height  = MIN(view->height, base->height - MIN_VIEW_HEIGHT);
2404                 base->height -= view->height;
2406                 /* Make room for the title bar. */
2407                 view->height -= 1;
2408         }
2410         /* Make room for the title bar. */
2411         base->height -= 1;
2413         offset = 0;
2415         foreach_displayed_view (view, i) {
2416                 if (!view->win) {
2417                         view->win = newwin(view->height, 0, offset, 0);
2418                         if (!view->win)
2419                                 die("Failed to create %s view", view->name);
2421                         scrollok(view->win, FALSE);
2423                         view->title = newwin(1, 0, offset + view->height, 0);
2424                         if (!view->title)
2425                                 die("Failed to create title window");
2427                 } else {
2428                         wresize(view->win, view->height, view->width);
2429                         mvwin(view->win,   offset, 0);
2430                         mvwin(view->title, offset + view->height, 0);
2431                 }
2433                 offset += view->height + 1;
2434         }
2437 static void
2438 redraw_display(bool clear)
2440         struct view *view;
2441         int i;
2443         foreach_displayed_view (view, i) {
2444                 if (clear)
2445                         wclear(view->win);
2446                 redraw_view(view);
2447                 update_view_title(view);
2448         }
2451 static void
2452 toggle_date_option(enum date *date)
2454         static const char *help[] = {
2455                 "no",
2456                 "default",
2457                 "relative",
2458                 "short"
2459         };
2461         opt_date = (opt_date + 1) % ARRAY_SIZE(help);
2462         redraw_display(FALSE);
2463         report("Displaying %s dates", help[opt_date]);
2466 static void
2467 toggle_view_option(bool *option, const char *help)
2469         *option = !*option;
2470         redraw_display(FALSE);
2471         report("%sabling %s", *option ? "En" : "Dis", help);
2474 static void
2475 open_option_menu(void)
2477         const struct menu_item menu[] = {
2478                 { '.', "line numbers", &opt_line_number },
2479                 { 'D', "date display", &opt_date },
2480                 { 'A', "author display", &opt_author },
2481                 { 'g', "revision graph display", &opt_rev_graph },
2482                 { 'F', "reference display", &opt_show_refs },
2483                 { 0 }
2484         };
2485         int selected = 0;
2487         if (prompt_menu("Toggle option", menu, &selected)) {
2488                 if (menu[selected].data == &opt_date)
2489                         toggle_date_option(menu[selected].data);
2490                 else
2491                         toggle_view_option(menu[selected].data, menu[selected].text);
2492         }
2495 static void
2496 maximize_view(struct view *view)
2498         memset(display, 0, sizeof(display));
2499         current_view = 0;
2500         display[current_view] = view;
2501         resize_display();
2502         redraw_display(FALSE);
2503         report("");
2507 /*
2508  * Navigation
2509  */
2511 static bool
2512 goto_view_line(struct view *view, unsigned long offset, unsigned long lineno)
2514         if (lineno >= view->lines)
2515                 lineno = view->lines > 0 ? view->lines - 1 : 0;
2517         if (offset > lineno || offset + view->height <= lineno) {
2518                 unsigned long half = view->height / 2;
2520                 if (lineno > half)
2521                         offset = lineno - half;
2522                 else
2523                         offset = 0;
2524         }
2526         if (offset != view->offset || lineno != view->lineno) {
2527                 view->offset = offset;
2528                 view->lineno = lineno;
2529                 return TRUE;
2530         }
2532         return FALSE;
2535 /* Scrolling backend */
2536 static void
2537 do_scroll_view(struct view *view, int lines)
2539         bool redraw_current_line = FALSE;
2541         /* The rendering expects the new offset. */
2542         view->offset += lines;
2544         assert(0 <= view->offset && view->offset < view->lines);
2545         assert(lines);
2547         /* Move current line into the view. */
2548         if (view->lineno < view->offset) {
2549                 view->lineno = view->offset;
2550                 redraw_current_line = TRUE;
2551         } else if (view->lineno >= view->offset + view->height) {
2552                 view->lineno = view->offset + view->height - 1;
2553                 redraw_current_line = TRUE;
2554         }
2556         assert(view->offset <= view->lineno && view->lineno < view->lines);
2558         /* Redraw the whole screen if scrolling is pointless. */
2559         if (view->height < ABS(lines)) {
2560                 redraw_view(view);
2562         } else {
2563                 int line = lines > 0 ? view->height - lines : 0;
2564                 int end = line + ABS(lines);
2566                 scrollok(view->win, TRUE);
2567                 wscrl(view->win, lines);
2568                 scrollok(view->win, FALSE);
2570                 while (line < end && draw_view_line(view, line))
2571                         line++;
2573                 if (redraw_current_line)
2574                         draw_view_line(view, view->lineno - view->offset);
2575                 wnoutrefresh(view->win);
2576         }
2578         view->has_scrolled = TRUE;
2579         report("");
2582 /* Scroll frontend */
2583 static void
2584 scroll_view(struct view *view, enum request request)
2586         int lines = 1;
2588         assert(view_is_displayed(view));
2590         switch (request) {
2591         case REQ_SCROLL_LEFT:
2592                 if (view->yoffset == 0) {
2593                         report("Cannot scroll beyond the first column");
2594                         return;
2595                 }
2596                 if (view->yoffset <= apply_step(opt_hscroll, view->width))
2597                         view->yoffset = 0;
2598                 else
2599                         view->yoffset -= apply_step(opt_hscroll, view->width);
2600                 redraw_view_from(view, 0);
2601                 report("");
2602                 return;
2603         case REQ_SCROLL_RIGHT:
2604                 view->yoffset += apply_step(opt_hscroll, view->width);
2605                 redraw_view(view);
2606                 report("");
2607                 return;
2608         case REQ_SCROLL_PAGE_DOWN:
2609                 lines = view->height;
2610         case REQ_SCROLL_LINE_DOWN:
2611                 if (view->offset + lines > view->lines)
2612                         lines = view->lines - view->offset;
2614                 if (lines == 0 || view->offset + view->height >= view->lines) {
2615                         report("Cannot scroll beyond the last line");
2616                         return;
2617                 }
2618                 break;
2620         case REQ_SCROLL_PAGE_UP:
2621                 lines = view->height;
2622         case REQ_SCROLL_LINE_UP:
2623                 if (lines > view->offset)
2624                         lines = view->offset;
2626                 if (lines == 0) {
2627                         report("Cannot scroll beyond the first line");
2628                         return;
2629                 }
2631                 lines = -lines;
2632                 break;
2634         default:
2635                 die("request %d not handled in switch", request);
2636         }
2638         do_scroll_view(view, lines);
2641 /* Cursor moving */
2642 static void
2643 move_view(struct view *view, enum request request)
2645         int scroll_steps = 0;
2646         int steps;
2648         switch (request) {
2649         case REQ_MOVE_FIRST_LINE:
2650                 steps = -view->lineno;
2651                 break;
2653         case REQ_MOVE_LAST_LINE:
2654                 steps = view->lines - view->lineno - 1;
2655                 break;
2657         case REQ_MOVE_PAGE_UP:
2658                 steps = view->height > view->lineno
2659                       ? -view->lineno : -view->height;
2660                 break;
2662         case REQ_MOVE_PAGE_DOWN:
2663                 steps = view->lineno + view->height >= view->lines
2664                       ? view->lines - view->lineno - 1 : view->height;
2665                 break;
2667         case REQ_MOVE_UP:
2668                 steps = -1;
2669                 break;
2671         case REQ_MOVE_DOWN:
2672                 steps = 1;
2673                 break;
2675         default:
2676                 die("request %d not handled in switch", request);
2677         }
2679         if (steps <= 0 && view->lineno == 0) {
2680                 report("Cannot move beyond the first line");
2681                 return;
2683         } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2684                 report("Cannot move beyond the last line");
2685                 return;
2686         }
2688         /* Move the current line */
2689         view->lineno += steps;
2690         assert(0 <= view->lineno && view->lineno < view->lines);
2692         /* Check whether the view needs to be scrolled */
2693         if (view->lineno < view->offset ||
2694             view->lineno >= view->offset + view->height) {
2695                 scroll_steps = steps;
2696                 if (steps < 0 && -steps > view->offset) {
2697                         scroll_steps = -view->offset;
2699                 } else if (steps > 0) {
2700                         if (view->lineno == view->lines - 1 &&
2701                             view->lines > view->height) {
2702                                 scroll_steps = view->lines - view->offset - 1;
2703                                 if (scroll_steps >= view->height)
2704                                         scroll_steps -= view->height - 1;
2705                         }
2706                 }
2707         }
2709         if (!view_is_displayed(view)) {
2710                 view->offset += scroll_steps;
2711                 assert(0 <= view->offset && view->offset < view->lines);
2712                 view->ops->select(view, &view->line[view->lineno]);
2713                 return;
2714         }
2716         /* Repaint the old "current" line if we be scrolling */
2717         if (ABS(steps) < view->height)
2718                 draw_view_line(view, view->lineno - steps - view->offset);
2720         if (scroll_steps) {
2721                 do_scroll_view(view, scroll_steps);
2722                 return;
2723         }
2725         /* Draw the current line */
2726         draw_view_line(view, view->lineno - view->offset);
2728         wnoutrefresh(view->win);
2729         report("");
2733 /*
2734  * Searching
2735  */
2737 static void search_view(struct view *view, enum request request);
2739 static bool
2740 grep_text(struct view *view, const char *text[])
2742         regmatch_t pmatch;
2743         size_t i;
2745         for (i = 0; text[i]; i++)
2746                 if (*text[i] &&
2747                     regexec(view->regex, text[i], 1, &pmatch, 0) != REG_NOMATCH)
2748                         return TRUE;
2749         return FALSE;
2752 static void
2753 select_view_line(struct view *view, unsigned long lineno)
2755         unsigned long old_lineno = view->lineno;
2756         unsigned long old_offset = view->offset;
2758         if (goto_view_line(view, view->offset, lineno)) {
2759                 if (view_is_displayed(view)) {
2760                         if (old_offset != view->offset) {
2761                                 redraw_view(view);
2762                         } else {
2763                                 draw_view_line(view, old_lineno - view->offset);
2764                                 draw_view_line(view, view->lineno - view->offset);
2765                                 wnoutrefresh(view->win);
2766                         }
2767                 } else {
2768                         view->ops->select(view, &view->line[view->lineno]);
2769                 }
2770         }
2773 static void
2774 find_next(struct view *view, enum request request)
2776         unsigned long lineno = view->lineno;
2777         int direction;
2779         if (!*view->grep) {
2780                 if (!*opt_search)
2781                         report("No previous search");
2782                 else
2783                         search_view(view, request);
2784                 return;
2785         }
2787         switch (request) {
2788         case REQ_SEARCH:
2789         case REQ_FIND_NEXT:
2790                 direction = 1;
2791                 break;
2793         case REQ_SEARCH_BACK:
2794         case REQ_FIND_PREV:
2795                 direction = -1;
2796                 break;
2798         default:
2799                 return;
2800         }
2802         if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
2803                 lineno += direction;
2805         /* Note, lineno is unsigned long so will wrap around in which case it
2806          * will become bigger than view->lines. */
2807         for (; lineno < view->lines; lineno += direction) {
2808                 if (view->ops->grep(view, &view->line[lineno])) {
2809                         select_view_line(view, lineno);
2810                         report("Line %ld matches '%s'", lineno + 1, view->grep);
2811                         return;
2812                 }
2813         }
2815         report("No match found for '%s'", view->grep);
2818 static void
2819 search_view(struct view *view, enum request request)
2821         int regex_err;
2823         if (view->regex) {
2824                 regfree(view->regex);
2825                 *view->grep = 0;
2826         } else {
2827                 view->regex = calloc(1, sizeof(*view->regex));
2828                 if (!view->regex)
2829                         return;
2830         }
2832         regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
2833         if (regex_err != 0) {
2834                 char buf[SIZEOF_STR] = "unknown error";
2836                 regerror(regex_err, view->regex, buf, sizeof(buf));
2837                 report("Search failed: %s", buf);
2838                 return;
2839         }
2841         string_copy(view->grep, opt_search);
2843         find_next(view, request);
2846 /*
2847  * Incremental updating
2848  */
2850 static void
2851 reset_view(struct view *view)
2853         int i;
2855         for (i = 0; i < view->lines; i++)
2856                 free(view->line[i].data);
2857         free(view->line);
2859         view->p_offset = view->offset;
2860         view->p_yoffset = view->yoffset;
2861         view->p_lineno = view->lineno;
2863         view->line = NULL;
2864         view->offset = 0;
2865         view->yoffset = 0;
2866         view->lines  = 0;
2867         view->lineno = 0;
2868         view->vid[0] = 0;
2869         view->update_secs = 0;
2872 static void
2873 free_argv(const char *argv[])
2875         int argc;
2877         for (argc = 0; argv[argc]; argc++)
2878                 free((void *) argv[argc]);
2881 static bool
2882 format_argv(const char *dst_argv[], const char *src_argv[], enum format_flags flags)
2884         char buf[SIZEOF_STR];
2885         int argc;
2886         bool noreplace = flags == FORMAT_NONE;
2888         free_argv(dst_argv);
2890         for (argc = 0; src_argv[argc]; argc++) {
2891                 const char *arg = src_argv[argc];
2892                 size_t bufpos = 0;
2894                 while (arg) {
2895                         char *next = strstr(arg, "%(");
2896                         int len = next - arg;
2897                         const char *value;
2899                         if (!next || noreplace) {
2900                                 if (flags == FORMAT_DASH && !strcmp(arg, "--"))
2901                                         noreplace = TRUE;
2902                                 len = strlen(arg);
2903                                 value = "";
2905                         } else if (!prefixcmp(next, "%(directory)")) {
2906                                 value = opt_path;
2908                         } else if (!prefixcmp(next, "%(file)")) {
2909                                 value = opt_file;
2911                         } else if (!prefixcmp(next, "%(ref)")) {
2912                                 value = *opt_ref ? opt_ref : "HEAD";
2914                         } else if (!prefixcmp(next, "%(head)")) {
2915                                 value = ref_head;
2917                         } else if (!prefixcmp(next, "%(commit)")) {
2918                                 value = ref_commit;
2920                         } else if (!prefixcmp(next, "%(blob)")) {
2921                                 value = ref_blob;
2923                         } else {
2924                                 report("Unknown replacement: `%s`", next);
2925                                 return FALSE;
2926                         }
2928                         if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
2929                                 return FALSE;
2931                         arg = next && !noreplace ? strchr(next, ')') + 1 : NULL;
2932                 }
2934                 dst_argv[argc] = strdup(buf);
2935                 if (!dst_argv[argc])
2936                         break;
2937         }
2939         dst_argv[argc] = NULL;
2941         return src_argv[argc] == NULL;
2944 static bool
2945 restore_view_position(struct view *view)
2947         if (!view->p_restore || (view->pipe && view->lines <= view->p_lineno))
2948                 return FALSE;
2950         /* Changing the view position cancels the restoring. */
2951         /* FIXME: Changing back to the first line is not detected. */
2952         if (view->offset != 0 || view->lineno != 0) {
2953                 view->p_restore = FALSE;
2954                 return FALSE;
2955         }
2957         if (goto_view_line(view, view->p_offset, view->p_lineno) &&
2958             view_is_displayed(view))
2959                 werase(view->win);
2961         view->yoffset = view->p_yoffset;
2962         view->p_restore = FALSE;
2964         return TRUE;
2967 static void
2968 end_update(struct view *view, bool force)
2970         if (!view->pipe)
2971                 return;
2972         while (!view->ops->read(view, NULL))
2973                 if (!force)
2974                         return;
2975         set_nonblocking_input(FALSE);
2976         if (force)
2977                 kill_io(view->pipe);
2978         done_io(view->pipe);
2979         view->pipe = NULL;
2982 static void
2983 setup_update(struct view *view, const char *vid)
2985         set_nonblocking_input(TRUE);
2986         reset_view(view);
2987         string_copy_rev(view->vid, vid);
2988         view->pipe = &view->io;
2989         view->start_time = time(NULL);
2992 static bool
2993 prepare_update(struct view *view, const char *argv[], const char *dir,
2994                enum format_flags flags)
2996         if (view->pipe)
2997                 end_update(view, TRUE);
2998         return init_io_rd(&view->io, argv, dir, flags);
3001 static bool
3002 prepare_update_file(struct view *view, const char *name)
3004         if (view->pipe)
3005                 end_update(view, TRUE);
3006         return io_open(&view->io, "%s", name);
3009 static bool
3010 begin_update(struct view *view, bool refresh)
3012         if (view->pipe)
3013                 end_update(view, TRUE);
3015         if (!refresh) {
3016                 if (view->ops->prepare) {
3017                         if (!view->ops->prepare(view))
3018                                 return FALSE;
3019                 } else if (!init_io_rd(&view->io, view->ops->argv, NULL, FORMAT_ALL)) {
3020                         return FALSE;
3021                 }
3023                 /* Put the current ref_* value to the view title ref
3024                  * member. This is needed by the blob view. Most other
3025                  * views sets it automatically after loading because the
3026                  * first line is a commit line. */
3027                 string_copy_rev(view->ref, view->id);
3028         }
3030         if (!start_io(&view->io))
3031                 return FALSE;
3033         setup_update(view, view->id);
3035         return TRUE;
3038 static bool
3039 update_view(struct view *view)
3041         char out_buffer[BUFSIZ * 2];
3042         char *line;
3043         /* Clear the view and redraw everything since the tree sorting
3044          * might have rearranged things. */
3045         bool redraw = view->lines == 0;
3046         bool can_read = TRUE;
3048         if (!view->pipe)
3049                 return TRUE;
3051         if (!io_can_read(view->pipe)) {
3052                 if (view->lines == 0 && view_is_displayed(view)) {
3053                         time_t secs = time(NULL) - view->start_time;
3055                         if (secs > 1 && secs > view->update_secs) {
3056                                 if (view->update_secs == 0)
3057                                         redraw_view(view);
3058                                 update_view_title(view);
3059                                 view->update_secs = secs;
3060                         }
3061                 }
3062                 return TRUE;
3063         }
3065         for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
3066                 if (opt_iconv_in != ICONV_NONE) {
3067                         ICONV_CONST char *inbuf = line;
3068                         size_t inlen = strlen(line) + 1;
3070                         char *outbuf = out_buffer;
3071                         size_t outlen = sizeof(out_buffer);
3073                         size_t ret;
3075                         ret = iconv(opt_iconv_in, &inbuf, &inlen, &outbuf, &outlen);
3076                         if (ret != (size_t) -1)
3077                                 line = out_buffer;
3078                 }
3080                 if (!view->ops->read(view, line)) {
3081                         report("Allocation failure");
3082                         end_update(view, TRUE);
3083                         return FALSE;
3084                 }
3085         }
3087         {
3088                 unsigned long lines = view->lines;
3089                 int digits;
3091                 for (digits = 0; lines; digits++)
3092                         lines /= 10;
3094                 /* Keep the displayed view in sync with line number scaling. */
3095                 if (digits != view->digits) {
3096                         view->digits = digits;
3097                         if (opt_line_number || view == VIEW(REQ_VIEW_BLAME))
3098                                 redraw = TRUE;
3099                 }
3100         }
3102         if (io_error(view->pipe)) {
3103                 report("Failed to read: %s", io_strerror(view->pipe));
3104                 end_update(view, TRUE);
3106         } else if (io_eof(view->pipe)) {
3107                 report("");
3108                 end_update(view, FALSE);
3109         }
3111         if (restore_view_position(view))
3112                 redraw = TRUE;
3114         if (!view_is_displayed(view))
3115                 return TRUE;
3117         if (redraw)
3118                 redraw_view_from(view, 0);
3119         else
3120                 redraw_view_dirty(view);
3122         /* Update the title _after_ the redraw so that if the redraw picks up a
3123          * commit reference in view->ref it'll be available here. */
3124         update_view_title(view);
3125         return TRUE;
3128 DEFINE_ALLOCATOR(realloc_lines, struct line, 256)
3130 static struct line *
3131 add_line_data(struct view *view, void *data, enum line_type type)
3133         struct line *line;
3135         if (!realloc_lines(&view->line, view->lines, 1))
3136                 return NULL;
3138         line = &view->line[view->lines++];
3139         memset(line, 0, sizeof(*line));
3140         line->type = type;
3141         line->data = data;
3142         line->dirty = 1;
3144         return line;
3147 static struct line *
3148 add_line_text(struct view *view, const char *text, enum line_type type)
3150         char *data = text ? strdup(text) : NULL;
3152         return data ? add_line_data(view, data, type) : NULL;
3155 static struct line *
3156 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
3158         char buf[SIZEOF_STR];
3159         va_list args;
3161         va_start(args, fmt);
3162         if (vsnprintf(buf, sizeof(buf), fmt, args) >= sizeof(buf))
3163                 buf[0] = 0;
3164         va_end(args);
3166         return buf[0] ? add_line_text(view, buf, type) : NULL;
3169 /*
3170  * View opening
3171  */
3173 enum open_flags {
3174         OPEN_DEFAULT = 0,       /* Use default view switching. */
3175         OPEN_SPLIT = 1,         /* Split current view. */
3176         OPEN_RELOAD = 4,        /* Reload view even if it is the current. */
3177         OPEN_REFRESH = 16,      /* Refresh view using previous command. */
3178         OPEN_PREPARED = 32,     /* Open already prepared command. */
3179 };
3181 static void
3182 open_view(struct view *prev, enum request request, enum open_flags flags)
3184         bool split = !!(flags & OPEN_SPLIT);
3185         bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
3186         bool nomaximize = !!(flags & OPEN_REFRESH);
3187         struct view *view = VIEW(request);
3188         int nviews = displayed_views();
3189         struct view *base_view = display[0];
3191         if (view == prev && nviews == 1 && !reload) {
3192                 report("Already in %s view", view->name);
3193                 return;
3194         }
3196         if (view->git_dir && !opt_git_dir[0]) {
3197                 report("The %s view is disabled in pager view", view->name);
3198                 return;
3199         }
3201         if (split) {
3202                 display[1] = view;
3203                 current_view = 1;
3204         } else if (!nomaximize) {
3205                 /* Maximize the current view. */
3206                 memset(display, 0, sizeof(display));
3207                 current_view = 0;
3208                 display[current_view] = view;
3209         }
3211         /* No parent signals that this is the first loaded view. */
3212         if (prev && view != prev) {
3213                 view->parent = prev;
3214         }
3216         /* Resize the view when switching between split- and full-screen,
3217          * or when switching between two different full-screen views. */
3218         if (nviews != displayed_views() ||
3219             (nviews == 1 && base_view != display[0]))
3220                 resize_display();
3222         if (view->ops->open) {
3223                 if (view->pipe)
3224                         end_update(view, TRUE);
3225                 if (!view->ops->open(view)) {
3226                         report("Failed to load %s view", view->name);
3227                         return;
3228                 }
3229                 restore_view_position(view);
3231         } else if ((reload || strcmp(view->vid, view->id)) &&
3232                    !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
3233                 report("Failed to load %s view", view->name);
3234                 return;
3235         }
3237         if (split && prev->lineno - prev->offset >= prev->height) {
3238                 /* Take the title line into account. */
3239                 int lines = prev->lineno - prev->offset - prev->height + 1;
3241                 /* Scroll the view that was split if the current line is
3242                  * outside the new limited view. */
3243                 do_scroll_view(prev, lines);
3244         }
3246         if (prev && view != prev && split && view_is_displayed(prev)) {
3247                 /* "Blur" the previous view. */
3248                 update_view_title(prev);
3249         }
3251         if (view->pipe && view->lines == 0) {
3252                 /* Clear the old view and let the incremental updating refill
3253                  * the screen. */
3254                 werase(view->win);
3255                 view->p_restore = flags & (OPEN_RELOAD | OPEN_REFRESH);
3256                 report("");
3257         } else if (view_is_displayed(view)) {
3258                 redraw_view(view);
3259                 report("");
3260         }
3263 static void
3264 open_external_viewer(const char *argv[], const char *dir)
3266         def_prog_mode();           /* save current tty modes */
3267         endwin();                  /* restore original tty modes */
3268         run_io_fg(argv, dir);
3269         fprintf(stderr, "Press Enter to continue");
3270         getc(opt_tty);
3271         reset_prog_mode();
3272         redraw_display(TRUE);
3275 static void
3276 open_mergetool(const char *file)
3278         const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
3280         open_external_viewer(mergetool_argv, opt_cdup);
3283 static void
3284 open_editor(bool from_root, const char *file)
3286         const char *editor_argv[] = { "vi", file, NULL };
3287         const char *editor;
3289         editor = getenv("GIT_EDITOR");
3290         if (!editor && *opt_editor)
3291                 editor = opt_editor;
3292         if (!editor)
3293                 editor = getenv("VISUAL");
3294         if (!editor)
3295                 editor = getenv("EDITOR");
3296         if (!editor)
3297                 editor = "vi";
3299         editor_argv[0] = editor;
3300         open_external_viewer(editor_argv, from_root ? opt_cdup : NULL);
3303 static void
3304 open_run_request(enum request request)
3306         struct run_request *req = get_run_request(request);
3307         const char *argv[ARRAY_SIZE(req->argv)] = { NULL };
3309         if (!req) {
3310                 report("Unknown run request");
3311                 return;
3312         }
3314         if (format_argv(argv, req->argv, FORMAT_ALL))
3315                 open_external_viewer(argv, NULL);
3316         free_argv(argv);
3319 /*
3320  * User request switch noodle
3321  */
3323 static int
3324 view_driver(struct view *view, enum request request)
3326         int i;
3328         if (request == REQ_NONE)
3329                 return TRUE;
3331         if (request > REQ_NONE) {
3332                 open_run_request(request);
3333                 /* FIXME: When all views can refresh always do this. */
3334                 if (view == VIEW(REQ_VIEW_STATUS) ||
3335                     view == VIEW(REQ_VIEW_MAIN) ||
3336                     view == VIEW(REQ_VIEW_LOG) ||
3337                     view == VIEW(REQ_VIEW_BRANCH) ||
3338                     view == VIEW(REQ_VIEW_STAGE))
3339                         request = REQ_REFRESH;
3340                 else
3341                         return TRUE;
3342         }
3344         if (view && view->lines) {
3345                 request = view->ops->request(view, request, &view->line[view->lineno]);
3346                 if (request == REQ_NONE)
3347                         return TRUE;
3348         }
3350         switch (request) {
3351         case REQ_MOVE_UP:
3352         case REQ_MOVE_DOWN:
3353         case REQ_MOVE_PAGE_UP:
3354         case REQ_MOVE_PAGE_DOWN:
3355         case REQ_MOVE_FIRST_LINE:
3356         case REQ_MOVE_LAST_LINE:
3357                 move_view(view, request);
3358                 break;
3360         case REQ_SCROLL_LEFT:
3361         case REQ_SCROLL_RIGHT:
3362         case REQ_SCROLL_LINE_DOWN:
3363         case REQ_SCROLL_LINE_UP:
3364         case REQ_SCROLL_PAGE_DOWN:
3365         case REQ_SCROLL_PAGE_UP:
3366                 scroll_view(view, request);
3367                 break;
3369         case REQ_VIEW_BLAME:
3370                 if (!opt_file[0]) {
3371                         report("No file chosen, press %s to open tree view",
3372                                get_key(view->keymap, REQ_VIEW_TREE));
3373                         break;
3374                 }
3375                 open_view(view, request, OPEN_DEFAULT);
3376                 break;
3378         case REQ_VIEW_BLOB:
3379                 if (!ref_blob[0]) {
3380                         report("No file chosen, press %s to open tree view",
3381                                get_key(view->keymap, REQ_VIEW_TREE));
3382                         break;
3383                 }
3384                 open_view(view, request, OPEN_DEFAULT);
3385                 break;
3387         case REQ_VIEW_PAGER:
3388                 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3389                         report("No pager content, press %s to run command from prompt",
3390                                get_key(view->keymap, REQ_PROMPT));
3391                         break;
3392                 }
3393                 open_view(view, request, OPEN_DEFAULT);
3394                 break;
3396         case REQ_VIEW_STAGE:
3397                 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3398                         report("No stage content, press %s to open the status view and choose file",
3399                                get_key(view->keymap, REQ_VIEW_STATUS));
3400                         break;
3401                 }
3402                 open_view(view, request, OPEN_DEFAULT);
3403                 break;
3405         case REQ_VIEW_STATUS:
3406                 if (opt_is_inside_work_tree == FALSE) {
3407                         report("The status view requires a working tree");
3408                         break;
3409                 }
3410                 open_view(view, request, OPEN_DEFAULT);
3411                 break;
3413         case REQ_VIEW_MAIN:
3414         case REQ_VIEW_DIFF:
3415         case REQ_VIEW_LOG:
3416         case REQ_VIEW_TREE:
3417         case REQ_VIEW_HELP:
3418         case REQ_VIEW_BRANCH:
3419                 open_view(view, request, OPEN_DEFAULT);
3420                 break;
3422         case REQ_NEXT:
3423         case REQ_PREVIOUS:
3424                 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3426                 if ((view == VIEW(REQ_VIEW_DIFF) &&
3427                      view->parent == VIEW(REQ_VIEW_MAIN)) ||
3428                    (view == VIEW(REQ_VIEW_DIFF) &&
3429                      view->parent == VIEW(REQ_VIEW_BLAME)) ||
3430                    (view == VIEW(REQ_VIEW_STAGE) &&
3431                      view->parent == VIEW(REQ_VIEW_STATUS)) ||
3432                    (view == VIEW(REQ_VIEW_BLOB) &&
3433                      view->parent == VIEW(REQ_VIEW_TREE)) ||
3434                    (view == VIEW(REQ_VIEW_MAIN) &&
3435                      view->parent == VIEW(REQ_VIEW_BRANCH))) {
3436                         int line;
3438                         view = view->parent;
3439                         line = view->lineno;
3440                         move_view(view, request);
3441                         if (view_is_displayed(view))
3442                                 update_view_title(view);
3443                         if (line != view->lineno)
3444                                 view->ops->request(view, REQ_ENTER,
3445                                                    &view->line[view->lineno]);
3447                 } else {
3448                         move_view(view, request);
3449                 }
3450                 break;
3452         case REQ_VIEW_NEXT:
3453         {
3454                 int nviews = displayed_views();
3455                 int next_view = (current_view + 1) % nviews;
3457                 if (next_view == current_view) {
3458                         report("Only one view is displayed");
3459                         break;
3460                 }
3462                 current_view = next_view;
3463                 /* Blur out the title of the previous view. */
3464                 update_view_title(view);
3465                 report("");
3466                 break;
3467         }
3468         case REQ_REFRESH:
3469                 report("Refreshing is not yet supported for the %s view", view->name);
3470                 break;
3472         case REQ_MAXIMIZE:
3473                 if (displayed_views() == 2)
3474                         maximize_view(view);
3475                 break;
3477         case REQ_OPTIONS:
3478                 open_option_menu();
3479                 break;
3481         case REQ_TOGGLE_LINENO:
3482                 toggle_view_option(&opt_line_number, "line numbers");
3483                 break;
3485         case REQ_TOGGLE_DATE:
3486                 toggle_date_option(&opt_date);
3487                 break;
3489         case REQ_TOGGLE_AUTHOR:
3490                 toggle_view_option(&opt_author, "author display");
3491                 break;
3493         case REQ_TOGGLE_REV_GRAPH:
3494                 toggle_view_option(&opt_rev_graph, "revision graph display");
3495                 break;
3497         case REQ_TOGGLE_REFS:
3498                 toggle_view_option(&opt_show_refs, "reference display");
3499                 break;
3501         case REQ_TOGGLE_SORT_FIELD:
3502         case REQ_TOGGLE_SORT_ORDER:
3503                 report("Sorting is not yet supported for the %s view", view->name);
3504                 break;
3506         case REQ_SEARCH:
3507         case REQ_SEARCH_BACK:
3508                 search_view(view, request);
3509                 break;
3511         case REQ_FIND_NEXT:
3512         case REQ_FIND_PREV:
3513                 find_next(view, request);
3514                 break;
3516         case REQ_STOP_LOADING:
3517                 for (i = 0; i < ARRAY_SIZE(views); i++) {
3518                         view = &views[i];
3519                         if (view->pipe)
3520                                 report("Stopped loading the %s view", view->name),
3521                         end_update(view, TRUE);
3522                 }
3523                 break;
3525         case REQ_SHOW_VERSION:
3526                 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3527                 return TRUE;
3529         case REQ_SCREEN_REDRAW:
3530                 redraw_display(TRUE);
3531                 break;
3533         case REQ_EDIT:
3534                 report("Nothing to edit");
3535                 break;
3537         case REQ_ENTER:
3538                 report("Nothing to enter");
3539                 break;
3541         case REQ_VIEW_CLOSE:
3542                 /* XXX: Mark closed views by letting view->parent point to the
3543                  * view itself. Parents to closed view should never be
3544                  * followed. */
3545                 if (view->parent &&
3546                     view->parent->parent != view->parent) {
3547                         maximize_view(view->parent);
3548                         view->parent = view;
3549                         break;
3550                 }
3551                 /* Fall-through */
3552         case REQ_QUIT:
3553                 return FALSE;
3555         default:
3556                 report("Unknown key, press %s for help",
3557                        get_key(view->keymap, REQ_VIEW_HELP));
3558                 return TRUE;
3559         }
3561         return TRUE;
3565 /*
3566  * View backend utilities
3567  */
3569 enum sort_field {
3570         ORDERBY_NAME,
3571         ORDERBY_DATE,
3572         ORDERBY_AUTHOR,
3573 };
3575 struct sort_state {
3576         const enum sort_field *fields;
3577         size_t size, current;
3578         bool reverse;
3579 };
3581 #define SORT_STATE(fields) { fields, ARRAY_SIZE(fields), 0 }
3582 #define get_sort_field(state) ((state).fields[(state).current])
3583 #define sort_order(state, result) ((state).reverse ? -(result) : (result))
3585 static void
3586 sort_view(struct view *view, enum request request, struct sort_state *state,
3587           int (*compare)(const void *, const void *))
3589         switch (request) {
3590         case REQ_TOGGLE_SORT_FIELD:
3591                 state->current = (state->current + 1) % state->size;
3592                 break;
3594         case REQ_TOGGLE_SORT_ORDER:
3595                 state->reverse = !state->reverse;
3596                 break;
3597         default:
3598                 die("Not a sort request");
3599         }
3601         qsort(view->line, view->lines, sizeof(*view->line), compare);
3602         redraw_view(view);
3605 DEFINE_ALLOCATOR(realloc_authors, const char *, 256)
3607 /* Small author cache to reduce memory consumption. It uses binary
3608  * search to lookup or find place to position new entries. No entries
3609  * are ever freed. */
3610 static const char *
3611 get_author(const char *name)
3613         static const char **authors;
3614         static size_t authors_size;
3615         int from = 0, to = authors_size - 1;
3617         while (from <= to) {
3618                 size_t pos = (to + from) / 2;
3619                 int cmp = strcmp(name, authors[pos]);
3621                 if (!cmp)
3622                         return authors[pos];
3624                 if (cmp < 0)
3625                         to = pos - 1;
3626                 else
3627                         from = pos + 1;
3628         }
3630         if (!realloc_authors(&authors, authors_size, 1))
3631                 return NULL;
3632         name = strdup(name);
3633         if (!name)
3634                 return NULL;
3636         memmove(authors + from + 1, authors + from, (authors_size - from) * sizeof(*authors));
3637         authors[from] = name;
3638         authors_size++;
3640         return name;
3643 static void
3644 parse_timezone(time_t *time, const char *zone)
3646         long tz;
3648         tz  = ('0' - zone[1]) * 60 * 60 * 10;
3649         tz += ('0' - zone[2]) * 60 * 60;
3650         tz += ('0' - zone[3]) * 60;
3651         tz += ('0' - zone[4]);
3653         if (zone[0] == '-')
3654                 tz = -tz;
3656         *time -= tz;
3659 /* Parse author lines where the name may be empty:
3660  *      author  <email@address.tld> 1138474660 +0100
3661  */
3662 static void
3663 parse_author_line(char *ident, const char **author, time_t *time)
3665         char *nameend = strchr(ident, '<');
3666         char *emailend = strchr(ident, '>');
3668         if (nameend && emailend)
3669                 *nameend = *emailend = 0;
3670         ident = chomp_string(ident);
3671         if (!*ident) {
3672                 if (nameend)
3673                         ident = chomp_string(nameend + 1);
3674                 if (!*ident)
3675                         ident = "Unknown";
3676         }
3678         *author = get_author(ident);
3680         /* Parse epoch and timezone */
3681         if (emailend && emailend[1] == ' ') {
3682                 char *secs = emailend + 2;
3683                 char *zone = strchr(secs, ' ');
3685                 *time = (time_t) atol(secs);
3687                 if (zone && strlen(zone) == STRING_SIZE(" +0700"))
3688                         parse_timezone(time, zone + 1);
3689         }
3692 static bool
3693 open_commit_parent_menu(char buf[SIZEOF_STR], int *parents)
3695         char rev[SIZEOF_REV];
3696         const char *revlist_argv[] = {
3697                 "git", "log", "--no-color", "-1", "--pretty=format:%s", rev, NULL
3698         };
3699         struct menu_item *items;
3700         char text[SIZEOF_STR];
3701         bool ok = TRUE;
3702         int i;
3704         items = calloc(*parents + 1, sizeof(*items));
3705         if (!items)
3706                 return FALSE;
3708         for (i = 0; i < *parents; i++) {
3709                 string_copy_rev(rev, &buf[SIZEOF_REV * i]);
3710                 if (!run_io_buf(revlist_argv, text, sizeof(text)) ||
3711                     !(items[i].text = strdup(text))) {
3712                         ok = FALSE;
3713                         break;
3714                 }
3715         }
3717         if (ok) {
3718                 *parents = 0;
3719                 ok = prompt_menu("Select parent", items, parents);
3720         }
3721         for (i = 0; items[i].text; i++)
3722                 free((char *) items[i].text);
3723         free(items);
3724         return ok;
3727 static bool
3728 select_commit_parent(const char *id, char rev[SIZEOF_REV], const char *path)
3730         char buf[SIZEOF_STR * 4];
3731         const char *revlist_argv[] = {
3732                 "git", "log", "--no-color", "-1",
3733                         "--pretty=format:%P", id, "--", path, NULL
3734         };
3735         int parents;
3737         if (!run_io_buf(revlist_argv, buf, sizeof(buf)) ||
3738             (parents = strlen(buf) / 40) < 0) {
3739                 report("Failed to get parent information");
3740                 return FALSE;
3742         } else if (parents == 0) {
3743                 if (path)
3744                         report("Path '%s' does not exist in the parent", path);
3745                 else
3746                         report("The selected commit has no parents");
3747                 return FALSE;
3748         }
3750         if (parents > 1 && !open_commit_parent_menu(buf, &parents))
3751                 return FALSE;
3753         string_copy_rev(rev, &buf[41 * parents]);
3754         return TRUE;
3757 /*
3758  * Pager backend
3759  */
3761 static bool
3762 pager_draw(struct view *view, struct line *line, unsigned int lineno)
3764         char text[SIZEOF_STR];
3766         if (opt_line_number && draw_lineno(view, lineno))
3767                 return TRUE;
3769         string_expand(text, sizeof(text), line->data, opt_tab_size);
3770         draw_text(view, line->type, text, TRUE);
3771         return TRUE;
3774 static bool
3775 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
3777         const char *describe_argv[] = { "git", "describe", commit_id, NULL };
3778         char ref[SIZEOF_STR];
3780         if (!run_io_buf(describe_argv, ref, sizeof(ref)) || !*ref)
3781                 return TRUE;
3783         /* This is the only fatal call, since it can "corrupt" the buffer. */
3784         if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
3785                 return FALSE;
3787         return TRUE;
3790 static void
3791 add_pager_refs(struct view *view, struct line *line)
3793         char buf[SIZEOF_STR];
3794         char *commit_id = (char *)line->data + STRING_SIZE("commit ");
3795         struct ref_list *list;
3796         size_t bufpos = 0, i;
3797         const char *sep = "Refs: ";
3798         bool is_tag = FALSE;
3800         assert(line->type == LINE_COMMIT);
3802         list = get_ref_list(commit_id);
3803         if (!list) {
3804                 if (view == VIEW(REQ_VIEW_DIFF))
3805                         goto try_add_describe_ref;
3806                 return;
3807         }
3809         for (i = 0; i < list->size; i++) {
3810                 struct ref *ref = list->refs[i];
3811                 const char *fmt = ref->tag    ? "%s[%s]" :
3812                                   ref->remote ? "%s<%s>" : "%s%s";
3814                 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
3815                         return;
3816                 sep = ", ";
3817                 if (ref->tag)
3818                         is_tag = TRUE;
3819         }
3821         if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
3822 try_add_describe_ref:
3823                 /* Add <tag>-g<commit_id> "fake" reference. */
3824                 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
3825                         return;
3826         }
3828         if (bufpos == 0)
3829                 return;
3831         add_line_text(view, buf, LINE_PP_REFS);
3834 static bool
3835 pager_read(struct view *view, char *data)
3837         struct line *line;
3839         if (!data)
3840                 return TRUE;
3842         line = add_line_text(view, data, get_line_type(data));
3843         if (!line)
3844                 return FALSE;
3846         if (line->type == LINE_COMMIT &&
3847             (view == VIEW(REQ_VIEW_DIFF) ||
3848              view == VIEW(REQ_VIEW_LOG)))
3849                 add_pager_refs(view, line);
3851         return TRUE;
3854 static enum request
3855 pager_request(struct view *view, enum request request, struct line *line)
3857         int split = 0;
3859         if (request != REQ_ENTER)
3860                 return request;
3862         if (line->type == LINE_COMMIT &&
3863            (view == VIEW(REQ_VIEW_LOG) ||
3864             view == VIEW(REQ_VIEW_PAGER))) {
3865                 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
3866                 split = 1;
3867         }
3869         /* Always scroll the view even if it was split. That way
3870          * you can use Enter to scroll through the log view and
3871          * split open each commit diff. */
3872         scroll_view(view, REQ_SCROLL_LINE_DOWN);
3874         /* FIXME: A minor workaround. Scrolling the view will call report("")
3875          * but if we are scrolling a non-current view this won't properly
3876          * update the view title. */
3877         if (split)
3878                 update_view_title(view);
3880         return REQ_NONE;
3883 static bool
3884 pager_grep(struct view *view, struct line *line)
3886         const char *text[] = { line->data, NULL };
3888         return grep_text(view, text);
3891 static void
3892 pager_select(struct view *view, struct line *line)
3894         if (line->type == LINE_COMMIT) {
3895                 char *text = (char *)line->data + STRING_SIZE("commit ");
3897                 if (view != VIEW(REQ_VIEW_PAGER))
3898                         string_copy_rev(view->ref, text);
3899                 string_copy_rev(ref_commit, text);
3900         }
3903 static struct view_ops pager_ops = {
3904         "line",
3905         NULL,
3906         NULL,
3907         pager_read,
3908         pager_draw,
3909         pager_request,
3910         pager_grep,
3911         pager_select,
3912 };
3914 static const char *log_argv[SIZEOF_ARG] = {
3915         "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
3916 };
3918 static enum request
3919 log_request(struct view *view, enum request request, struct line *line)
3921         switch (request) {
3922         case REQ_REFRESH:
3923                 load_refs();
3924                 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
3925                 return REQ_NONE;
3926         default:
3927                 return pager_request(view, request, line);
3928         }
3931 static struct view_ops log_ops = {
3932         "line",
3933         log_argv,
3934         NULL,
3935         pager_read,
3936         pager_draw,
3937         log_request,
3938         pager_grep,
3939         pager_select,
3940 };
3942 static const char *diff_argv[SIZEOF_ARG] = {
3943         "git", "show", "--pretty=fuller", "--no-color", "--root",
3944                 "--patch-with-stat", "--find-copies-harder", "-C", "%(commit)", NULL
3945 };
3947 static struct view_ops diff_ops = {
3948         "line",
3949         diff_argv,
3950         NULL,
3951         pager_read,
3952         pager_draw,
3953         pager_request,
3954         pager_grep,
3955         pager_select,
3956 };
3958 /*
3959  * Help backend
3960  */
3962 static bool help_keymap_hidden[ARRAY_SIZE(keymap_table)];
3964 static char *
3965 help_name(char buf[SIZEOF_STR], const char *name, size_t namelen)
3967         int bufpos;
3969         for (bufpos = 0; bufpos <= namelen; bufpos++) {
3970                 buf[bufpos] = tolower(name[bufpos]);
3971                 if (buf[bufpos] == '_')
3972                         buf[bufpos] = '-';
3973         }
3975         buf[bufpos] = 0;
3976         return buf;
3979 #define help_keymap_name(buf, keymap) \
3980         help_name(buf, keymap_table[keymap].name, keymap_table[keymap].namelen)
3982 static bool
3983 help_open_keymap_title(struct view *view, enum keymap keymap)
3985         char buf[SIZEOF_STR];
3986         struct line *line;
3988         line = add_line_format(view, LINE_HELP_KEYMAP, "[%c] %s bindings",
3989                                help_keymap_hidden[keymap] ? '+' : '-',
3990                                help_keymap_name(buf, keymap));
3991         if (line)
3992                 line->other = keymap;
3994         return help_keymap_hidden[keymap];
3997 static void
3998 help_open_keymap(struct view *view, enum keymap keymap)
4000         const char *group = NULL;
4001         char buf[SIZEOF_STR];
4002         size_t bufpos;
4003         bool add_title = TRUE;
4004         int i;
4006         for (i = 0; i < ARRAY_SIZE(req_info); i++) {
4007                 const char *key = NULL;
4009                 if (req_info[i].request == REQ_NONE)
4010                         continue;
4012                 if (!req_info[i].request) {
4013                         group = req_info[i].help;
4014                         continue;
4015                 }
4017                 key = get_keys(keymap, req_info[i].request, TRUE);
4018                 if (!key || !*key)
4019                         continue;
4021                 if (add_title && help_open_keymap_title(view, keymap))
4022                         return;
4023                 add_title = false;
4025                 if (group) {
4026                         add_line_text(view, group, LINE_HELP_GROUP);
4027                         group = NULL;
4028                 }
4030                 add_line_format(view, LINE_DEFAULT, "    %-25s %-20s %s", key,
4031                                 help_name(buf, req_info[i].name, req_info[i].namelen),
4032                                 req_info[i].help);
4033         }
4035         group = "External commands:";
4037         for (i = 0; i < run_requests; i++) {
4038                 struct run_request *req = get_run_request(REQ_NONE + i + 1);
4039                 const char *key;
4040                 int argc;
4042                 if (!req || req->keymap != keymap)
4043                         continue;
4045                 key = get_key_name(req->key);
4046                 if (!*key)
4047                         key = "(no key defined)";
4049                 if (add_title && help_open_keymap_title(view, keymap))
4050                         return;
4051                 if (group) {
4052                         add_line_text(view, group, LINE_HELP_GROUP);
4053                         group = NULL;
4054                 }
4056                 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
4057                         if (!string_format_from(buf, &bufpos, "%s%s",
4058                                                 argc ? " " : "", req->argv[argc]))
4059                                 return;
4061                 add_line_format(view, LINE_DEFAULT, "    %-25s `%s`", key, buf);
4062         }
4065 static bool
4066 help_open(struct view *view)
4068         enum keymap keymap;
4070         reset_view(view);
4071         add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
4072         add_line_text(view, "", LINE_DEFAULT);
4074         for (keymap = 0; keymap < ARRAY_SIZE(keymap_table); keymap++)
4075                 help_open_keymap(view, keymap);
4077         return TRUE;
4080 static enum request
4081 help_request(struct view *view, enum request request, struct line *line)
4083         switch (request) {
4084         case REQ_ENTER:
4085                 if (line->type == LINE_HELP_KEYMAP) {
4086                         help_keymap_hidden[line->other] =
4087                                 !help_keymap_hidden[line->other];
4088                         view->p_restore = TRUE;
4089                         open_view(view, REQ_VIEW_HELP, OPEN_REFRESH);
4090                 }
4092                 return REQ_NONE;
4093         default:
4094                 return pager_request(view, request, line);
4095         }
4098 static struct view_ops help_ops = {
4099         "line",
4100         NULL,
4101         help_open,
4102         NULL,
4103         pager_draw,
4104         help_request,
4105         pager_grep,
4106         pager_select,
4107 };
4110 /*
4111  * Tree backend
4112  */
4114 struct tree_stack_entry {
4115         struct tree_stack_entry *prev;  /* Entry below this in the stack */
4116         unsigned long lineno;           /* Line number to restore */
4117         char *name;                     /* Position of name in opt_path */
4118 };
4120 /* The top of the path stack. */
4121 static struct tree_stack_entry *tree_stack = NULL;
4122 unsigned long tree_lineno = 0;
4124 static void
4125 pop_tree_stack_entry(void)
4127         struct tree_stack_entry *entry = tree_stack;
4129         tree_lineno = entry->lineno;
4130         entry->name[0] = 0;
4131         tree_stack = entry->prev;
4132         free(entry);
4135 static void
4136 push_tree_stack_entry(const char *name, unsigned long lineno)
4138         struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
4139         size_t pathlen = strlen(opt_path);
4141         if (!entry)
4142                 return;
4144         entry->prev = tree_stack;
4145         entry->name = opt_path + pathlen;
4146         tree_stack = entry;
4148         if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
4149                 pop_tree_stack_entry();
4150                 return;
4151         }
4153         /* Move the current line to the first tree entry. */
4154         tree_lineno = 1;
4155         entry->lineno = lineno;
4158 /* Parse output from git-ls-tree(1):
4159  *
4160  * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
4161  */
4163 #define SIZEOF_TREE_ATTR \
4164         STRING_SIZE("100644 blob f931e1d229c3e185caad4449bf5b66ed72462657\t")
4166 #define SIZEOF_TREE_MODE \
4167         STRING_SIZE("100644 ")
4169 #define TREE_ID_OFFSET \
4170         STRING_SIZE("100644 blob ")
4172 struct tree_entry {
4173         char id[SIZEOF_REV];
4174         mode_t mode;
4175         time_t time;                    /* Date from the author ident. */
4176         const char *author;             /* Author of the commit. */
4177         char name[1];
4178 };
4180 static const char *
4181 tree_path(const struct line *line)
4183         return ((struct tree_entry *) line->data)->name;
4186 static int
4187 tree_compare_entry(const struct line *line1, const struct line *line2)
4189         if (line1->type != line2->type)
4190                 return line1->type == LINE_TREE_DIR ? -1 : 1;
4191         return strcmp(tree_path(line1), tree_path(line2));
4194 static const enum sort_field tree_sort_fields[] = {
4195         ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
4196 };
4197 static struct sort_state tree_sort_state = SORT_STATE(tree_sort_fields);
4199 static int
4200 tree_compare(const void *l1, const void *l2)
4202         const struct line *line1 = (const struct line *) l1;
4203         const struct line *line2 = (const struct line *) l2;
4204         const struct tree_entry *entry1 = ((const struct line *) l1)->data;
4205         const struct tree_entry *entry2 = ((const struct line *) l2)->data;
4207         if (line1->type == LINE_TREE_HEAD)
4208                 return -1;
4209         if (line2->type == LINE_TREE_HEAD)
4210                 return 1;
4212         switch (get_sort_field(tree_sort_state)) {
4213         case ORDERBY_DATE:
4214                 return sort_order(tree_sort_state, entry1->time - entry2->time);
4216         case ORDERBY_AUTHOR:
4217                 return sort_order(tree_sort_state, strcmp(entry1->author, entry2->author));
4219         case ORDERBY_NAME:
4220         default:
4221                 return sort_order(tree_sort_state, tree_compare_entry(line1, line2));
4222         }
4226 static struct line *
4227 tree_entry(struct view *view, enum line_type type, const char *path,
4228            const char *mode, const char *id)
4230         struct tree_entry *entry = calloc(1, sizeof(*entry) + strlen(path));
4231         struct line *line = entry ? add_line_data(view, entry, type) : NULL;
4233         if (!entry || !line) {
4234                 free(entry);
4235                 return NULL;
4236         }
4238         strncpy(entry->name, path, strlen(path));
4239         if (mode)
4240                 entry->mode = strtoul(mode, NULL, 8);
4241         if (id)
4242                 string_copy_rev(entry->id, id);
4244         return line;
4247 static bool
4248 tree_read_date(struct view *view, char *text, bool *read_date)
4250         static const char *author_name;
4251         static time_t author_time;
4253         if (!text && *read_date) {
4254                 *read_date = FALSE;
4255                 return TRUE;
4257         } else if (!text) {
4258                 char *path = *opt_path ? opt_path : ".";
4259                 /* Find next entry to process */
4260                 const char *log_file[] = {
4261                         "git", "log", "--no-color", "--pretty=raw",
4262                                 "--cc", "--raw", view->id, "--", path, NULL
4263                 };
4264                 struct io io = {};
4266                 if (!view->lines) {
4267                         tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL);
4268                         report("Tree is empty");
4269                         return TRUE;
4270                 }
4272                 if (!run_io_rd(&io, log_file, opt_cdup, FORMAT_NONE)) {
4273                         report("Failed to load tree data");
4274                         return TRUE;
4275                 }
4277                 done_io(view->pipe);
4278                 view->io = io;
4279                 *read_date = TRUE;
4280                 return FALSE;
4282         } else if (*text == 'a' && get_line_type(text) == LINE_AUTHOR) {
4283                 parse_author_line(text + STRING_SIZE("author "),
4284                                   &author_name, &author_time);
4286         } else if (*text == ':') {
4287                 char *pos;
4288                 size_t annotated = 1;
4289                 size_t i;
4291                 pos = strchr(text, '\t');
4292                 if (!pos)
4293                         return TRUE;
4294                 text = pos + 1;
4295                 if (*opt_path && !strncmp(text, opt_path, strlen(opt_path)))
4296                         text += strlen(opt_path);
4297                 pos = strchr(text, '/');
4298                 if (pos)
4299                         *pos = 0;
4301                 for (i = 1; i < view->lines; i++) {
4302                         struct line *line = &view->line[i];
4303                         struct tree_entry *entry = line->data;
4305                         annotated += !!entry->author;
4306                         if (entry->author || strcmp(entry->name, text))
4307                                 continue;
4309                         entry->author = author_name;
4310                         entry->time = author_time;
4311                         line->dirty = 1;
4312                         break;
4313                 }
4315                 if (annotated == view->lines)
4316                         kill_io(view->pipe);
4317         }
4318         return TRUE;
4321 static bool
4322 tree_read(struct view *view, char *text)
4324         static bool read_date = FALSE;
4325         struct tree_entry *data;
4326         struct line *entry, *line;
4327         enum line_type type;
4328         size_t textlen = text ? strlen(text) : 0;
4329         char *path = text + SIZEOF_TREE_ATTR;
4331         if (read_date || !text)
4332                 return tree_read_date(view, text, &read_date);
4334         if (textlen <= SIZEOF_TREE_ATTR)
4335                 return FALSE;
4336         if (view->lines == 0 &&
4337             !tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL))
4338                 return FALSE;
4340         /* Strip the path part ... */
4341         if (*opt_path) {
4342                 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
4343                 size_t striplen = strlen(opt_path);
4345                 if (pathlen > striplen)
4346                         memmove(path, path + striplen,
4347                                 pathlen - striplen + 1);
4349                 /* Insert "link" to parent directory. */
4350                 if (view->lines == 1 &&
4351                     !tree_entry(view, LINE_TREE_DIR, "..", "040000", view->ref))
4352                         return FALSE;
4353         }
4355         type = text[SIZEOF_TREE_MODE] == 't' ? LINE_TREE_DIR : LINE_TREE_FILE;
4356         entry = tree_entry(view, type, path, text, text + TREE_ID_OFFSET);
4357         if (!entry)
4358                 return FALSE;
4359         data = entry->data;
4361         /* Skip "Directory ..." and ".." line. */
4362         for (line = &view->line[1 + !!*opt_path]; line < entry; line++) {
4363                 if (tree_compare_entry(line, entry) <= 0)
4364                         continue;
4366                 memmove(line + 1, line, (entry - line) * sizeof(*entry));
4368                 line->data = data;
4369                 line->type = type;
4370                 for (; line <= entry; line++)
4371                         line->dirty = line->cleareol = 1;
4372                 return TRUE;
4373         }
4375         if (tree_lineno > view->lineno) {
4376                 view->lineno = tree_lineno;
4377                 tree_lineno = 0;
4378         }
4380         return TRUE;
4383 static bool
4384 tree_draw(struct view *view, struct line *line, unsigned int lineno)
4386         struct tree_entry *entry = line->data;
4388         if (line->type == LINE_TREE_HEAD) {
4389                 if (draw_text(view, line->type, "Directory path /", TRUE))
4390                         return TRUE;
4391         } else {
4392                 if (draw_mode(view, entry->mode))
4393                         return TRUE;
4395                 if (opt_author && draw_author(view, entry->author))
4396                         return TRUE;
4398                 if (opt_date && draw_date(view, entry->author ? &entry->time : NULL))
4399                         return TRUE;
4400         }
4401         if (draw_text(view, line->type, entry->name, TRUE))
4402                 return TRUE;
4403         return TRUE;
4406 static void
4407 open_blob_editor()
4409         char file[SIZEOF_STR] = "/tmp/tigblob.XXXXXX";
4410         int fd = mkstemp(file);
4412         if (fd == -1)
4413                 report("Failed to create temporary file");
4414         else if (!run_io_append(blob_ops.argv, FORMAT_ALL, fd))
4415                 report("Failed to save blob data to file");
4416         else
4417                 open_editor(FALSE, file);
4418         if (fd != -1)
4419                 unlink(file);
4422 static enum request
4423 tree_request(struct view *view, enum request request, struct line *line)
4425         enum open_flags flags;
4427         switch (request) {
4428         case REQ_VIEW_BLAME:
4429                 if (line->type != LINE_TREE_FILE) {
4430                         report("Blame only supported for files");
4431                         return REQ_NONE;
4432                 }
4434                 string_copy(opt_ref, view->vid);
4435                 return request;
4437         case REQ_EDIT:
4438                 if (line->type != LINE_TREE_FILE) {
4439                         report("Edit only supported for files");
4440                 } else if (!is_head_commit(view->vid)) {
4441                         open_blob_editor();
4442                 } else {
4443                         open_editor(TRUE, opt_file);
4444                 }
4445                 return REQ_NONE;
4447         case REQ_TOGGLE_SORT_FIELD:
4448         case REQ_TOGGLE_SORT_ORDER:
4449                 sort_view(view, request, &tree_sort_state, tree_compare);
4450                 return REQ_NONE;
4452         case REQ_PARENT:
4453                 if (!*opt_path) {
4454                         /* quit view if at top of tree */
4455                         return REQ_VIEW_CLOSE;
4456                 }
4457                 /* fake 'cd  ..' */
4458                 line = &view->line[1];
4459                 break;
4461         case REQ_ENTER:
4462                 break;
4464         default:
4465                 return request;
4466         }
4468         /* Cleanup the stack if the tree view is at a different tree. */
4469         while (!*opt_path && tree_stack)
4470                 pop_tree_stack_entry();
4472         switch (line->type) {
4473         case LINE_TREE_DIR:
4474                 /* Depending on whether it is a subdirectory or parent link
4475                  * mangle the path buffer. */
4476                 if (line == &view->line[1] && *opt_path) {
4477                         pop_tree_stack_entry();
4479                 } else {
4480                         const char *basename = tree_path(line);
4482                         push_tree_stack_entry(basename, view->lineno);
4483                 }
4485                 /* Trees and subtrees share the same ID, so they are not not
4486                  * unique like blobs. */
4487                 flags = OPEN_RELOAD;
4488                 request = REQ_VIEW_TREE;
4489                 break;
4491         case LINE_TREE_FILE:
4492                 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4493                 request = REQ_VIEW_BLOB;
4494                 break;
4496         default:
4497                 return REQ_NONE;
4498         }
4500         open_view(view, request, flags);
4501         if (request == REQ_VIEW_TREE)
4502                 view->lineno = tree_lineno;
4504         return REQ_NONE;
4507 static bool
4508 tree_grep(struct view *view, struct line *line)
4510         struct tree_entry *entry = line->data;
4511         const char *text[] = {
4512                 entry->name,
4513                 opt_author ? entry->author : "",
4514                 opt_date ? mkdate(&entry->time) : "",
4515                 NULL
4516         };
4518         return grep_text(view, text);
4521 static void
4522 tree_select(struct view *view, struct line *line)
4524         struct tree_entry *entry = line->data;
4526         if (line->type == LINE_TREE_FILE) {
4527                 string_copy_rev(ref_blob, entry->id);
4528                 string_format(opt_file, "%s%s", opt_path, tree_path(line));
4530         } else if (line->type != LINE_TREE_DIR) {
4531                 return;
4532         }
4534         string_copy_rev(view->ref, entry->id);
4537 static bool
4538 tree_prepare(struct view *view)
4540         if (view->lines == 0 && opt_prefix[0]) {
4541                 char *pos = opt_prefix;
4543                 while (pos && *pos) {
4544                         char *end = strchr(pos, '/');
4546                         if (end)
4547                                 *end = 0;
4548                         push_tree_stack_entry(pos, 0);
4549                         pos = end;
4550                         if (end) {
4551                                 *end = '/';
4552                                 pos++;
4553                         }
4554                 }
4556         } else if (strcmp(view->vid, view->id)) {
4557                 opt_path[0] = 0;
4558         }
4560         return init_io_rd(&view->io, view->ops->argv, opt_cdup, FORMAT_ALL);
4563 static const char *tree_argv[SIZEOF_ARG] = {
4564         "git", "ls-tree", "%(commit)", "%(directory)", NULL
4565 };
4567 static struct view_ops tree_ops = {
4568         "file",
4569         tree_argv,
4570         NULL,
4571         tree_read,
4572         tree_draw,
4573         tree_request,
4574         tree_grep,
4575         tree_select,
4576         tree_prepare,
4577 };
4579 static bool
4580 blob_read(struct view *view, char *line)
4582         if (!line)
4583                 return TRUE;
4584         return add_line_text(view, line, LINE_DEFAULT) != NULL;
4587 static enum request
4588 blob_request(struct view *view, enum request request, struct line *line)
4590         switch (request) {
4591         case REQ_EDIT:
4592                 open_blob_editor();
4593                 return REQ_NONE;
4594         default:
4595                 return pager_request(view, request, line);
4596         }
4599 static const char *blob_argv[SIZEOF_ARG] = {
4600         "git", "cat-file", "blob", "%(blob)", NULL
4601 };
4603 static struct view_ops blob_ops = {
4604         "line",
4605         blob_argv,
4606         NULL,
4607         blob_read,
4608         pager_draw,
4609         blob_request,
4610         pager_grep,
4611         pager_select,
4612 };
4614 /*
4615  * Blame backend
4616  *
4617  * Loading the blame view is a two phase job:
4618  *
4619  *  1. File content is read either using opt_file from the
4620  *     filesystem or using git-cat-file.
4621  *  2. Then blame information is incrementally added by
4622  *     reading output from git-blame.
4623  */
4625 static const char *blame_head_argv[] = {
4626         "git", "blame", "--incremental", "--", "%(file)", NULL
4627 };
4629 static const char *blame_ref_argv[] = {
4630         "git", "blame", "--incremental", "%(ref)", "--", "%(file)", NULL
4631 };
4633 static const char *blame_cat_file_argv[] = {
4634         "git", "cat-file", "blob", "%(ref):%(file)", NULL
4635 };
4637 struct blame_commit {
4638         char id[SIZEOF_REV];            /* SHA1 ID. */
4639         char title[128];                /* First line of the commit message. */
4640         const char *author;             /* Author of the commit. */
4641         time_t time;                    /* Date from the author ident. */
4642         char filename[128];             /* Name of file. */
4643         bool has_previous;              /* Was a "previous" line detected. */
4644 };
4646 struct blame {
4647         struct blame_commit *commit;
4648         unsigned long lineno;
4649         char text[1];
4650 };
4652 static bool
4653 blame_open(struct view *view)
4655         char path[SIZEOF_STR];
4657         if (!view->parent && *opt_prefix) {
4658                 string_copy(path, opt_file);
4659                 if (!string_format(opt_file, "%s%s", opt_prefix, path))
4660                         return FALSE;
4661         }
4663         if (*opt_ref || !io_open(&view->io, "%s%s", opt_cdup, opt_file)) {
4664                 if (!run_io_rd(&view->io, blame_cat_file_argv, opt_cdup, FORMAT_ALL))
4665                         return FALSE;
4666         }
4668         setup_update(view, opt_file);
4669         string_format(view->ref, "%s ...", opt_file);
4671         return TRUE;
4674 static struct blame_commit *
4675 get_blame_commit(struct view *view, const char *id)
4677         size_t i;
4679         for (i = 0; i < view->lines; i++) {
4680                 struct blame *blame = view->line[i].data;
4682                 if (!blame->commit)
4683                         continue;
4685                 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
4686                         return blame->commit;
4687         }
4689         {
4690                 struct blame_commit *commit = calloc(1, sizeof(*commit));
4692                 if (commit)
4693                         string_ncopy(commit->id, id, SIZEOF_REV);
4694                 return commit;
4695         }
4698 static bool
4699 parse_number(const char **posref, size_t *number, size_t min, size_t max)
4701         const char *pos = *posref;
4703         *posref = NULL;
4704         pos = strchr(pos + 1, ' ');
4705         if (!pos || !isdigit(pos[1]))
4706                 return FALSE;
4707         *number = atoi(pos + 1);
4708         if (*number < min || *number > max)
4709                 return FALSE;
4711         *posref = pos;
4712         return TRUE;
4715 static struct blame_commit *
4716 parse_blame_commit(struct view *view, const char *text, int *blamed)
4718         struct blame_commit *commit;
4719         struct blame *blame;
4720         const char *pos = text + SIZEOF_REV - 2;
4721         size_t orig_lineno = 0;
4722         size_t lineno;
4723         size_t group;
4725         if (strlen(text) <= SIZEOF_REV || pos[1] != ' ')
4726                 return NULL;
4728         if (!parse_number(&pos, &orig_lineno, 1, 9999999) ||
4729             !parse_number(&pos, &lineno, 1, view->lines) ||
4730             !parse_number(&pos, &group, 1, view->lines - lineno + 1))
4731                 return NULL;
4733         commit = get_blame_commit(view, text);
4734         if (!commit)
4735                 return NULL;
4737         *blamed += group;
4738         while (group--) {
4739                 struct line *line = &view->line[lineno + group - 1];
4741                 blame = line->data;
4742                 blame->commit = commit;
4743                 blame->lineno = orig_lineno + group - 1;
4744                 line->dirty = 1;
4745         }
4747         return commit;
4750 static bool
4751 blame_read_file(struct view *view, const char *line, bool *read_file)
4753         if (!line) {
4754                 const char **argv = *opt_ref ? blame_ref_argv : blame_head_argv;
4755                 struct io io = {};
4757                 if (view->lines == 0 && !view->parent)
4758                         die("No blame exist for %s", view->vid);
4760                 if (view->lines == 0 || !run_io_rd(&io, argv, opt_cdup, FORMAT_ALL)) {
4761                         report("Failed to load blame data");
4762                         return TRUE;
4763                 }
4765                 done_io(view->pipe);
4766                 view->io = io;
4767                 *read_file = FALSE;
4768                 return FALSE;
4770         } else {
4771                 size_t linelen = strlen(line);
4772                 struct blame *blame = malloc(sizeof(*blame) + linelen);
4774                 if (!blame)
4775                         return FALSE;
4777                 blame->commit = NULL;
4778                 strncpy(blame->text, line, linelen);
4779                 blame->text[linelen] = 0;
4780                 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
4781         }
4784 static bool
4785 match_blame_header(const char *name, char **line)
4787         size_t namelen = strlen(name);
4788         bool matched = !strncmp(name, *line, namelen);
4790         if (matched)
4791                 *line += namelen;
4793         return matched;
4796 static bool
4797 blame_read(struct view *view, char *line)
4799         static struct blame_commit *commit = NULL;
4800         static int blamed = 0;
4801         static bool read_file = TRUE;
4803         if (read_file)
4804                 return blame_read_file(view, line, &read_file);
4806         if (!line) {
4807                 /* Reset all! */
4808                 commit = NULL;
4809                 blamed = 0;
4810                 read_file = TRUE;
4811                 string_format(view->ref, "%s", view->vid);
4812                 if (view_is_displayed(view)) {
4813                         update_view_title(view);
4814                         redraw_view_from(view, 0);
4815                 }
4816                 return TRUE;
4817         }
4819         if (!commit) {
4820                 commit = parse_blame_commit(view, line, &blamed);
4821                 string_format(view->ref, "%s %2d%%", view->vid,
4822                               view->lines ? blamed * 100 / view->lines : 0);
4824         } else if (match_blame_header("author ", &line)) {
4825                 commit->author = get_author(line);
4827         } else if (match_blame_header("author-time ", &line)) {
4828                 commit->time = (time_t) atol(line);
4830         } else if (match_blame_header("author-tz ", &line)) {
4831                 parse_timezone(&commit->time, line);
4833         } else if (match_blame_header("summary ", &line)) {
4834                 string_ncopy(commit->title, line, strlen(line));
4836         } else if (match_blame_header("previous ", &line)) {
4837                 commit->has_previous = TRUE;
4839         } else if (match_blame_header("filename ", &line)) {
4840                 string_ncopy(commit->filename, line, strlen(line));
4841                 commit = NULL;
4842         }
4844         return TRUE;
4847 static bool
4848 blame_draw(struct view *view, struct line *line, unsigned int lineno)
4850         struct blame *blame = line->data;
4851         time_t *time = NULL;
4852         const char *id = NULL, *author = NULL;
4853         char text[SIZEOF_STR];
4855         if (blame->commit && *blame->commit->filename) {
4856                 id = blame->commit->id;
4857                 author = blame->commit->author;
4858                 time = &blame->commit->time;
4859         }
4861         if (opt_date && draw_date(view, time))
4862                 return TRUE;
4864         if (opt_author && draw_author(view, author))
4865                 return TRUE;
4867         if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
4868                 return TRUE;
4870         if (draw_lineno(view, lineno))
4871                 return TRUE;
4873         string_expand(text, sizeof(text), blame->text, opt_tab_size);
4874         draw_text(view, LINE_DEFAULT, text, TRUE);
4875         return TRUE;
4878 static bool
4879 check_blame_commit(struct blame *blame, bool check_null_id)
4881         if (!blame->commit)
4882                 report("Commit data not loaded yet");
4883         else if (check_null_id && !strcmp(blame->commit->id, NULL_ID))
4884                 report("No commit exist for the selected line");
4885         else
4886                 return TRUE;
4887         return FALSE;
4890 static void
4891 setup_blame_parent_line(struct view *view, struct blame *blame)
4893         const char *diff_tree_argv[] = {
4894                 "git", "diff-tree", "-U0", blame->commit->id,
4895                         "--", blame->commit->filename, NULL
4896         };
4897         struct io io = {};
4898         int parent_lineno = -1;
4899         int blamed_lineno = -1;
4900         char *line;
4902         if (!run_io(&io, diff_tree_argv, NULL, IO_RD))
4903                 return;
4905         while ((line = io_get(&io, '\n', TRUE))) {
4906                 if (*line == '@') {
4907                         char *pos = strchr(line, '+');
4909                         parent_lineno = atoi(line + 4);
4910                         if (pos)
4911                                 blamed_lineno = atoi(pos + 1);
4913                 } else if (*line == '+' && parent_lineno != -1) {
4914                         if (blame->lineno == blamed_lineno - 1 &&
4915                             !strcmp(blame->text, line + 1)) {
4916                                 view->lineno = parent_lineno ? parent_lineno - 1 : 0;
4917                                 break;
4918                         }
4919                         blamed_lineno++;
4920                 }
4921         }
4923         done_io(&io);
4926 static enum request
4927 blame_request(struct view *view, enum request request, struct line *line)
4929         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4930         struct blame *blame = line->data;
4932         switch (request) {
4933         case REQ_VIEW_BLAME:
4934                 if (check_blame_commit(blame, TRUE)) {
4935                         string_copy(opt_ref, blame->commit->id);
4936                         string_copy(opt_file, blame->commit->filename);
4937                         if (blame->lineno)
4938                                 view->lineno = blame->lineno;
4939                         open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4940                 }
4941                 break;
4943         case REQ_PARENT:
4944                 if (check_blame_commit(blame, TRUE) &&
4945                     select_commit_parent(blame->commit->id, opt_ref,
4946                                          blame->commit->filename)) {
4947                         string_copy(opt_file, blame->commit->filename);
4948                         setup_blame_parent_line(view, blame);
4949                         open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4950                 }
4951                 break;
4953         case REQ_ENTER:
4954                 if (!check_blame_commit(blame, FALSE))
4955                         break;
4957                 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
4958                     !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
4959                         break;
4961                 if (!strcmp(blame->commit->id, NULL_ID)) {
4962                         struct view *diff = VIEW(REQ_VIEW_DIFF);
4963                         const char *diff_index_argv[] = {
4964                                 "git", "diff-index", "--root", "--patch-with-stat",
4965                                         "-C", "-M", "HEAD", "--", view->vid, NULL
4966                         };
4968                         if (!blame->commit->has_previous) {
4969                                 diff_index_argv[1] = "diff";
4970                                 diff_index_argv[2] = "--no-color";
4971                                 diff_index_argv[6] = "--";
4972                                 diff_index_argv[7] = "/dev/null";
4973                         }
4975                         if (!prepare_update(diff, diff_index_argv, NULL, FORMAT_DASH)) {
4976                                 report("Failed to allocate diff command");
4977                                 break;
4978                         }
4979                         flags |= OPEN_PREPARED;
4980                 }
4982                 open_view(view, REQ_VIEW_DIFF, flags);
4983                 if (VIEW(REQ_VIEW_DIFF)->pipe && !strcmp(blame->commit->id, NULL_ID))
4984                         string_copy_rev(VIEW(REQ_VIEW_DIFF)->ref, NULL_ID);
4985                 break;
4987         default:
4988                 return request;
4989         }
4991         return REQ_NONE;
4994 static bool
4995 blame_grep(struct view *view, struct line *line)
4997         struct blame *blame = line->data;
4998         struct blame_commit *commit = blame->commit;
4999         const char *text[] = {
5000                 blame->text,
5001                 commit ? commit->title : "",
5002                 commit ? commit->id : "",
5003                 commit && opt_author ? commit->author : "",
5004                 commit && opt_date ? mkdate(&commit->time) : "",
5005                 NULL
5006         };
5008         return grep_text(view, text);
5011 static void
5012 blame_select(struct view *view, struct line *line)
5014         struct blame *blame = line->data;
5015         struct blame_commit *commit = blame->commit;
5017         if (!commit)
5018                 return;
5020         if (!strcmp(commit->id, NULL_ID))
5021                 string_ncopy(ref_commit, "HEAD", 4);
5022         else
5023                 string_copy_rev(ref_commit, commit->id);
5026 static struct view_ops blame_ops = {
5027         "line",
5028         NULL,
5029         blame_open,
5030         blame_read,
5031         blame_draw,
5032         blame_request,
5033         blame_grep,
5034         blame_select,
5035 };
5037 /*
5038  * Branch backend
5039  */
5041 struct branch {
5042         const char *author;             /* Author of the last commit. */
5043         time_t time;                    /* Date of the last activity. */
5044         const struct ref *ref;          /* Name and commit ID information. */
5045 };
5047 static const struct ref branch_all;
5049 static const enum sort_field branch_sort_fields[] = {
5050         ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
5051 };
5052 static struct sort_state branch_sort_state = SORT_STATE(branch_sort_fields);
5054 static int
5055 branch_compare(const void *l1, const void *l2)
5057         const struct branch *branch1 = ((const struct line *) l1)->data;
5058         const struct branch *branch2 = ((const struct line *) l2)->data;
5060         switch (get_sort_field(branch_sort_state)) {
5061         case ORDERBY_DATE:
5062                 return sort_order(branch_sort_state, branch1->time - branch2->time);
5064         case ORDERBY_AUTHOR:
5065                 return sort_order(branch_sort_state, strcmp(branch1->author, branch2->author));
5067         case ORDERBY_NAME:
5068         default:
5069                 return sort_order(branch_sort_state, strcmp(branch1->ref->name, branch2->ref->name));
5070         }
5073 static bool
5074 branch_draw(struct view *view, struct line *line, unsigned int lineno)
5076         struct branch *branch = line->data;
5077         enum line_type type = branch->ref->head ? LINE_MAIN_HEAD : LINE_DEFAULT;
5079         if (opt_date && draw_date(view, branch->author ? &branch->time : NULL))
5080                 return TRUE;
5082         if (opt_author && draw_author(view, branch->author))
5083                 return TRUE;
5085         draw_text(view, type, branch->ref == &branch_all ? "All branches" : branch->ref->name, TRUE);
5086         return TRUE;
5089 static enum request
5090 branch_request(struct view *view, enum request request, struct line *line)
5092         struct branch *branch = line->data;
5094         switch (request) {
5095         case REQ_REFRESH:
5096                 load_refs();
5097                 open_view(view, REQ_VIEW_BRANCH, OPEN_REFRESH);
5098                 return REQ_NONE;
5100         case REQ_TOGGLE_SORT_FIELD:
5101         case REQ_TOGGLE_SORT_ORDER:
5102                 sort_view(view, request, &branch_sort_state, branch_compare);
5103                 return REQ_NONE;
5105         case REQ_ENTER:
5106                 if (branch->ref == &branch_all) {
5107                         const char *all_branches_argv[] = {
5108                                 "git", "log", "--no-color", "--pretty=raw", "--parents",
5109                                       "--topo-order", "--all", NULL
5110                         };
5111                         struct view *main_view = VIEW(REQ_VIEW_MAIN);
5113                         if (!prepare_update(main_view, all_branches_argv, NULL, FORMAT_NONE)) {
5114                                 report("Failed to load view of all branches");
5115                                 return REQ_NONE;
5116                         }
5117                         open_view(view, REQ_VIEW_MAIN, OPEN_PREPARED | OPEN_SPLIT);
5118                 } else {
5119                         open_view(view, REQ_VIEW_MAIN, OPEN_SPLIT);
5120                 }
5121                 return REQ_NONE;
5123         default:
5124                 return request;
5125         }
5128 static bool
5129 branch_read(struct view *view, char *line)
5131         static char id[SIZEOF_REV];
5132         struct branch *reference;
5133         size_t i;
5135         if (!line)
5136                 return TRUE;
5138         switch (get_line_type(line)) {
5139         case LINE_COMMIT:
5140                 string_copy_rev(id, line + STRING_SIZE("commit "));
5141                 return TRUE;
5143         case LINE_AUTHOR:
5144                 for (i = 0, reference = NULL; i < view->lines; i++) {
5145                         struct branch *branch = view->line[i].data;
5147                         if (strcmp(branch->ref->id, id))
5148                                 continue;
5150                         view->line[i].dirty = TRUE;
5151                         if (reference) {
5152                                 branch->author = reference->author;
5153                                 branch->time = reference->time;
5154                                 continue;
5155                         }
5157                         parse_author_line(line + STRING_SIZE("author "),
5158                                           &branch->author, &branch->time);
5159                         reference = branch;
5160                 }
5161                 return TRUE;
5163         default:
5164                 return TRUE;
5165         }
5169 static bool
5170 branch_open_visitor(void *data, const struct ref *ref)
5172         struct view *view = data;
5173         struct branch *branch;
5175         if (ref->tag || ref->ltag || ref->remote)
5176                 return TRUE;
5178         branch = calloc(1, sizeof(*branch));
5179         if (!branch)
5180                 return FALSE;
5182         branch->ref = ref;
5183         return !!add_line_data(view, branch, LINE_DEFAULT);
5186 static bool
5187 branch_open(struct view *view)
5189         const char *branch_log[] = {
5190                 "git", "log", "--no-color", "--pretty=raw",
5191                         "--simplify-by-decoration", "--all", NULL
5192         };
5194         if (!run_io_rd(&view->io, branch_log, NULL, FORMAT_NONE)) {
5195                 report("Failed to load branch data");
5196                 return TRUE;
5197         }
5199         setup_update(view, view->id);
5200         branch_open_visitor(view, &branch_all);
5201         foreach_ref(branch_open_visitor, view);
5202         view->p_restore = TRUE;
5204         return TRUE;
5207 static bool
5208 branch_grep(struct view *view, struct line *line)
5210         struct branch *branch = line->data;
5211         const char *text[] = {
5212                 branch->ref->name,
5213                 branch->author,
5214                 NULL
5215         };
5217         return grep_text(view, text);
5220 static void
5221 branch_select(struct view *view, struct line *line)
5223         struct branch *branch = line->data;
5225         string_copy_rev(view->ref, branch->ref->id);
5226         string_copy_rev(ref_commit, branch->ref->id);
5227         string_copy_rev(ref_head, branch->ref->id);
5230 static struct view_ops branch_ops = {
5231         "branch",
5232         NULL,
5233         branch_open,
5234         branch_read,
5235         branch_draw,
5236         branch_request,
5237         branch_grep,
5238         branch_select,
5239 };
5241 /*
5242  * Status backend
5243  */
5245 struct status {
5246         char status;
5247         struct {
5248                 mode_t mode;
5249                 char rev[SIZEOF_REV];
5250                 char name[SIZEOF_STR];
5251         } old;
5252         struct {
5253                 mode_t mode;
5254                 char rev[SIZEOF_REV];
5255                 char name[SIZEOF_STR];
5256         } new;
5257 };
5259 static char status_onbranch[SIZEOF_STR];
5260 static struct status stage_status;
5261 static enum line_type stage_line_type;
5262 static size_t stage_chunks;
5263 static int *stage_chunk;
5265 DEFINE_ALLOCATOR(realloc_ints, int, 32)
5267 /* This should work even for the "On branch" line. */
5268 static inline bool
5269 status_has_none(struct view *view, struct line *line)
5271         return line < view->line + view->lines && !line[1].data;
5274 /* Get fields from the diff line:
5275  * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
5276  */
5277 static inline bool
5278 status_get_diff(struct status *file, const char *buf, size_t bufsize)
5280         const char *old_mode = buf +  1;
5281         const char *new_mode = buf +  8;
5282         const char *old_rev  = buf + 15;
5283         const char *new_rev  = buf + 56;
5284         const char *status   = buf + 97;
5286         if (bufsize < 98 ||
5287             old_mode[-1] != ':' ||
5288             new_mode[-1] != ' ' ||
5289             old_rev[-1]  != ' ' ||
5290             new_rev[-1]  != ' ' ||
5291             status[-1]   != ' ')
5292                 return FALSE;
5294         file->status = *status;
5296         string_copy_rev(file->old.rev, old_rev);
5297         string_copy_rev(file->new.rev, new_rev);
5299         file->old.mode = strtoul(old_mode, NULL, 8);
5300         file->new.mode = strtoul(new_mode, NULL, 8);
5302         file->old.name[0] = file->new.name[0] = 0;
5304         return TRUE;
5307 static bool
5308 status_run(struct view *view, const char *argv[], char status, enum line_type type)
5310         struct status *unmerged = NULL;
5311         char *buf;
5312         struct io io = {};
5314         if (!run_io(&io, argv, NULL, IO_RD))
5315                 return FALSE;
5317         add_line_data(view, NULL, type);
5319         while ((buf = io_get(&io, 0, TRUE))) {
5320                 struct status *file = unmerged;
5322                 if (!file) {
5323                         file = calloc(1, sizeof(*file));
5324                         if (!file || !add_line_data(view, file, type))
5325                                 goto error_out;
5326                 }
5328                 /* Parse diff info part. */
5329                 if (status) {
5330                         file->status = status;
5331                         if (status == 'A')
5332                                 string_copy(file->old.rev, NULL_ID);
5334                 } else if (!file->status || file == unmerged) {
5335                         if (!status_get_diff(file, buf, strlen(buf)))
5336                                 goto error_out;
5338                         buf = io_get(&io, 0, TRUE);
5339                         if (!buf)
5340                                 break;
5342                         /* Collapse all modified entries that follow an
5343                          * associated unmerged entry. */
5344                         if (unmerged == file) {
5345                                 unmerged->status = 'U';
5346                                 unmerged = NULL;
5347                         } else if (file->status == 'U') {
5348                                 unmerged = file;
5349                         }
5350                 }
5352                 /* Grab the old name for rename/copy. */
5353                 if (!*file->old.name &&
5354                     (file->status == 'R' || file->status == 'C')) {
5355                         string_ncopy(file->old.name, buf, strlen(buf));
5357                         buf = io_get(&io, 0, TRUE);
5358                         if (!buf)
5359                                 break;
5360                 }
5362                 /* git-ls-files just delivers a NUL separated list of
5363                  * file names similar to the second half of the
5364                  * git-diff-* output. */
5365                 string_ncopy(file->new.name, buf, strlen(buf));
5366                 if (!*file->old.name)
5367                         string_copy(file->old.name, file->new.name);
5368                 file = NULL;
5369         }
5371         if (io_error(&io)) {
5372 error_out:
5373                 done_io(&io);
5374                 return FALSE;
5375         }
5377         if (!view->line[view->lines - 1].data)
5378                 add_line_data(view, NULL, LINE_STAT_NONE);
5380         done_io(&io);
5381         return TRUE;
5384 /* Don't show unmerged entries in the staged section. */
5385 static const char *status_diff_index_argv[] = {
5386         "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
5387                              "--cached", "-M", "HEAD", NULL
5388 };
5390 static const char *status_diff_files_argv[] = {
5391         "git", "diff-files", "-z", NULL
5392 };
5394 static const char *status_list_other_argv[] = {
5395         "git", "ls-files", "-z", "--others", "--exclude-standard", NULL
5396 };
5398 static const char *status_list_no_head_argv[] = {
5399         "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
5400 };
5402 static const char *update_index_argv[] = {
5403         "git", "update-index", "-q", "--unmerged", "--refresh", NULL
5404 };
5406 /* Restore the previous line number to stay in the context or select a
5407  * line with something that can be updated. */
5408 static void
5409 status_restore(struct view *view)
5411         if (view->p_lineno >= view->lines)
5412                 view->p_lineno = view->lines - 1;
5413         while (view->p_lineno < view->lines && !view->line[view->p_lineno].data)
5414                 view->p_lineno++;
5415         while (view->p_lineno > 0 && !view->line[view->p_lineno].data)
5416                 view->p_lineno--;
5418         /* If the above fails, always skip the "On branch" line. */
5419         if (view->p_lineno < view->lines)
5420                 view->lineno = view->p_lineno;
5421         else
5422                 view->lineno = 1;
5424         if (view->lineno < view->offset)
5425                 view->offset = view->lineno;
5426         else if (view->offset + view->height <= view->lineno)
5427                 view->offset = view->lineno - view->height + 1;
5429         view->p_restore = FALSE;
5432 static void
5433 status_update_onbranch(void)
5435         static const char *paths[][2] = {
5436                 { "rebase-apply/rebasing",      "Rebasing" },
5437                 { "rebase-apply/applying",      "Applying mailbox" },
5438                 { "rebase-apply/",              "Rebasing mailbox" },
5439                 { "rebase-merge/interactive",   "Interactive rebase" },
5440                 { "rebase-merge/",              "Rebase merge" },
5441                 { "MERGE_HEAD",                 "Merging" },
5442                 { "BISECT_LOG",                 "Bisecting" },
5443                 { "HEAD",                       "On branch" },
5444         };
5445         char buf[SIZEOF_STR];
5446         struct stat stat;
5447         int i;
5449         if (is_initial_commit()) {
5450                 string_copy(status_onbranch, "Initial commit");
5451                 return;
5452         }
5454         for (i = 0; i < ARRAY_SIZE(paths); i++) {
5455                 char *head = opt_head;
5457                 if (!string_format(buf, "%s/%s", opt_git_dir, paths[i][0]) ||
5458                     lstat(buf, &stat) < 0)
5459                         continue;
5461                 if (!*opt_head) {
5462                         struct io io = {};
5464                         if (io_open(&io, "%s/rebase-merge/head-name", opt_git_dir) &&
5465                             io_read_buf(&io, buf, sizeof(buf))) {
5466                                 head = buf;
5467                                 if (!prefixcmp(head, "refs/heads/"))
5468                                         head += STRING_SIZE("refs/heads/");
5469                         }
5470                 }
5472                 if (!string_format(status_onbranch, "%s %s", paths[i][1], head))
5473                         string_copy(status_onbranch, opt_head);
5474                 return;
5475         }
5477         string_copy(status_onbranch, "Not currently on any branch");
5480 /* First parse staged info using git-diff-index(1), then parse unstaged
5481  * info using git-diff-files(1), and finally untracked files using
5482  * git-ls-files(1). */
5483 static bool
5484 status_open(struct view *view)
5486         reset_view(view);
5488         add_line_data(view, NULL, LINE_STAT_HEAD);
5489         status_update_onbranch();
5491         run_io_bg(update_index_argv);
5493         if (is_initial_commit()) {
5494                 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
5495                         return FALSE;
5496         } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
5497                 return FALSE;
5498         }
5500         if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
5501             !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
5502                 return FALSE;
5504         /* Restore the exact position or use the specialized restore
5505          * mode? */
5506         if (!view->p_restore)
5507                 status_restore(view);
5508         return TRUE;
5511 static bool
5512 status_draw(struct view *view, struct line *line, unsigned int lineno)
5514         struct status *status = line->data;
5515         enum line_type type;
5516         const char *text;
5518         if (!status) {
5519                 switch (line->type) {
5520                 case LINE_STAT_STAGED:
5521                         type = LINE_STAT_SECTION;
5522                         text = "Changes to be committed:";
5523                         break;
5525                 case LINE_STAT_UNSTAGED:
5526                         type = LINE_STAT_SECTION;
5527                         text = "Changed but not updated:";
5528                         break;
5530                 case LINE_STAT_UNTRACKED:
5531                         type = LINE_STAT_SECTION;
5532                         text = "Untracked files:";
5533                         break;
5535                 case LINE_STAT_NONE:
5536                         type = LINE_DEFAULT;
5537                         text = "  (no files)";
5538                         break;
5540                 case LINE_STAT_HEAD:
5541                         type = LINE_STAT_HEAD;
5542                         text = status_onbranch;
5543                         break;
5545                 default:
5546                         return FALSE;
5547                 }
5548         } else {
5549                 static char buf[] = { '?', ' ', ' ', ' ', 0 };
5551                 buf[0] = status->status;
5552                 if (draw_text(view, line->type, buf, TRUE))
5553                         return TRUE;
5554                 type = LINE_DEFAULT;
5555                 text = status->new.name;
5556         }
5558         draw_text(view, type, text, TRUE);
5559         return TRUE;
5562 static enum request
5563 status_load_error(struct view *view, struct view *stage, const char *path)
5565         if (displayed_views() == 2 || display[current_view] != view)
5566                 maximize_view(view);
5567         report("Failed to load '%s': %s", path, io_strerror(&stage->io));
5568         return REQ_NONE;
5571 static enum request
5572 status_enter(struct view *view, struct line *line)
5574         struct status *status = line->data;
5575         const char *oldpath = status ? status->old.name : NULL;
5576         /* Diffs for unmerged entries are empty when passing the new
5577          * path, so leave it empty. */
5578         const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
5579         const char *info;
5580         enum open_flags split;
5581         struct view *stage = VIEW(REQ_VIEW_STAGE);
5583         if (line->type == LINE_STAT_NONE ||
5584             (!status && line[1].type == LINE_STAT_NONE)) {
5585                 report("No file to diff");
5586                 return REQ_NONE;
5587         }
5589         switch (line->type) {
5590         case LINE_STAT_STAGED:
5591                 if (is_initial_commit()) {
5592                         const char *no_head_diff_argv[] = {
5593                                 "git", "diff", "--no-color", "--patch-with-stat",
5594                                         "--", "/dev/null", newpath, NULL
5595                         };
5597                         if (!prepare_update(stage, no_head_diff_argv, opt_cdup, FORMAT_DASH))
5598                                 return status_load_error(view, stage, newpath);
5599                 } else {
5600                         const char *index_show_argv[] = {
5601                                 "git", "diff-index", "--root", "--patch-with-stat",
5602                                         "-C", "-M", "--cached", "HEAD", "--",
5603                                         oldpath, newpath, NULL
5604                         };
5606                         if (!prepare_update(stage, index_show_argv, opt_cdup, FORMAT_DASH))
5607                                 return status_load_error(view, stage, newpath);
5608                 }
5610                 if (status)
5611                         info = "Staged changes to %s";
5612                 else
5613                         info = "Staged changes";
5614                 break;
5616         case LINE_STAT_UNSTAGED:
5617         {
5618                 const char *files_show_argv[] = {
5619                         "git", "diff-files", "--root", "--patch-with-stat",
5620                                 "-C", "-M", "--", oldpath, newpath, NULL
5621                 };
5623                 if (!prepare_update(stage, files_show_argv, opt_cdup, FORMAT_DASH))
5624                         return status_load_error(view, stage, newpath);
5625                 if (status)
5626                         info = "Unstaged changes to %s";
5627                 else
5628                         info = "Unstaged changes";
5629                 break;
5630         }
5631         case LINE_STAT_UNTRACKED:
5632                 if (!newpath) {
5633                         report("No file to show");
5634                         return REQ_NONE;
5635                 }
5637                 if (!suffixcmp(status->new.name, -1, "/")) {
5638                         report("Cannot display a directory");
5639                         return REQ_NONE;
5640                 }
5642                 if (!prepare_update_file(stage, newpath))
5643                         return status_load_error(view, stage, newpath);
5644                 info = "Untracked file %s";
5645                 break;
5647         case LINE_STAT_HEAD:
5648                 return REQ_NONE;
5650         default:
5651                 die("line type %d not handled in switch", line->type);
5652         }
5654         split = view_is_displayed(view) ? OPEN_SPLIT : 0;
5655         open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split);
5656         if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
5657                 if (status) {
5658                         stage_status = *status;
5659                 } else {
5660                         memset(&stage_status, 0, sizeof(stage_status));
5661                 }
5663                 stage_line_type = line->type;
5664                 stage_chunks = 0;
5665                 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
5666         }
5668         return REQ_NONE;
5671 static bool
5672 status_exists(struct status *status, enum line_type type)
5674         struct view *view = VIEW(REQ_VIEW_STATUS);
5675         unsigned long lineno;
5677         for (lineno = 0; lineno < view->lines; lineno++) {
5678                 struct line *line = &view->line[lineno];
5679                 struct status *pos = line->data;
5681                 if (line->type != type)
5682                         continue;
5683                 if (!pos && (!status || !status->status) && line[1].data) {
5684                         select_view_line(view, lineno);
5685                         return TRUE;
5686                 }
5687                 if (pos && !strcmp(status->new.name, pos->new.name)) {
5688                         select_view_line(view, lineno);
5689                         return TRUE;
5690                 }
5691         }
5693         return FALSE;
5697 static bool
5698 status_update_prepare(struct io *io, enum line_type type)
5700         const char *staged_argv[] = {
5701                 "git", "update-index", "-z", "--index-info", NULL
5702         };
5703         const char *others_argv[] = {
5704                 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
5705         };
5707         switch (type) {
5708         case LINE_STAT_STAGED:
5709                 return run_io(io, staged_argv, opt_cdup, IO_WR);
5711         case LINE_STAT_UNSTAGED:
5712                 return run_io(io, others_argv, opt_cdup, IO_WR);
5714         case LINE_STAT_UNTRACKED:
5715                 return run_io(io, others_argv, NULL, IO_WR);
5717         default:
5718                 die("line type %d not handled in switch", type);
5719                 return FALSE;
5720         }
5723 static bool
5724 status_update_write(struct io *io, struct status *status, enum line_type type)
5726         char buf[SIZEOF_STR];
5727         size_t bufsize = 0;
5729         switch (type) {
5730         case LINE_STAT_STAGED:
5731                 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
5732                                         status->old.mode,
5733                                         status->old.rev,
5734                                         status->old.name, 0))
5735                         return FALSE;
5736                 break;
5738         case LINE_STAT_UNSTAGED:
5739         case LINE_STAT_UNTRACKED:
5740                 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
5741                         return FALSE;
5742                 break;
5744         default:
5745                 die("line type %d not handled in switch", type);
5746         }
5748         return io_write(io, buf, bufsize);
5751 static bool
5752 status_update_file(struct status *status, enum line_type type)
5754         struct io io = {};
5755         bool result;
5757         if (!status_update_prepare(&io, type))
5758                 return FALSE;
5760         result = status_update_write(&io, status, type);
5761         return done_io(&io) && result;
5764 static bool
5765 status_update_files(struct view *view, struct line *line)
5767         char buf[sizeof(view->ref)];
5768         struct io io = {};
5769         bool result = TRUE;
5770         struct line *pos = view->line + view->lines;
5771         int files = 0;
5772         int file, done;
5773         int cursor_y = -1, cursor_x = -1;
5775         if (!status_update_prepare(&io, line->type))
5776                 return FALSE;
5778         for (pos = line; pos < view->line + view->lines && pos->data; pos++)
5779                 files++;
5781         string_copy(buf, view->ref);
5782         getsyx(cursor_y, cursor_x);
5783         for (file = 0, done = 5; result && file < files; line++, file++) {
5784                 int almost_done = file * 100 / files;
5786                 if (almost_done > done) {
5787                         done = almost_done;
5788                         string_format(view->ref, "updating file %u of %u (%d%% done)",
5789                                       file, files, done);
5790                         update_view_title(view);
5791                         setsyx(cursor_y, cursor_x);
5792                         doupdate();
5793                 }
5794                 result = status_update_write(&io, line->data, line->type);
5795         }
5796         string_copy(view->ref, buf);
5798         return done_io(&io) && result;
5801 static bool
5802 status_update(struct view *view)
5804         struct line *line = &view->line[view->lineno];
5806         assert(view->lines);
5808         if (!line->data) {
5809                 /* This should work even for the "On branch" line. */
5810                 if (line < view->line + view->lines && !line[1].data) {
5811                         report("Nothing to update");
5812                         return FALSE;
5813                 }
5815                 if (!status_update_files(view, line + 1)) {
5816                         report("Failed to update file status");
5817                         return FALSE;
5818                 }
5820         } else if (!status_update_file(line->data, line->type)) {
5821                 report("Failed to update file status");
5822                 return FALSE;
5823         }
5825         return TRUE;
5828 static bool
5829 status_revert(struct status *status, enum line_type type, bool has_none)
5831         if (!status || type != LINE_STAT_UNSTAGED) {
5832                 if (type == LINE_STAT_STAGED) {
5833                         report("Cannot revert changes to staged files");
5834                 } else if (type == LINE_STAT_UNTRACKED) {
5835                         report("Cannot revert changes to untracked files");
5836                 } else if (has_none) {
5837                         report("Nothing to revert");
5838                 } else {
5839                         report("Cannot revert changes to multiple files");
5840                 }
5842         } else if (prompt_yesno("Are you sure you want to revert changes?")) {
5843                 char mode[10] = "100644";
5844                 const char *reset_argv[] = {
5845                         "git", "update-index", "--cacheinfo", mode,
5846                                 status->old.rev, status->old.name, NULL
5847                 };
5848                 const char *checkout_argv[] = {
5849                         "git", "checkout", "--", status->old.name, NULL
5850                 };
5852                 if (status->status == 'U') {
5853                         string_format(mode, "%5o", status->old.mode);
5855                         if (status->old.mode == 0 && status->new.mode == 0) {
5856                                 reset_argv[2] = "--force-remove";
5857                                 reset_argv[3] = status->old.name;
5858                                 reset_argv[4] = NULL;
5859                         }
5861                         if (!run_io_fg(reset_argv, opt_cdup))
5862                                 return FALSE;
5863                         if (status->old.mode == 0 && status->new.mode == 0)
5864                                 return TRUE;
5865                 }
5867                 return run_io_fg(checkout_argv, opt_cdup);
5868         }
5870         return FALSE;
5873 static enum request
5874 status_request(struct view *view, enum request request, struct line *line)
5876         struct status *status = line->data;
5878         switch (request) {
5879         case REQ_STATUS_UPDATE:
5880                 if (!status_update(view))
5881                         return REQ_NONE;
5882                 break;
5884         case REQ_STATUS_REVERT:
5885                 if (!status_revert(status, line->type, status_has_none(view, line)))
5886                         return REQ_NONE;
5887                 break;
5889         case REQ_STATUS_MERGE:
5890                 if (!status || status->status != 'U') {
5891                         report("Merging only possible for files with unmerged status ('U').");
5892                         return REQ_NONE;
5893                 }
5894                 open_mergetool(status->new.name);
5895                 break;
5897         case REQ_EDIT:
5898                 if (!status)
5899                         return request;
5900                 if (status->status == 'D') {
5901                         report("File has been deleted.");
5902                         return REQ_NONE;
5903                 }
5905                 open_editor(status->status != '?', status->new.name);
5906                 break;
5908         case REQ_VIEW_BLAME:
5909                 if (status)
5910                         opt_ref[0] = 0;
5911                 return request;
5913         case REQ_ENTER:
5914                 /* After returning the status view has been split to
5915                  * show the stage view. No further reloading is
5916                  * necessary. */
5917                 return status_enter(view, line);
5919         case REQ_REFRESH:
5920                 /* Simply reload the view. */
5921                 break;
5923         default:
5924                 return request;
5925         }
5927         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
5929         return REQ_NONE;
5932 static void
5933 status_select(struct view *view, struct line *line)
5935         struct status *status = line->data;
5936         char file[SIZEOF_STR] = "all files";
5937         const char *text;
5938         const char *key;
5940         if (status && !string_format(file, "'%s'", status->new.name))
5941                 return;
5943         if (!status && line[1].type == LINE_STAT_NONE)
5944                 line++;
5946         switch (line->type) {
5947         case LINE_STAT_STAGED:
5948                 text = "Press %s to unstage %s for commit";
5949                 break;
5951         case LINE_STAT_UNSTAGED:
5952                 text = "Press %s to stage %s for commit";
5953                 break;
5955         case LINE_STAT_UNTRACKED:
5956                 text = "Press %s to stage %s for addition";
5957                 break;
5959         case LINE_STAT_HEAD:
5960         case LINE_STAT_NONE:
5961                 text = "Nothing to update";
5962                 break;
5964         default:
5965                 die("line type %d not handled in switch", line->type);
5966         }
5968         if (status && status->status == 'U') {
5969                 text = "Press %s to resolve conflict in %s";
5970                 key = get_key(KEYMAP_STATUS, REQ_STATUS_MERGE);
5972         } else {
5973                 key = get_key(KEYMAP_STATUS, REQ_STATUS_UPDATE);
5974         }
5976         string_format(view->ref, text, key, file);
5977         if (status)
5978                 string_copy(opt_file, status->new.name);
5981 static bool
5982 status_grep(struct view *view, struct line *line)
5984         struct status *status = line->data;
5986         if (status) {
5987                 const char buf[2] = { status->status, 0 };
5988                 const char *text[] = { status->new.name, buf, NULL };
5990                 return grep_text(view, text);
5991         }
5993         return FALSE;
5996 static struct view_ops status_ops = {
5997         "file",
5998         NULL,
5999         status_open,
6000         NULL,
6001         status_draw,
6002         status_request,
6003         status_grep,
6004         status_select,
6005 };
6008 static bool
6009 stage_diff_write(struct io *io, struct line *line, struct line *end)
6011         while (line < end) {
6012                 if (!io_write(io, line->data, strlen(line->data)) ||
6013                     !io_write(io, "\n", 1))
6014                         return FALSE;
6015                 line++;
6016                 if (line->type == LINE_DIFF_CHUNK ||
6017                     line->type == LINE_DIFF_HEADER)
6018                         break;
6019         }
6021         return TRUE;
6024 static struct line *
6025 stage_diff_find(struct view *view, struct line *line, enum line_type type)
6027         for (; view->line < line; line--)
6028                 if (line->type == type)
6029                         return line;
6031         return NULL;
6034 static bool
6035 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
6037         const char *apply_argv[SIZEOF_ARG] = {
6038                 "git", "apply", "--whitespace=nowarn", NULL
6039         };
6040         struct line *diff_hdr;
6041         struct io io = {};
6042         int argc = 3;
6044         diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
6045         if (!diff_hdr)
6046                 return FALSE;
6048         if (!revert)
6049                 apply_argv[argc++] = "--cached";
6050         if (revert || stage_line_type == LINE_STAT_STAGED)
6051                 apply_argv[argc++] = "-R";
6052         apply_argv[argc++] = "-";
6053         apply_argv[argc++] = NULL;
6054         if (!run_io(&io, apply_argv, opt_cdup, IO_WR))
6055                 return FALSE;
6057         if (!stage_diff_write(&io, diff_hdr, chunk) ||
6058             !stage_diff_write(&io, chunk, view->line + view->lines))
6059                 chunk = NULL;
6061         done_io(&io);
6062         run_io_bg(update_index_argv);
6064         return chunk ? TRUE : FALSE;
6067 static bool
6068 stage_update(struct view *view, struct line *line)
6070         struct line *chunk = NULL;
6072         if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
6073                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6075         if (chunk) {
6076                 if (!stage_apply_chunk(view, chunk, FALSE)) {
6077                         report("Failed to apply chunk");
6078                         return FALSE;
6079                 }
6081         } else if (!stage_status.status) {
6082                 view = VIEW(REQ_VIEW_STATUS);
6084                 for (line = view->line; line < view->line + view->lines; line++)
6085                         if (line->type == stage_line_type)
6086                                 break;
6088                 if (!status_update_files(view, line + 1)) {
6089                         report("Failed to update files");
6090                         return FALSE;
6091                 }
6093         } else if (!status_update_file(&stage_status, stage_line_type)) {
6094                 report("Failed to update file");
6095                 return FALSE;
6096         }
6098         return TRUE;
6101 static bool
6102 stage_revert(struct view *view, struct line *line)
6104         struct line *chunk = NULL;
6106         if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
6107                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6109         if (chunk) {
6110                 if (!prompt_yesno("Are you sure you want to revert changes?"))
6111                         return FALSE;
6113                 if (!stage_apply_chunk(view, chunk, TRUE)) {
6114                         report("Failed to revert chunk");
6115                         return FALSE;
6116                 }
6117                 return TRUE;
6119         } else {
6120                 return status_revert(stage_status.status ? &stage_status : NULL,
6121                                      stage_line_type, FALSE);
6122         }
6126 static void
6127 stage_next(struct view *view, struct line *line)
6129         int i;
6131         if (!stage_chunks) {
6132                 for (line = view->line; line < view->line + view->lines; line++) {
6133                         if (line->type != LINE_DIFF_CHUNK)
6134                                 continue;
6136                         if (!realloc_ints(&stage_chunk, stage_chunks, 1)) {
6137                                 report("Allocation failure");
6138                                 return;
6139                         }
6141                         stage_chunk[stage_chunks++] = line - view->line;
6142                 }
6143         }
6145         for (i = 0; i < stage_chunks; i++) {
6146                 if (stage_chunk[i] > view->lineno) {
6147                         do_scroll_view(view, stage_chunk[i] - view->lineno);
6148                         report("Chunk %d of %d", i + 1, stage_chunks);
6149                         return;
6150                 }
6151         }
6153         report("No next chunk found");
6156 static enum request
6157 stage_request(struct view *view, enum request request, struct line *line)
6159         switch (request) {
6160         case REQ_STATUS_UPDATE:
6161                 if (!stage_update(view, line))
6162                         return REQ_NONE;
6163                 break;
6165         case REQ_STATUS_REVERT:
6166                 if (!stage_revert(view, line))
6167                         return REQ_NONE;
6168                 break;
6170         case REQ_STAGE_NEXT:
6171                 if (stage_line_type == LINE_STAT_UNTRACKED) {
6172                         report("File is untracked; press %s to add",
6173                                get_key(KEYMAP_STAGE, REQ_STATUS_UPDATE));
6174                         return REQ_NONE;
6175                 }
6176                 stage_next(view, line);
6177                 return REQ_NONE;
6179         case REQ_EDIT:
6180                 if (!stage_status.new.name[0])
6181                         return request;
6182                 if (stage_status.status == 'D') {
6183                         report("File has been deleted.");
6184                         return REQ_NONE;
6185                 }
6187                 open_editor(stage_status.status != '?', stage_status.new.name);
6188                 break;
6190         case REQ_REFRESH:
6191                 /* Reload everything ... */
6192                 break;
6194         case REQ_VIEW_BLAME:
6195                 if (stage_status.new.name[0]) {
6196                         string_copy(opt_file, stage_status.new.name);
6197                         opt_ref[0] = 0;
6198                 }
6199                 return request;
6201         case REQ_ENTER:
6202                 return pager_request(view, request, line);
6204         default:
6205                 return request;
6206         }
6208         VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
6209         open_view(view, REQ_VIEW_STATUS, OPEN_REFRESH);
6211         /* Check whether the staged entry still exists, and close the
6212          * stage view if it doesn't. */
6213         if (!status_exists(&stage_status, stage_line_type)) {
6214                 status_restore(VIEW(REQ_VIEW_STATUS));
6215                 return REQ_VIEW_CLOSE;
6216         }
6218         if (stage_line_type == LINE_STAT_UNTRACKED) {
6219                 if (!suffixcmp(stage_status.new.name, -1, "/")) {
6220                         report("Cannot display a directory");
6221                         return REQ_NONE;
6222                 }
6224                 if (!prepare_update_file(view, stage_status.new.name)) {
6225                         report("Failed to open file: %s", strerror(errno));
6226                         return REQ_NONE;
6227                 }
6228         }
6229         open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
6231         return REQ_NONE;
6234 static struct view_ops stage_ops = {
6235         "line",
6236         NULL,
6237         NULL,
6238         pager_read,
6239         pager_draw,
6240         stage_request,
6241         pager_grep,
6242         pager_select,
6243 };
6246 /*
6247  * Revision graph
6248  */
6250 struct commit {
6251         char id[SIZEOF_REV];            /* SHA1 ID. */
6252         char title[128];                /* First line of the commit message. */
6253         const char *author;             /* Author of the commit. */
6254         time_t time;                    /* Date from the author ident. */
6255         struct ref_list *refs;          /* Repository references. */
6256         chtype graph[SIZEOF_REVGRAPH];  /* Ancestry chain graphics. */
6257         size_t graph_size;              /* The width of the graph array. */
6258         bool has_parents;               /* Rewritten --parents seen. */
6259 };
6261 /* Size of rev graph with no  "padding" columns */
6262 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
6264 struct rev_graph {
6265         struct rev_graph *prev, *next, *parents;
6266         char rev[SIZEOF_REVITEMS][SIZEOF_REV];
6267         size_t size;
6268         struct commit *commit;
6269         size_t pos;
6270         unsigned int boundary:1;
6271 };
6273 /* Parents of the commit being visualized. */
6274 static struct rev_graph graph_parents[4];
6276 /* The current stack of revisions on the graph. */
6277 static struct rev_graph graph_stacks[4] = {
6278         { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
6279         { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
6280         { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
6281         { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
6282 };
6284 static inline bool
6285 graph_parent_is_merge(struct rev_graph *graph)
6287         return graph->parents->size > 1;
6290 static inline void
6291 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
6293         struct commit *commit = graph->commit;
6295         if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
6296                 commit->graph[commit->graph_size++] = symbol;
6299 static void
6300 clear_rev_graph(struct rev_graph *graph)
6302         graph->boundary = 0;
6303         graph->size = graph->pos = 0;
6304         graph->commit = NULL;
6305         memset(graph->parents, 0, sizeof(*graph->parents));
6308 static void
6309 done_rev_graph(struct rev_graph *graph)
6311         if (graph_parent_is_merge(graph) &&
6312             graph->pos < graph->size - 1 &&
6313             graph->next->size == graph->size + graph->parents->size - 1) {
6314                 size_t i = graph->pos + graph->parents->size - 1;
6316                 graph->commit->graph_size = i * 2;
6317                 while (i < graph->next->size - 1) {
6318                         append_to_rev_graph(graph, ' ');
6319                         append_to_rev_graph(graph, '\\');
6320                         i++;
6321                 }
6322         }
6324         clear_rev_graph(graph);
6327 static void
6328 push_rev_graph(struct rev_graph *graph, const char *parent)
6330         int i;
6332         /* "Collapse" duplicate parents lines.
6333          *
6334          * FIXME: This needs to also update update the drawn graph but
6335          * for now it just serves as a method for pruning graph lines. */
6336         for (i = 0; i < graph->size; i++)
6337                 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
6338                         return;
6340         if (graph->size < SIZEOF_REVITEMS) {
6341                 string_copy_rev(graph->rev[graph->size++], parent);
6342         }
6345 static chtype
6346 get_rev_graph_symbol(struct rev_graph *graph)
6348         chtype symbol;
6350         if (graph->boundary)
6351                 symbol = REVGRAPH_BOUND;
6352         else if (graph->parents->size == 0)
6353                 symbol = REVGRAPH_INIT;
6354         else if (graph_parent_is_merge(graph))
6355                 symbol = REVGRAPH_MERGE;
6356         else if (graph->pos >= graph->size)
6357                 symbol = REVGRAPH_BRANCH;
6358         else
6359                 symbol = REVGRAPH_COMMIT;
6361         return symbol;
6364 static void
6365 draw_rev_graph(struct rev_graph *graph)
6367         struct rev_filler {
6368                 chtype separator, line;
6369         };
6370         enum { DEFAULT, RSHARP, RDIAG, LDIAG };
6371         static struct rev_filler fillers[] = {
6372                 { ' ',  '|' },
6373                 { '`',  '.' },
6374                 { '\'', ' ' },
6375                 { '/',  ' ' },
6376         };
6377         chtype symbol = get_rev_graph_symbol(graph);
6378         struct rev_filler *filler;
6379         size_t i;
6381         if (opt_line_graphics)
6382                 fillers[DEFAULT].line = line_graphics[LINE_GRAPHIC_VLINE];
6384         filler = &fillers[DEFAULT];
6386         for (i = 0; i < graph->pos; i++) {
6387                 append_to_rev_graph(graph, filler->line);
6388                 if (graph_parent_is_merge(graph->prev) &&
6389                     graph->prev->pos == i)
6390                         filler = &fillers[RSHARP];
6392                 append_to_rev_graph(graph, filler->separator);
6393         }
6395         /* Place the symbol for this revision. */
6396         append_to_rev_graph(graph, symbol);
6398         if (graph->prev->size > graph->size)
6399                 filler = &fillers[RDIAG];
6400         else
6401                 filler = &fillers[DEFAULT];
6403         i++;
6405         for (; i < graph->size; i++) {
6406                 append_to_rev_graph(graph, filler->separator);
6407                 append_to_rev_graph(graph, filler->line);
6408                 if (graph_parent_is_merge(graph->prev) &&
6409                     i < graph->prev->pos + graph->parents->size)
6410                         filler = &fillers[RSHARP];
6411                 if (graph->prev->size > graph->size)
6412                         filler = &fillers[LDIAG];
6413         }
6415         if (graph->prev->size > graph->size) {
6416                 append_to_rev_graph(graph, filler->separator);
6417                 if (filler->line != ' ')
6418                         append_to_rev_graph(graph, filler->line);
6419         }
6422 /* Prepare the next rev graph */
6423 static void
6424 prepare_rev_graph(struct rev_graph *graph)
6426         size_t i;
6428         /* First, traverse all lines of revisions up to the active one. */
6429         for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
6430                 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
6431                         break;
6433                 push_rev_graph(graph->next, graph->rev[graph->pos]);
6434         }
6436         /* Interleave the new revision parent(s). */
6437         for (i = 0; !graph->boundary && i < graph->parents->size; i++)
6438                 push_rev_graph(graph->next, graph->parents->rev[i]);
6440         /* Lastly, put any remaining revisions. */
6441         for (i = graph->pos + 1; i < graph->size; i++)
6442                 push_rev_graph(graph->next, graph->rev[i]);
6445 static void
6446 update_rev_graph(struct view *view, struct rev_graph *graph)
6448         /* If this is the finalizing update ... */
6449         if (graph->commit)
6450                 prepare_rev_graph(graph);
6452         /* Graph visualization needs a one rev look-ahead,
6453          * so the first update doesn't visualize anything. */
6454         if (!graph->prev->commit)
6455                 return;
6457         if (view->lines > 2)
6458                 view->line[view->lines - 3].dirty = 1;
6459         if (view->lines > 1)
6460                 view->line[view->lines - 2].dirty = 1;
6461         draw_rev_graph(graph->prev);
6462         done_rev_graph(graph->prev->prev);
6466 /*
6467  * Main view backend
6468  */
6470 static const char *main_argv[SIZEOF_ARG] = {
6471         "git", "log", "--no-color", "--pretty=raw", "--parents",
6472                       "--topo-order", "%(head)", NULL
6473 };
6475 static bool
6476 main_draw(struct view *view, struct line *line, unsigned int lineno)
6478         struct commit *commit = line->data;
6480         if (!commit->author)
6481                 return FALSE;
6483         if (opt_date && draw_date(view, &commit->time))
6484                 return TRUE;
6486         if (opt_author && draw_author(view, commit->author))
6487                 return TRUE;
6489         if (opt_rev_graph && commit->graph_size &&
6490             draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
6491                 return TRUE;
6493         if (opt_show_refs && commit->refs) {
6494                 size_t i;
6496                 for (i = 0; i < commit->refs->size; i++) {
6497                         struct ref *ref = commit->refs->refs[i];
6498                         enum line_type type;
6500                         if (ref->head)
6501                                 type = LINE_MAIN_HEAD;
6502                         else if (ref->ltag)
6503                                 type = LINE_MAIN_LOCAL_TAG;
6504                         else if (ref->tag)
6505                                 type = LINE_MAIN_TAG;
6506                         else if (ref->tracked)
6507                                 type = LINE_MAIN_TRACKED;
6508                         else if (ref->remote)
6509                                 type = LINE_MAIN_REMOTE;
6510                         else
6511                                 type = LINE_MAIN_REF;
6513                         if (draw_text(view, type, "[", TRUE) ||
6514                             draw_text(view, type, ref->name, TRUE) ||
6515                             draw_text(view, type, "]", TRUE))
6516                                 return TRUE;
6518                         if (draw_text(view, LINE_DEFAULT, " ", TRUE))
6519                                 return TRUE;
6520                 }
6521         }
6523         draw_text(view, LINE_DEFAULT, commit->title, TRUE);
6524         return TRUE;
6527 /* Reads git log --pretty=raw output and parses it into the commit struct. */
6528 static bool
6529 main_read(struct view *view, char *line)
6531         static struct rev_graph *graph = graph_stacks;
6532         enum line_type type;
6533         struct commit *commit;
6535         if (!line) {
6536                 int i;
6538                 if (!view->lines && !view->parent)
6539                         die("No revisions match the given arguments.");
6540                 if (view->lines > 0) {
6541                         commit = view->line[view->lines - 1].data;
6542                         view->line[view->lines - 1].dirty = 1;
6543                         if (!commit->author) {
6544                                 view->lines--;
6545                                 free(commit);
6546                                 graph->commit = NULL;
6547                         }
6548                 }
6549                 update_rev_graph(view, graph);
6551                 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
6552                         clear_rev_graph(&graph_stacks[i]);
6553                 return TRUE;
6554         }
6556         type = get_line_type(line);
6557         if (type == LINE_COMMIT) {
6558                 commit = calloc(1, sizeof(struct commit));
6559                 if (!commit)
6560                         return FALSE;
6562                 line += STRING_SIZE("commit ");
6563                 if (*line == '-') {
6564                         graph->boundary = 1;
6565                         line++;
6566                 }
6568                 string_copy_rev(commit->id, line);
6569                 commit->refs = get_ref_list(commit->id);
6570                 graph->commit = commit;
6571                 add_line_data(view, commit, LINE_MAIN_COMMIT);
6573                 while ((line = strchr(line, ' '))) {
6574                         line++;
6575                         push_rev_graph(graph->parents, line);
6576                         commit->has_parents = TRUE;
6577                 }
6578                 return TRUE;
6579         }
6581         if (!view->lines)
6582                 return TRUE;
6583         commit = view->line[view->lines - 1].data;
6585         switch (type) {
6586         case LINE_PARENT:
6587                 if (commit->has_parents)
6588                         break;
6589                 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
6590                 break;
6592         case LINE_AUTHOR:
6593                 parse_author_line(line + STRING_SIZE("author "),
6594                                   &commit->author, &commit->time);
6595                 update_rev_graph(view, graph);
6596                 graph = graph->next;
6597                 break;
6599         default:
6600                 /* Fill in the commit title if it has not already been set. */
6601                 if (commit->title[0])
6602                         break;
6604                 /* Require titles to start with a non-space character at the
6605                  * offset used by git log. */
6606                 if (strncmp(line, "    ", 4))
6607                         break;
6608                 line += 4;
6609                 /* Well, if the title starts with a whitespace character,
6610                  * try to be forgiving.  Otherwise we end up with no title. */
6611                 while (isspace(*line))
6612                         line++;
6613                 if (*line == '\0')
6614                         break;
6615                 /* FIXME: More graceful handling of titles; append "..." to
6616                  * shortened titles, etc. */
6618                 string_expand(commit->title, sizeof(commit->title), line, 1);
6619                 view->line[view->lines - 1].dirty = 1;
6620         }
6622         return TRUE;
6625 static enum request
6626 main_request(struct view *view, enum request request, struct line *line)
6628         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
6630         switch (request) {
6631         case REQ_ENTER:
6632                 open_view(view, REQ_VIEW_DIFF, flags);
6633                 break;
6634         case REQ_REFRESH:
6635                 load_refs();
6636                 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
6637                 break;
6638         default:
6639                 return request;
6640         }
6642         return REQ_NONE;
6645 static bool
6646 grep_refs(struct ref_list *list, regex_t *regex)
6648         regmatch_t pmatch;
6649         size_t i;
6651         if (!opt_show_refs || !list)
6652                 return FALSE;
6654         for (i = 0; i < list->size; i++) {
6655                 if (regexec(regex, list->refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
6656                         return TRUE;
6657         }
6659         return FALSE;
6662 static bool
6663 main_grep(struct view *view, struct line *line)
6665         struct commit *commit = line->data;
6666         const char *text[] = {
6667                 commit->title,
6668                 opt_author ? commit->author : "",
6669                 opt_date ? mkdate(&commit->time) : "",
6670                 NULL
6671         };
6673         return grep_text(view, text) || grep_refs(commit->refs, view->regex);
6676 static void
6677 main_select(struct view *view, struct line *line)
6679         struct commit *commit = line->data;
6681         string_copy_rev(view->ref, commit->id);
6682         string_copy_rev(ref_commit, view->ref);
6685 static struct view_ops main_ops = {
6686         "commit",
6687         main_argv,
6688         NULL,
6689         main_read,
6690         main_draw,
6691         main_request,
6692         main_grep,
6693         main_select,
6694 };
6697 /*
6698  * Unicode / UTF-8 handling
6699  *
6700  * NOTE: Much of the following code for dealing with Unicode is derived from
6701  * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
6702  * src/intl/charset.c from the UTF-8 branch commit elinks-0.11.0-g31f2c28.
6703  */
6705 static inline int
6706 unicode_width(unsigned long c)
6708         if (c >= 0x1100 &&
6709            (c <= 0x115f                         /* Hangul Jamo */
6710             || c == 0x2329
6711             || c == 0x232a
6712             || (c >= 0x2e80  && c <= 0xa4cf && c != 0x303f)
6713                                                 /* CJK ... Yi */
6714             || (c >= 0xac00  && c <= 0xd7a3)    /* Hangul Syllables */
6715             || (c >= 0xf900  && c <= 0xfaff)    /* CJK Compatibility Ideographs */
6716             || (c >= 0xfe30  && c <= 0xfe6f)    /* CJK Compatibility Forms */
6717             || (c >= 0xff00  && c <= 0xff60)    /* Fullwidth Forms */
6718             || (c >= 0xffe0  && c <= 0xffe6)
6719             || (c >= 0x20000 && c <= 0x2fffd)
6720             || (c >= 0x30000 && c <= 0x3fffd)))
6721                 return 2;
6723         if (c == '\t')
6724                 return opt_tab_size;
6726         return 1;
6729 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
6730  * Illegal bytes are set one. */
6731 static const unsigned char utf8_bytes[256] = {
6732         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,
6733         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,
6734         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,
6735         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,
6736         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,
6737         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,
6738         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,
6739         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,
6740 };
6742 /* Decode UTF-8 multi-byte representation into a Unicode character. */
6743 static inline unsigned long
6744 utf8_to_unicode(const char *string, size_t length)
6746         unsigned long unicode;
6748         switch (length) {
6749         case 1:
6750                 unicode  =   string[0];
6751                 break;
6752         case 2:
6753                 unicode  =  (string[0] & 0x1f) << 6;
6754                 unicode +=  (string[1] & 0x3f);
6755                 break;
6756         case 3:
6757                 unicode  =  (string[0] & 0x0f) << 12;
6758                 unicode += ((string[1] & 0x3f) << 6);
6759                 unicode +=  (string[2] & 0x3f);
6760                 break;
6761         case 4:
6762                 unicode  =  (string[0] & 0x0f) << 18;
6763                 unicode += ((string[1] & 0x3f) << 12);
6764                 unicode += ((string[2] & 0x3f) << 6);
6765                 unicode +=  (string[3] & 0x3f);
6766                 break;
6767         case 5:
6768                 unicode  =  (string[0] & 0x0f) << 24;
6769                 unicode += ((string[1] & 0x3f) << 18);
6770                 unicode += ((string[2] & 0x3f) << 12);
6771                 unicode += ((string[3] & 0x3f) << 6);
6772                 unicode +=  (string[4] & 0x3f);
6773                 break;
6774         case 6:
6775                 unicode  =  (string[0] & 0x01) << 30;
6776                 unicode += ((string[1] & 0x3f) << 24);
6777                 unicode += ((string[2] & 0x3f) << 18);
6778                 unicode += ((string[3] & 0x3f) << 12);
6779                 unicode += ((string[4] & 0x3f) << 6);
6780                 unicode +=  (string[5] & 0x3f);
6781                 break;
6782         default:
6783                 die("Invalid Unicode length");
6784         }
6786         /* Invalid characters could return the special 0xfffd value but NUL
6787          * should be just as good. */
6788         return unicode > 0xffff ? 0 : unicode;
6791 /* Calculates how much of string can be shown within the given maximum width
6792  * and sets trimmed parameter to non-zero value if all of string could not be
6793  * shown. If the reserve flag is TRUE, it will reserve at least one
6794  * trailing character, which can be useful when drawing a delimiter.
6795  *
6796  * Returns the number of bytes to output from string to satisfy max_width. */
6797 static size_t
6798 utf8_length(const char **start, size_t skip, int *width, size_t max_width, int *trimmed, bool reserve)
6800         const char *string = *start;
6801         const char *end = strchr(string, '\0');
6802         unsigned char last_bytes = 0;
6803         size_t last_ucwidth = 0;
6805         *width = 0;
6806         *trimmed = 0;
6808         while (string < end) {
6809                 int c = *(unsigned char *) string;
6810                 unsigned char bytes = utf8_bytes[c];
6811                 size_t ucwidth;
6812                 unsigned long unicode;
6814                 if (string + bytes > end)
6815                         break;
6817                 /* Change representation to figure out whether
6818                  * it is a single- or double-width character. */
6820                 unicode = utf8_to_unicode(string, bytes);
6821                 /* FIXME: Graceful handling of invalid Unicode character. */
6822                 if (!unicode)
6823                         break;
6825                 ucwidth = unicode_width(unicode);
6826                 if (skip > 0) {
6827                         skip -= ucwidth <= skip ? ucwidth : skip;
6828                         *start += bytes;
6829                 }
6830                 *width  += ucwidth;
6831                 if (*width > max_width) {
6832                         *trimmed = 1;
6833                         *width -= ucwidth;
6834                         if (reserve && *width == max_width) {
6835                                 string -= last_bytes;
6836                                 *width -= last_ucwidth;
6837                         }
6838                         break;
6839                 }
6841                 string  += bytes;
6842                 last_bytes = ucwidth ? bytes : 0;
6843                 last_ucwidth = ucwidth;
6844         }
6846         return string - *start;
6850 /*
6851  * Status management
6852  */
6854 /* Whether or not the curses interface has been initialized. */
6855 static bool cursed = FALSE;
6857 /* Terminal hacks and workarounds. */
6858 static bool use_scroll_redrawwin;
6859 static bool use_scroll_status_wclear;
6861 /* The status window is used for polling keystrokes. */
6862 static WINDOW *status_win;
6864 /* Reading from the prompt? */
6865 static bool input_mode = FALSE;
6867 static bool status_empty = FALSE;
6869 /* Update status and title window. */
6870 static void
6871 report(const char *msg, ...)
6873         struct view *view = display[current_view];
6875         if (input_mode)
6876                 return;
6878         if (!view) {
6879                 char buf[SIZEOF_STR];
6880                 va_list args;
6882                 va_start(args, msg);
6883                 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
6884                         buf[sizeof(buf) - 1] = 0;
6885                         buf[sizeof(buf) - 2] = '.';
6886                         buf[sizeof(buf) - 3] = '.';
6887                         buf[sizeof(buf) - 4] = '.';
6888                 }
6889                 va_end(args);
6890                 die("%s", buf);
6891         }
6893         if (!status_empty || *msg) {
6894                 va_list args;
6896                 va_start(args, msg);
6898                 wmove(status_win, 0, 0);
6899                 if (view->has_scrolled && use_scroll_status_wclear)
6900                         wclear(status_win);
6901                 if (*msg) {
6902                         vwprintw(status_win, msg, args);
6903                         status_empty = FALSE;
6904                 } else {
6905                         status_empty = TRUE;
6906                 }
6907                 wclrtoeol(status_win);
6908                 wnoutrefresh(status_win);
6910                 va_end(args);
6911         }
6913         update_view_title(view);
6916 /* Controls when nodelay should be in effect when polling user input. */
6917 static void
6918 set_nonblocking_input(bool loading)
6920         static unsigned int loading_views;
6922         if ((loading == FALSE && loading_views-- == 1) ||
6923             (loading == TRUE  && loading_views++ == 0))
6924                 nodelay(status_win, loading);
6927 static void
6928 init_display(void)
6930         const char *term;
6931         int x, y;
6933         /* Initialize the curses library */
6934         if (isatty(STDIN_FILENO)) {
6935                 cursed = !!initscr();
6936                 opt_tty = stdin;
6937         } else {
6938                 /* Leave stdin and stdout alone when acting as a pager. */
6939                 opt_tty = fopen("/dev/tty", "r+");
6940                 if (!opt_tty)
6941                         die("Failed to open /dev/tty");
6942                 cursed = !!newterm(NULL, opt_tty, opt_tty);
6943         }
6945         if (!cursed)
6946                 die("Failed to initialize curses");
6948         nonl();         /* Disable conversion and detect newlines from input. */
6949         cbreak();       /* Take input chars one at a time, no wait for \n */
6950         noecho();       /* Don't echo input */
6951         leaveok(stdscr, FALSE);
6953         if (has_colors())
6954                 init_colors();
6956         getmaxyx(stdscr, y, x);
6957         status_win = newwin(1, 0, y - 1, 0);
6958         if (!status_win)
6959                 die("Failed to create status window");
6961         /* Enable keyboard mapping */
6962         keypad(status_win, TRUE);
6963         wbkgdset(status_win, get_line_attr(LINE_STATUS));
6965         TABSIZE = opt_tab_size;
6966         if (opt_line_graphics) {
6967                 line_graphics[LINE_GRAPHIC_VLINE] = ACS_VLINE;
6968         }
6970         term = getenv("XTERM_VERSION") ? NULL : getenv("COLORTERM");
6971         if (term && !strcmp(term, "gnome-terminal")) {
6972                 /* In the gnome-terminal-emulator, the message from
6973                  * scrolling up one line when impossible followed by
6974                  * scrolling down one line causes corruption of the
6975                  * status line. This is fixed by calling wclear. */
6976                 use_scroll_status_wclear = TRUE;
6977                 use_scroll_redrawwin = FALSE;
6979         } else if (term && !strcmp(term, "xrvt-xpm")) {
6980                 /* No problems with full optimizations in xrvt-(unicode)
6981                  * and aterm. */
6982                 use_scroll_status_wclear = use_scroll_redrawwin = FALSE;
6984         } else {
6985                 /* When scrolling in (u)xterm the last line in the
6986                  * scrolling direction will update slowly. */
6987                 use_scroll_redrawwin = TRUE;
6988                 use_scroll_status_wclear = FALSE;
6989         }
6992 static int
6993 get_input(int prompt_position)
6995         struct view *view;
6996         int i, key, cursor_y, cursor_x;
6998         if (prompt_position)
6999                 input_mode = TRUE;
7001         while (TRUE) {
7002                 foreach_view (view, i) {
7003                         update_view(view);
7004                         if (view_is_displayed(view) && view->has_scrolled &&
7005                             use_scroll_redrawwin)
7006                                 redrawwin(view->win);
7007                         view->has_scrolled = FALSE;
7008                 }
7010                 /* Update the cursor position. */
7011                 if (prompt_position) {
7012                         getbegyx(status_win, cursor_y, cursor_x);
7013                         cursor_x = prompt_position;
7014                 } else {
7015                         view = display[current_view];
7016                         getbegyx(view->win, cursor_y, cursor_x);
7017                         cursor_x = view->width - 1;
7018                         cursor_y += view->lineno - view->offset;
7019                 }
7020                 setsyx(cursor_y, cursor_x);
7022                 /* Refresh, accept single keystroke of input */
7023                 doupdate();
7024                 key = wgetch(status_win);
7026                 /* wgetch() with nodelay() enabled returns ERR when
7027                  * there's no input. */
7028                 if (key == ERR) {
7030                 } else if (key == KEY_RESIZE) {
7031                         int height, width;
7033                         getmaxyx(stdscr, height, width);
7035                         wresize(status_win, 1, width);
7036                         mvwin(status_win, height - 1, 0);
7037                         wnoutrefresh(status_win);
7038                         resize_display();
7039                         redraw_display(TRUE);
7041                 } else {
7042                         input_mode = FALSE;
7043                         return key;
7044                 }
7045         }
7048 static char *
7049 prompt_input(const char *prompt, input_handler handler, void *data)
7051         enum input_status status = INPUT_OK;
7052         static char buf[SIZEOF_STR];
7053         size_t pos = 0;
7055         buf[pos] = 0;
7057         while (status == INPUT_OK || status == INPUT_SKIP) {
7058                 int key;
7060                 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
7061                 wclrtoeol(status_win);
7063                 key = get_input(pos + 1);
7064                 switch (key) {
7065                 case KEY_RETURN:
7066                 case KEY_ENTER:
7067                 case '\n':
7068                         status = pos ? INPUT_STOP : INPUT_CANCEL;
7069                         break;
7071                 case KEY_BACKSPACE:
7072                         if (pos > 0)
7073                                 buf[--pos] = 0;
7074                         else
7075                                 status = INPUT_CANCEL;
7076                         break;
7078                 case KEY_ESC:
7079                         status = INPUT_CANCEL;
7080                         break;
7082                 default:
7083                         if (pos >= sizeof(buf)) {
7084                                 report("Input string too long");
7085                                 return NULL;
7086                         }
7088                         status = handler(data, buf, key);
7089                         if (status == INPUT_OK)
7090                                 buf[pos++] = (char) key;
7091                 }
7092         }
7094         /* Clear the status window */
7095         status_empty = FALSE;
7096         report("");
7098         if (status == INPUT_CANCEL)
7099                 return NULL;
7101         buf[pos++] = 0;
7103         return buf;
7106 static enum input_status
7107 prompt_yesno_handler(void *data, char *buf, int c)
7109         if (c == 'y' || c == 'Y')
7110                 return INPUT_STOP;
7111         if (c == 'n' || c == 'N')
7112                 return INPUT_CANCEL;
7113         return INPUT_SKIP;
7116 static bool
7117 prompt_yesno(const char *prompt)
7119         char prompt2[SIZEOF_STR];
7121         if (!string_format(prompt2, "%s [Yy/Nn]", prompt))
7122                 return FALSE;
7124         return !!prompt_input(prompt2, prompt_yesno_handler, NULL);
7127 static enum input_status
7128 read_prompt_handler(void *data, char *buf, int c)
7130         return isprint(c) ? INPUT_OK : INPUT_SKIP;
7133 static char *
7134 read_prompt(const char *prompt)
7136         return prompt_input(prompt, read_prompt_handler, NULL);
7139 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected)
7141         enum input_status status = INPUT_OK;
7142         int size = 0;
7144         while (items[size].text)
7145                 size++;
7147         while (status == INPUT_OK) {
7148                 const struct menu_item *item = &items[*selected];
7149                 int key;
7150                 int i;
7152                 mvwprintw(status_win, 0, 0, "%s (%d of %d) ",
7153                           prompt, *selected + 1, size);
7154                 if (item->hotkey)
7155                         wprintw(status_win, "[%c] ", (char) item->hotkey);
7156                 wprintw(status_win, "%s", item->text);
7157                 wclrtoeol(status_win);
7159                 key = get_input(COLS - 1);
7160                 switch (key) {
7161                 case KEY_RETURN:
7162                 case KEY_ENTER:
7163                 case '\n':
7164                         status = INPUT_STOP;
7165                         break;
7167                 case KEY_LEFT:
7168                 case KEY_UP:
7169                         *selected = *selected - 1;
7170                         if (*selected < 0)
7171                                 *selected = size - 1;
7172                         break;
7174                 case KEY_RIGHT:
7175                 case KEY_DOWN:
7176                         *selected = (*selected + 1) % size;
7177                         break;
7179                 case KEY_ESC:
7180                         status = INPUT_CANCEL;
7181                         break;
7183                 default:
7184                         for (i = 0; items[i].text; i++)
7185                                 if (items[i].hotkey == key) {
7186                                         *selected = i;
7187                                         status = INPUT_STOP;
7188                                         break;
7189                                 }
7190                 }
7191         }
7193         /* Clear the status window */
7194         status_empty = FALSE;
7195         report("");
7197         return status != INPUT_CANCEL;
7200 /*
7201  * Repository properties
7202  */
7204 static struct ref **refs = NULL;
7205 static size_t refs_size = 0;
7207 static struct ref_list **ref_lists = NULL;
7208 static size_t ref_lists_size = 0;
7210 DEFINE_ALLOCATOR(realloc_refs, struct ref *, 256)
7211 DEFINE_ALLOCATOR(realloc_refs_list, struct ref *, 8)
7212 DEFINE_ALLOCATOR(realloc_ref_lists, struct ref_list *, 8)
7214 static int
7215 compare_refs(const void *ref1_, const void *ref2_)
7217         const struct ref *ref1 = *(const struct ref **)ref1_;
7218         const struct ref *ref2 = *(const struct ref **)ref2_;
7220         if (ref1->tag != ref2->tag)
7221                 return ref2->tag - ref1->tag;
7222         if (ref1->ltag != ref2->ltag)
7223                 return ref2->ltag - ref2->ltag;
7224         if (ref1->head != ref2->head)
7225                 return ref2->head - ref1->head;
7226         if (ref1->tracked != ref2->tracked)
7227                 return ref2->tracked - ref1->tracked;
7228         if (ref1->remote != ref2->remote)
7229                 return ref2->remote - ref1->remote;
7230         return strcmp(ref1->name, ref2->name);
7233 static void
7234 foreach_ref(bool (*visitor)(void *data, const struct ref *ref), void *data)
7236         size_t i;
7238         for (i = 0; i < refs_size; i++)
7239                 if (!visitor(data, refs[i]))
7240                         break;
7243 static struct ref_list *
7244 get_ref_list(const char *id)
7246         struct ref_list *list;
7247         size_t i;
7249         for (i = 0; i < ref_lists_size; i++)
7250                 if (!strcmp(id, ref_lists[i]->id))
7251                         return ref_lists[i];
7253         if (!realloc_ref_lists(&ref_lists, ref_lists_size, 1))
7254                 return NULL;
7255         list = calloc(1, sizeof(*list));
7256         if (!list)
7257                 return NULL;
7259         for (i = 0; i < refs_size; i++) {
7260                 if (!strcmp(id, refs[i]->id) &&
7261                     realloc_refs_list(&list->refs, list->size, 1))
7262                         list->refs[list->size++] = refs[i];
7263         }
7265         if (!list->refs) {
7266                 free(list);
7267                 return NULL;
7268         }
7270         qsort(list->refs, list->size, sizeof(*list->refs), compare_refs);
7271         ref_lists[ref_lists_size++] = list;
7272         return list;
7275 static int
7276 read_ref(char *id, size_t idlen, char *name, size_t namelen)
7278         struct ref *ref = NULL;
7279         bool tag = FALSE;
7280         bool ltag = FALSE;
7281         bool remote = FALSE;
7282         bool tracked = FALSE;
7283         bool head = FALSE;
7284         int from = 0, to = refs_size - 1;
7286         if (!prefixcmp(name, "refs/tags/")) {
7287                 if (!suffixcmp(name, namelen, "^{}")) {
7288                         namelen -= 3;
7289                         name[namelen] = 0;
7290                 } else {
7291                         ltag = TRUE;
7292                 }
7294                 tag = TRUE;
7295                 namelen -= STRING_SIZE("refs/tags/");
7296                 name    += STRING_SIZE("refs/tags/");
7298         } else if (!prefixcmp(name, "refs/remotes/")) {
7299                 remote = TRUE;
7300                 namelen -= STRING_SIZE("refs/remotes/");
7301                 name    += STRING_SIZE("refs/remotes/");
7302                 tracked  = !strcmp(opt_remote, name);
7304         } else if (!prefixcmp(name, "refs/heads/")) {
7305                 namelen -= STRING_SIZE("refs/heads/");
7306                 name    += STRING_SIZE("refs/heads/");
7307                 head     = !strncmp(opt_head, name, namelen);
7309         } else if (!strcmp(name, "HEAD")) {
7310                 string_ncopy(opt_head_rev, id, idlen);
7311                 return OK;
7312         }
7314         /* If we are reloading or it's an annotated tag, replace the
7315          * previous SHA1 with the resolved commit id; relies on the fact
7316          * git-ls-remote lists the commit id of an annotated tag right
7317          * before the commit id it points to. */
7318         while (from <= to) {
7319                 size_t pos = (to + from) / 2;
7320                 int cmp = strcmp(name, refs[pos]->name);
7322                 if (!cmp) {
7323                         ref = refs[pos];
7324                         break;
7325                 }
7327                 if (cmp < 0)
7328                         to = pos - 1;
7329                 else
7330                         from = pos + 1;
7331         }
7333         if (!ref) {
7334                 if (!realloc_refs(&refs, refs_size, 1))
7335                         return ERR;
7336                 ref = calloc(1, sizeof(*ref) + namelen);
7337                 if (!ref)
7338                         return ERR;
7339                 memmove(refs + from + 1, refs + from,
7340                         (refs_size - from) * sizeof(*refs));
7341                 refs[from] = ref;
7342                 strncpy(ref->name, name, namelen);
7343                 refs_size++;
7344         }
7346         ref->head = head;
7347         ref->tag = tag;
7348         ref->ltag = ltag;
7349         ref->remote = remote;
7350         ref->tracked = tracked;
7351         string_copy_rev(ref->id, id);
7353         return OK;
7356 static int
7357 load_refs(void)
7359         const char *head_argv[] = {
7360                 "git", "symbolic-ref", "HEAD", NULL
7361         };
7362         static const char *ls_remote_argv[SIZEOF_ARG] = {
7363                 "git", "ls-remote", opt_git_dir, NULL
7364         };
7365         static bool init = FALSE;
7366         size_t i;
7368         if (!init) {
7369                 argv_from_env(ls_remote_argv, "TIG_LS_REMOTE");
7370                 init = TRUE;
7371         }
7373         if (!*opt_git_dir)
7374                 return OK;
7376         if (run_io_buf(head_argv, opt_head, sizeof(opt_head)) &&
7377             !prefixcmp(opt_head, "refs/heads/")) {
7378                 char *offset = opt_head + STRING_SIZE("refs/heads/");
7380                 memmove(opt_head, offset, strlen(offset) + 1);
7381         }
7383         for (i = 0; i < refs_size; i++)
7384                 refs[i]->id[0] = 0;
7386         if (run_io_load(ls_remote_argv, "\t", read_ref) == ERR)
7387                 return ERR;
7389         /* Update the ref lists to reflect changes. */
7390         for (i = 0; i < ref_lists_size; i++) {
7391                 struct ref_list *list = ref_lists[i];
7392                 size_t old, new;
7394                 for (old = new = 0; old < list->size; old++)
7395                         if (!strcmp(list->id, list->refs[old]->id))
7396                                 list->refs[new++] = list->refs[old];
7397                 list->size = new;
7398         }
7400         return OK;
7403 static void
7404 set_remote_branch(const char *name, const char *value, size_t valuelen)
7406         if (!strcmp(name, ".remote")) {
7407                 string_ncopy(opt_remote, value, valuelen);
7409         } else if (*opt_remote && !strcmp(name, ".merge")) {
7410                 size_t from = strlen(opt_remote);
7412                 if (!prefixcmp(value, "refs/heads/"))
7413                         value += STRING_SIZE("refs/heads/");
7415                 if (!string_format_from(opt_remote, &from, "/%s", value))
7416                         opt_remote[0] = 0;
7417         }
7420 static void
7421 set_repo_config_option(char *name, char *value, int (*cmd)(int, const char **))
7423         const char *argv[SIZEOF_ARG] = { name, "=" };
7424         int argc = 1 + (cmd == option_set_command);
7425         int error = ERR;
7427         if (!argv_from_string(argv, &argc, value))
7428                 config_msg = "Too many option arguments";
7429         else
7430                 error = cmd(argc, argv);
7432         if (error == ERR)
7433                 warn("Option 'tig.%s': %s", name, config_msg);
7436 static bool
7437 set_environment_variable(const char *name, const char *value)
7439         size_t len = strlen(name) + 1 + strlen(value) + 1;
7440         char *env = malloc(len);
7442         if (env &&
7443             string_nformat(env, len, NULL, "%s=%s", name, value) &&
7444             putenv(env) == 0)
7445                 return TRUE;
7446         free(env);
7447         return FALSE;
7450 static void
7451 set_work_tree(const char *value)
7453         char cwd[SIZEOF_STR];
7455         if (!getcwd(cwd, sizeof(cwd)))
7456                 die("Failed to get cwd path: %s", strerror(errno));
7457         if (chdir(opt_git_dir) < 0)
7458                 die("Failed to chdir(%s): %s", strerror(errno));
7459         if (!getcwd(opt_git_dir, sizeof(opt_git_dir)))
7460                 die("Failed to get git path: %s", strerror(errno));
7461         if (chdir(cwd) < 0)
7462                 die("Failed to chdir(%s): %s", cwd, strerror(errno));
7463         if (chdir(value) < 0)
7464                 die("Failed to chdir(%s): %s", value, strerror(errno));
7465         if (!getcwd(cwd, sizeof(cwd)))
7466                 die("Failed to get cwd path: %s", strerror(errno));
7467         if (!set_environment_variable("GIT_WORK_TREE", cwd))
7468                 die("Failed to set GIT_WORK_TREE to '%s'", cwd);
7469         if (!set_environment_variable("GIT_DIR", opt_git_dir))
7470                 die("Failed to set GIT_DIR to '%s'", opt_git_dir);
7471         opt_is_inside_work_tree = TRUE;
7474 static int
7475 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
7477         if (!strcmp(name, "i18n.commitencoding"))
7478                 string_ncopy(opt_encoding, value, valuelen);
7480         else if (!strcmp(name, "core.editor"))
7481                 string_ncopy(opt_editor, value, valuelen);
7483         else if (!strcmp(name, "core.worktree"))
7484                 set_work_tree(value);
7486         else if (!prefixcmp(name, "tig.color."))
7487                 set_repo_config_option(name + 10, value, option_color_command);
7489         else if (!prefixcmp(name, "tig.bind."))
7490                 set_repo_config_option(name + 9, value, option_bind_command);
7492         else if (!prefixcmp(name, "tig."))
7493                 set_repo_config_option(name + 4, value, option_set_command);
7495         else if (*opt_head && !prefixcmp(name, "branch.") &&
7496                  !strncmp(name + 7, opt_head, strlen(opt_head)))
7497                 set_remote_branch(name + 7 + strlen(opt_head), value, valuelen);
7499         return OK;
7502 static int
7503 load_git_config(void)
7505         const char *config_list_argv[] = { "git", "config", "--list", NULL };
7507         return run_io_load(config_list_argv, "=", read_repo_config_option);
7510 static int
7511 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
7513         if (!opt_git_dir[0]) {
7514                 string_ncopy(opt_git_dir, name, namelen);
7516         } else if (opt_is_inside_work_tree == -1) {
7517                 /* This can be 3 different values depending on the
7518                  * version of git being used. If git-rev-parse does not
7519                  * understand --is-inside-work-tree it will simply echo
7520                  * the option else either "true" or "false" is printed.
7521                  * Default to true for the unknown case. */
7522                 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
7524         } else if (*name == '.') {
7525                 string_ncopy(opt_cdup, name, namelen);
7527         } else {
7528                 string_ncopy(opt_prefix, name, namelen);
7529         }
7531         return OK;
7534 static int
7535 load_repo_info(void)
7537         const char *rev_parse_argv[] = {
7538                 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
7539                         "--show-cdup", "--show-prefix", NULL
7540         };
7542         return run_io_load(rev_parse_argv, "=", read_repo_info);
7546 /*
7547  * Main
7548  */
7550 static const char usage[] =
7551 "tig " TIG_VERSION " (" __DATE__ ")\n"
7552 "\n"
7553 "Usage: tig        [options] [revs] [--] [paths]\n"
7554 "   or: tig show   [options] [revs] [--] [paths]\n"
7555 "   or: tig blame  [rev] path\n"
7556 "   or: tig status\n"
7557 "   or: tig <      [git command output]\n"
7558 "\n"
7559 "Options:\n"
7560 "  -v, --version   Show version and exit\n"
7561 "  -h, --help      Show help message and exit";
7563 static void __NORETURN
7564 quit(int sig)
7566         /* XXX: Restore tty modes and let the OS cleanup the rest! */
7567         if (cursed)
7568                 endwin();
7569         exit(0);
7572 static void __NORETURN
7573 die(const char *err, ...)
7575         va_list args;
7577         endwin();
7579         va_start(args, err);
7580         fputs("tig: ", stderr);
7581         vfprintf(stderr, err, args);
7582         fputs("\n", stderr);
7583         va_end(args);
7585         exit(1);
7588 static void
7589 warn(const char *msg, ...)
7591         va_list args;
7593         va_start(args, msg);
7594         fputs("tig warning: ", stderr);
7595         vfprintf(stderr, msg, args);
7596         fputs("\n", stderr);
7597         va_end(args);
7600 static enum request
7601 parse_options(int argc, const char *argv[])
7603         enum request request = REQ_VIEW_MAIN;
7604         const char *subcommand;
7605         bool seen_dashdash = FALSE;
7606         /* XXX: This is vulnerable to the user overriding options
7607          * required for the main view parser. */
7608         const char *custom_argv[SIZEOF_ARG] = {
7609                 "git", "log", "--no-color", "--pretty=raw", "--parents",
7610                         "--topo-order", NULL
7611         };
7612         int i, j = 6;
7614         if (!isatty(STDIN_FILENO)) {
7615                 io_open(&VIEW(REQ_VIEW_PAGER)->io, "");
7616                 return REQ_VIEW_PAGER;
7617         }
7619         if (argc <= 1)
7620                 return REQ_NONE;
7622         subcommand = argv[1];
7623         if (!strcmp(subcommand, "status")) {
7624                 if (argc > 2)
7625                         warn("ignoring arguments after `%s'", subcommand);
7626                 return REQ_VIEW_STATUS;
7628         } else if (!strcmp(subcommand, "blame")) {
7629                 if (argc <= 2 || argc > 4)
7630                         die("invalid number of options to blame\n\n%s", usage);
7632                 i = 2;
7633                 if (argc == 4) {
7634                         string_ncopy(opt_ref, argv[i], strlen(argv[i]));
7635                         i++;
7636                 }
7638                 string_ncopy(opt_file, argv[i], strlen(argv[i]));
7639                 return REQ_VIEW_BLAME;
7641         } else if (!strcmp(subcommand, "show")) {
7642                 request = REQ_VIEW_DIFF;
7644         } else {
7645                 subcommand = NULL;
7646         }
7648         if (subcommand) {
7649                 custom_argv[1] = subcommand;
7650                 j = 2;
7651         }
7653         for (i = 1 + !!subcommand; i < argc; i++) {
7654                 const char *opt = argv[i];
7656                 if (seen_dashdash || !strcmp(opt, "--")) {
7657                         seen_dashdash = TRUE;
7659                 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
7660                         printf("tig version %s\n", TIG_VERSION);
7661                         quit(0);
7663                 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
7664                         printf("%s\n", usage);
7665                         quit(0);
7666                 }
7668                 custom_argv[j++] = opt;
7669                 if (j >= ARRAY_SIZE(custom_argv))
7670                         die("command too long");
7671         }
7673         if (!prepare_update(VIEW(request), custom_argv, NULL, FORMAT_NONE))
7674                 die("Failed to format arguments");
7676         return request;
7679 int
7680 main(int argc, const char *argv[])
7682         enum request request = parse_options(argc, argv);
7683         struct view *view;
7684         size_t i;
7686         signal(SIGINT, quit);
7687         signal(SIGPIPE, SIG_IGN);
7689         if (setlocale(LC_ALL, "")) {
7690                 char *codeset = nl_langinfo(CODESET);
7692                 string_ncopy(opt_codeset, codeset, strlen(codeset));
7693         }
7695         if (load_repo_info() == ERR)
7696                 die("Failed to load repo info.");
7698         if (load_options() == ERR)
7699                 die("Failed to load user config.");
7701         if (load_git_config() == ERR)
7702                 die("Failed to load repo config.");
7704         /* Require a git repository unless when running in pager mode. */
7705         if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
7706                 die("Not a git repository");
7708         if (*opt_encoding && strcmp(opt_codeset, "UTF-8")) {
7709                 opt_iconv_in = iconv_open("UTF-8", opt_encoding);
7710                 if (opt_iconv_in == ICONV_NONE)
7711                         die("Failed to initialize character set conversion");
7712         }
7714         if (*opt_codeset && strcmp(opt_codeset, "UTF-8")) {
7715                 opt_iconv_out = iconv_open(opt_codeset, "UTF-8");
7716                 if (opt_iconv_out == ICONV_NONE)
7717                         die("Failed to initialize character set conversion");
7718         }
7720         if (load_refs() == ERR)
7721                 die("Failed to load refs.");
7723         foreach_view (view, i)
7724                 argv_from_env(view->ops->argv, view->cmd_env);
7726         init_display();
7728         if (request != REQ_NONE)
7729                 open_view(NULL, request, OPEN_PREPARED);
7730         request = request == REQ_NONE ? REQ_VIEW_MAIN : REQ_NONE;
7732         while (view_driver(display[current_view], request)) {
7733                 int key = get_input(0);
7735                 view = display[current_view];
7736                 request = get_keybinding(view->keymap, key);
7738                 /* Some low-level request handling. This keeps access to
7739                  * status_win restricted. */
7740                 switch (request) {
7741                 case REQ_PROMPT:
7742                 {
7743                         char *cmd = read_prompt(":");
7745                         if (cmd && isdigit(*cmd)) {
7746                                 int lineno = view->lineno + 1;
7748                                 if (parse_int(&lineno, cmd, 1, view->lines + 1) == OK) {
7749                                         select_view_line(view, lineno - 1);
7750                                         report("");
7751                                 } else {
7752                                         report("Unable to parse '%s' as a line number", cmd);
7753                                 }
7755                         } else if (cmd) {
7756                                 struct view *next = VIEW(REQ_VIEW_PAGER);
7757                                 const char *argv[SIZEOF_ARG] = { "git" };
7758                                 int argc = 1;
7760                                 /* When running random commands, initially show the
7761                                  * command in the title. However, it maybe later be
7762                                  * overwritten if a commit line is selected. */
7763                                 string_ncopy(next->ref, cmd, strlen(cmd));
7765                                 if (!argv_from_string(argv, &argc, cmd)) {
7766                                         report("Too many arguments");
7767                                 } else if (!prepare_update(next, argv, NULL, FORMAT_DASH)) {
7768                                         report("Failed to format command");
7769                                 } else {
7770                                         open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
7771                                 }
7772                         }
7774                         request = REQ_NONE;
7775                         break;
7776                 }
7777                 case REQ_SEARCH:
7778                 case REQ_SEARCH_BACK:
7779                 {
7780                         const char *prompt = request == REQ_SEARCH ? "/" : "?";
7781                         char *search = read_prompt(prompt);
7783                         if (search)
7784                                 string_ncopy(opt_search, search, strlen(search));
7785                         else if (*opt_search)
7786                                 request = request == REQ_SEARCH ?
7787                                         REQ_FIND_NEXT :
7788                                         REQ_FIND_PREV;
7789                         else
7790                                 request = REQ_NONE;
7791                         break;
7792                 }
7793                 default:
7794                         break;
7795                 }
7796         }
7798         quit(0);
7800         return 0;