Code

41d8123379eec382c30f9e7d9e9bb9c1f09892d2
[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 char *
309 enum_map_name(const char *name, size_t namelen)
311         static char buf[SIZEOF_STR];
312         int bufpos;
314         for (bufpos = 0; bufpos <= namelen; bufpos++) {
315                 buf[bufpos] = tolower(name[bufpos]);
316                 if (buf[bufpos] == '_')
317                         buf[bufpos] = '-';
318         }
320         buf[bufpos] = 0;
321         return buf;
324 #define enum_name(entry) enum_map_name((entry).name, (entry).namelen)
326 static bool
327 map_enum_do(const struct enum_map *map, size_t map_size, int *value, const char *name)
329         size_t namelen = strlen(name);
330         int i;
332         for (i = 0; i < map_size; i++)
333                 if (namelen == map[i].namelen &&
334                     !string_enum_compare(name, map[i].name, namelen)) {
335                         *value = map[i].value;
336                         return TRUE;
337                 }
339         return FALSE;
342 #define map_enum(attr, map, name) \
343         map_enum_do(map, ARRAY_SIZE(map), attr, name)
345 #define prefixcmp(str1, str2) \
346         strncmp(str1, str2, STRING_SIZE(str2))
348 static inline int
349 suffixcmp(const char *str, int slen, const char *suffix)
351         size_t len = slen >= 0 ? slen : strlen(str);
352         size_t suffixlen = strlen(suffix);
354         return suffixlen < len ? strcmp(str + len - suffixlen, suffix) : -1;
358 /*
359  * What value of "tz" was in effect back then at "time" in the
360  * local timezone?
361  */
362 static int local_tzoffset(time_t time)
364         time_t t, t_local;
365         struct tm tm;
366         int offset, eastwest; 
368         t = time;
369         localtime_r(&t, &tm);
370         t_local = mktime(&tm);
372         if (t_local < t) {
373                 eastwest = -1;
374                 offset = t - t_local;
375         } else {
376                 eastwest = 1;
377                 offset = t_local - t;
378         }
379         offset /= 60; /* in minutes */
380         offset = (offset % 60) + ((offset / 60) * 100);
381         return offset * eastwest;
384 enum date {
385         DATE_NONE = 0,
386         DATE_DEFAULT,
387         DATE_RELATIVE,
388         DATE_SHORT
389 };
391 static char *
392 string_date(const time_t *time, enum date date)
394         static char buf[DATE_COLS + 1];
395         static const struct enum_map reldate[] = {
396                 { "second", 1,                  60 * 2 },
397                 { "minute", 60,                 60 * 60 * 2 },
398                 { "hour",   60 * 60,            60 * 60 * 24 * 2 },
399                 { "day",    60 * 60 * 24,       60 * 60 * 24 * 7 * 2 },
400                 { "week",   60 * 60 * 24 * 7,   60 * 60 * 24 * 7 * 5 },
401                 { "month",  60 * 60 * 24 * 30,  60 * 60 * 24 * 30 * 12 },
402         };
403         struct tm tm;
405         if (date == DATE_RELATIVE) {
406                 struct timeval now;
407                 time_t date = *time + local_tzoffset(*time);
408                 time_t seconds;
409                 int i;
411                 gettimeofday(&now, NULL);
412                 seconds = now.tv_sec < date ? date - now.tv_sec : now.tv_sec - date;
413                 for (i = 0; i < ARRAY_SIZE(reldate); i++) {
414                         if (seconds >= reldate[i].value)
415                                 continue;
417                         seconds /= reldate[i].namelen;
418                         if (!string_format(buf, "%ld %s%s %s",
419                                            seconds, reldate[i].name,
420                                            seconds > 1 ? "s" : "",
421                                            now.tv_sec >= date ? "ago" : "ahead"))
422                                 break;
423                         return buf;
424                 }
425         }
427         gmtime_r(time, &tm);
428         return strftime(buf, sizeof(buf), DATE_FORMAT, &tm) ? buf : NULL;
432 static bool
433 argv_from_string(const char *argv[SIZEOF_ARG], int *argc, char *cmd)
435         int valuelen;
437         while (*cmd && *argc < SIZEOF_ARG && (valuelen = strcspn(cmd, " \t"))) {
438                 bool advance = cmd[valuelen] != 0;
440                 cmd[valuelen] = 0;
441                 argv[(*argc)++] = chomp_string(cmd);
442                 cmd = chomp_string(cmd + valuelen + advance);
443         }
445         if (*argc < SIZEOF_ARG)
446                 argv[*argc] = NULL;
447         return *argc < SIZEOF_ARG;
450 static void
451 argv_from_env(const char **argv, const char *name)
453         char *env = argv ? getenv(name) : NULL;
454         int argc = 0;
456         if (env && *env)
457                 env = strdup(env);
458         if (env && !argv_from_string(argv, &argc, env))
459                 die("Too many arguments in the `%s` environment variable", name);
463 /*
464  * Executing external commands.
465  */
467 enum io_type {
468         IO_FD,                  /* File descriptor based IO. */
469         IO_BG,                  /* Execute command in the background. */
470         IO_FG,                  /* Execute command with same std{in,out,err}. */
471         IO_RD,                  /* Read only fork+exec IO. */
472         IO_WR,                  /* Write only fork+exec IO. */
473         IO_AP,                  /* Append fork+exec output to file. */
474 };
476 struct io {
477         enum io_type type;      /* The requested type of pipe. */
478         const char *dir;        /* Directory from which to execute. */
479         pid_t pid;              /* Pipe for reading or writing. */
480         int pipe;               /* Pipe end for reading or writing. */
481         int error;              /* Error status. */
482         const char *argv[SIZEOF_ARG];   /* Shell command arguments. */
483         char *buf;              /* Read buffer. */
484         size_t bufalloc;        /* Allocated buffer size. */
485         size_t bufsize;         /* Buffer content size. */
486         char *bufpos;           /* Current buffer position. */
487         unsigned int eof:1;     /* Has end of file been reached. */
488 };
490 static void
491 reset_io(struct io *io)
493         io->pipe = -1;
494         io->pid = 0;
495         io->buf = io->bufpos = NULL;
496         io->bufalloc = io->bufsize = 0;
497         io->error = 0;
498         io->eof = 0;
501 static void
502 init_io(struct io *io, const char *dir, enum io_type type)
504         reset_io(io);
505         io->type = type;
506         io->dir = dir;
509 static bool
510 init_io_rd(struct io *io, const char *argv[], const char *dir,
511                 enum format_flags flags)
513         init_io(io, dir, IO_RD);
514         return format_argv(io->argv, argv, flags);
517 static bool
518 io_open(struct io *io, const char *fmt, ...)
520         char name[SIZEOF_STR] = "";
521         bool fits;
522         va_list args;
524         init_io(io, NULL, IO_FD);
526         va_start(args, fmt);
527         fits = vsnprintf(name, sizeof(name), fmt, args) < sizeof(name);
528         va_end(args);
530         if (!fits) {
531                 io->error = ENAMETOOLONG;
532                 return FALSE;
533         }
534         io->pipe = *name ? open(name, O_RDONLY) : STDIN_FILENO;
535         if (io->pipe == -1)
536                 io->error = errno;
537         return io->pipe != -1;
540 static bool
541 kill_io(struct io *io)
543         return io->pid == 0 || kill(io->pid, SIGKILL) != -1;
546 static bool
547 done_io(struct io *io)
549         pid_t pid = io->pid;
551         if (io->pipe != -1)
552                 close(io->pipe);
553         free(io->buf);
554         reset_io(io);
556         while (pid > 0) {
557                 int status;
558                 pid_t waiting = waitpid(pid, &status, 0);
560                 if (waiting < 0) {
561                         if (errno == EINTR)
562                                 continue;
563                         report("waitpid failed (%s)", strerror(errno));
564                         return FALSE;
565                 }
567                 return waiting == pid &&
568                        !WIFSIGNALED(status) &&
569                        WIFEXITED(status) &&
570                        !WEXITSTATUS(status);
571         }
573         return TRUE;
576 static bool
577 start_io(struct io *io)
579         int pipefds[2] = { -1, -1 };
581         if (io->type == IO_FD)
582                 return TRUE;
584         if ((io->type == IO_RD || io->type == IO_WR) &&
585             pipe(pipefds) < 0)
586                 return FALSE;
587         else if (io->type == IO_AP)
588                 pipefds[1] = io->pipe;
590         if ((io->pid = fork())) {
591                 if (pipefds[!(io->type == IO_WR)] != -1)
592                         close(pipefds[!(io->type == IO_WR)]);
593                 if (io->pid != -1) {
594                         io->pipe = pipefds[!!(io->type == IO_WR)];
595                         return TRUE;
596                 }
598         } else {
599                 if (io->type != IO_FG) {
600                         int devnull = open("/dev/null", O_RDWR);
601                         int readfd  = io->type == IO_WR ? pipefds[0] : devnull;
602                         int writefd = (io->type == IO_RD || io->type == IO_AP)
603                                                         ? pipefds[1] : devnull;
605                         dup2(readfd,  STDIN_FILENO);
606                         dup2(writefd, STDOUT_FILENO);
607                         dup2(devnull, STDERR_FILENO);
609                         close(devnull);
610                         if (pipefds[0] != -1)
611                                 close(pipefds[0]);
612                         if (pipefds[1] != -1)
613                                 close(pipefds[1]);
614                 }
616                 if (io->dir && *io->dir && chdir(io->dir) == -1)
617                         die("Failed to change directory: %s", strerror(errno));
619                 execvp(io->argv[0], (char *const*) io->argv);
620                 die("Failed to execute program: %s", strerror(errno));
621         }
623         if (pipefds[!!(io->type == IO_WR)] != -1)
624                 close(pipefds[!!(io->type == IO_WR)]);
625         return FALSE;
628 static bool
629 run_io(struct io *io, const char **argv, const char *dir, enum io_type type)
631         init_io(io, dir, type);
632         if (!format_argv(io->argv, argv, FORMAT_NONE))
633                 return FALSE;
634         return start_io(io);
637 static int
638 run_io_do(struct io *io)
640         return start_io(io) && done_io(io);
643 static int
644 run_io_bg(const char **argv)
646         struct io io = {};
648         init_io(&io, NULL, IO_BG);
649         if (!format_argv(io.argv, argv, FORMAT_NONE))
650                 return FALSE;
651         return run_io_do(&io);
654 static bool
655 run_io_fg(const char **argv, const char *dir)
657         struct io io = {};
659         init_io(&io, dir, IO_FG);
660         if (!format_argv(io.argv, argv, FORMAT_NONE))
661                 return FALSE;
662         return run_io_do(&io);
665 static bool
666 run_io_append(const char **argv, enum format_flags flags, int fd)
668         struct io io = {};
670         init_io(&io, NULL, IO_AP);
671         io.pipe = fd;
672         if (format_argv(io.argv, argv, flags))
673                 return run_io_do(&io);
674         close(fd);
675         return FALSE;
678 static bool
679 run_io_rd(struct io *io, const char **argv, const char *dir, enum format_flags flags)
681         return init_io_rd(io, argv, dir, flags) && start_io(io);
684 static bool
685 io_eof(struct io *io)
687         return io->eof;
690 static int
691 io_error(struct io *io)
693         return io->error;
696 static char *
697 io_strerror(struct io *io)
699         return strerror(io->error);
702 static bool
703 io_can_read(struct io *io)
705         struct timeval tv = { 0, 500 };
706         fd_set fds;
708         FD_ZERO(&fds);
709         FD_SET(io->pipe, &fds);
711         return select(io->pipe + 1, &fds, NULL, NULL, &tv) > 0;
714 static ssize_t
715 io_read(struct io *io, void *buf, size_t bufsize)
717         do {
718                 ssize_t readsize = read(io->pipe, buf, bufsize);
720                 if (readsize < 0 && (errno == EAGAIN || errno == EINTR))
721                         continue;
722                 else if (readsize == -1)
723                         io->error = errno;
724                 else if (readsize == 0)
725                         io->eof = 1;
726                 return readsize;
727         } while (1);
730 DEFINE_ALLOCATOR(realloc_io_buf, char, BUFSIZ)
732 static char *
733 io_get(struct io *io, int c, bool can_read)
735         char *eol;
736         ssize_t readsize;
738         while (TRUE) {
739                 if (io->bufsize > 0) {
740                         eol = memchr(io->bufpos, c, io->bufsize);
741                         if (eol) {
742                                 char *line = io->bufpos;
744                                 *eol = 0;
745                                 io->bufpos = eol + 1;
746                                 io->bufsize -= io->bufpos - line;
747                                 return line;
748                         }
749                 }
751                 if (io_eof(io)) {
752                         if (io->bufsize) {
753                                 io->bufpos[io->bufsize] = 0;
754                                 io->bufsize = 0;
755                                 return io->bufpos;
756                         }
757                         return NULL;
758                 }
760                 if (!can_read)
761                         return NULL;
763                 if (io->bufsize > 0 && io->bufpos > io->buf)
764                         memmove(io->buf, io->bufpos, io->bufsize);
766                 if (io->bufalloc == io->bufsize) {
767                         if (!realloc_io_buf(&io->buf, io->bufalloc, BUFSIZ))
768                                 return NULL;
769                         io->bufalloc += BUFSIZ;
770                 }
772                 io->bufpos = io->buf;
773                 readsize = io_read(io, io->buf + io->bufsize, io->bufalloc - io->bufsize);
774                 if (io_error(io))
775                         return NULL;
776                 io->bufsize += readsize;
777         }
780 static bool
781 io_write(struct io *io, const void *buf, size_t bufsize)
783         size_t written = 0;
785         while (!io_error(io) && written < bufsize) {
786                 ssize_t size;
788                 size = write(io->pipe, buf + written, bufsize - written);
789                 if (size < 0 && (errno == EAGAIN || errno == EINTR))
790                         continue;
791                 else if (size == -1)
792                         io->error = errno;
793                 else
794                         written += size;
795         }
797         return written == bufsize;
800 static bool
801 io_read_buf(struct io *io, char buf[], size_t bufsize)
803         char *result = io_get(io, '\n', TRUE);
805         if (result) {
806                 result = chomp_string(result);
807                 string_ncopy_do(buf, bufsize, result, strlen(result));
808         }
810         return done_io(io) && result;
813 static bool
814 run_io_buf(const char **argv, char buf[], size_t bufsize)
816         struct io io = {};
818         return run_io_rd(&io, argv, NULL, FORMAT_NONE)
819             && io_read_buf(&io, buf, bufsize);
822 static int
823 io_load(struct io *io, const char *separators,
824         int (*read_property)(char *, size_t, char *, size_t))
826         char *name;
827         int state = OK;
829         if (!start_io(io))
830                 return ERR;
832         while (state == OK && (name = io_get(io, '\n', TRUE))) {
833                 char *value;
834                 size_t namelen;
835                 size_t valuelen;
837                 name = chomp_string(name);
838                 namelen = strcspn(name, separators);
840                 if (name[namelen]) {
841                         name[namelen] = 0;
842                         value = chomp_string(name + namelen + 1);
843                         valuelen = strlen(value);
845                 } else {
846                         value = "";
847                         valuelen = 0;
848                 }
850                 state = read_property(name, namelen, value, valuelen);
851         }
853         if (state != ERR && io_error(io))
854                 state = ERR;
855         done_io(io);
857         return state;
860 static int
861 run_io_load(const char **argv, const char *separators,
862             int (*read_property)(char *, size_t, char *, size_t))
864         struct io io = {};
866         return init_io_rd(&io, argv, NULL, FORMAT_NONE)
867                 ? io_load(&io, separators, read_property) : ERR;
871 /*
872  * User requests
873  */
875 #define REQ_INFO \
876         /* XXX: Keep the view request first and in sync with views[]. */ \
877         REQ_GROUP("View switching") \
878         REQ_(VIEW_MAIN,         "Show main view"), \
879         REQ_(VIEW_DIFF,         "Show diff view"), \
880         REQ_(VIEW_LOG,          "Show log view"), \
881         REQ_(VIEW_TREE,         "Show tree view"), \
882         REQ_(VIEW_BLOB,         "Show blob view"), \
883         REQ_(VIEW_BLAME,        "Show blame view"), \
884         REQ_(VIEW_BRANCH,       "Show branch view"), \
885         REQ_(VIEW_HELP,         "Show help page"), \
886         REQ_(VIEW_PAGER,        "Show pager view"), \
887         REQ_(VIEW_STATUS,       "Show status view"), \
888         REQ_(VIEW_STAGE,        "Show stage view"), \
889         \
890         REQ_GROUP("View manipulation") \
891         REQ_(ENTER,             "Enter current line and scroll"), \
892         REQ_(NEXT,              "Move to next"), \
893         REQ_(PREVIOUS,          "Move to previous"), \
894         REQ_(PARENT,            "Move to parent"), \
895         REQ_(VIEW_NEXT,         "Move focus to next view"), \
896         REQ_(REFRESH,           "Reload and refresh"), \
897         REQ_(MAXIMIZE,          "Maximize the current view"), \
898         REQ_(VIEW_CLOSE,        "Close the current view"), \
899         REQ_(QUIT,              "Close all views and quit"), \
900         \
901         REQ_GROUP("View specific requests") \
902         REQ_(STATUS_UPDATE,     "Update file status"), \
903         REQ_(STATUS_REVERT,     "Revert file changes"), \
904         REQ_(STATUS_MERGE,      "Merge file using external tool"), \
905         REQ_(STAGE_NEXT,        "Find next chunk to stage"), \
906         \
907         REQ_GROUP("Cursor navigation") \
908         REQ_(MOVE_UP,           "Move cursor one line up"), \
909         REQ_(MOVE_DOWN,         "Move cursor one line down"), \
910         REQ_(MOVE_PAGE_DOWN,    "Move cursor one page down"), \
911         REQ_(MOVE_PAGE_UP,      "Move cursor one page up"), \
912         REQ_(MOVE_FIRST_LINE,   "Move cursor to first line"), \
913         REQ_(MOVE_LAST_LINE,    "Move cursor to last line"), \
914         \
915         REQ_GROUP("Scrolling") \
916         REQ_(SCROLL_LEFT,       "Scroll two columns left"), \
917         REQ_(SCROLL_RIGHT,      "Scroll two columns right"), \
918         REQ_(SCROLL_LINE_UP,    "Scroll one line up"), \
919         REQ_(SCROLL_LINE_DOWN,  "Scroll one line down"), \
920         REQ_(SCROLL_PAGE_UP,    "Scroll one page up"), \
921         REQ_(SCROLL_PAGE_DOWN,  "Scroll one page down"), \
922         \
923         REQ_GROUP("Searching") \
924         REQ_(SEARCH,            "Search the view"), \
925         REQ_(SEARCH_BACK,       "Search backwards in the view"), \
926         REQ_(FIND_NEXT,         "Find next search match"), \
927         REQ_(FIND_PREV,         "Find previous search match"), \
928         \
929         REQ_GROUP("Option manipulation") \
930         REQ_(OPTIONS,           "Open option menu"), \
931         REQ_(TOGGLE_LINENO,     "Toggle line numbers"), \
932         REQ_(TOGGLE_DATE,       "Toggle date display"), \
933         REQ_(TOGGLE_DATE_SHORT, "Toggle short (date-only) dates"), \
934         REQ_(TOGGLE_AUTHOR,     "Toggle author display"), \
935         REQ_(TOGGLE_REV_GRAPH,  "Toggle revision graph visualization"), \
936         REQ_(TOGGLE_REFS,       "Toggle reference display (tags/branches)"), \
937         REQ_(TOGGLE_SORT_ORDER, "Toggle ascending/descending sort order"), \
938         REQ_(TOGGLE_SORT_FIELD, "Toggle field to sort by"), \
939         \
940         REQ_GROUP("Misc") \
941         REQ_(PROMPT,            "Bring up the prompt"), \
942         REQ_(SCREEN_REDRAW,     "Redraw the screen"), \
943         REQ_(SHOW_VERSION,      "Show version information"), \
944         REQ_(STOP_LOADING,      "Stop all loading views"), \
945         REQ_(EDIT,              "Open in editor"), \
946         REQ_(NONE,              "Do nothing")
949 /* User action requests. */
950 enum request {
951 #define REQ_GROUP(help)
952 #define REQ_(req, help) REQ_##req
954         /* Offset all requests to avoid conflicts with ncurses getch values. */
955         REQ_OFFSET = KEY_MAX + 1,
956         REQ_INFO
958 #undef  REQ_GROUP
959 #undef  REQ_
960 };
962 struct request_info {
963         enum request request;
964         const char *name;
965         int namelen;
966         const char *help;
967 };
969 static const struct request_info req_info[] = {
970 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
971 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
972         REQ_INFO
973 #undef  REQ_GROUP
974 #undef  REQ_
975 };
977 static enum request
978 get_request(const char *name)
980         int namelen = strlen(name);
981         int i;
983         for (i = 0; i < ARRAY_SIZE(req_info); i++)
984                 if (req_info[i].namelen == namelen &&
985                     !string_enum_compare(req_info[i].name, name, namelen))
986                         return req_info[i].request;
988         return REQ_NONE;
992 /*
993  * Options
994  */
996 /* Option and state variables. */
997 static enum date opt_date               = DATE_DEFAULT;
998 static bool opt_author                  = TRUE;
999 static bool opt_line_number             = FALSE;
1000 static bool opt_line_graphics           = TRUE;
1001 static bool opt_rev_graph               = FALSE;
1002 static bool opt_show_refs               = TRUE;
1003 static int opt_num_interval             = 5;
1004 static double opt_hscroll               = 0.50;
1005 static double opt_scale_split_view      = 2.0 / 3.0;
1006 static int opt_tab_size                 = 8;
1007 static int opt_author_cols              = 19;
1008 static char opt_path[SIZEOF_STR]        = "";
1009 static char opt_file[SIZEOF_STR]        = "";
1010 static char opt_ref[SIZEOF_REF]         = "";
1011 static char opt_head[SIZEOF_REF]        = "";
1012 static char opt_head_rev[SIZEOF_REV]    = "";
1013 static char opt_remote[SIZEOF_REF]      = "";
1014 static char opt_encoding[20]            = "UTF-8";
1015 static char opt_codeset[20]             = "UTF-8";
1016 static iconv_t opt_iconv_in             = ICONV_NONE;
1017 static iconv_t opt_iconv_out            = ICONV_NONE;
1018 static char opt_search[SIZEOF_STR]      = "";
1019 static char opt_cdup[SIZEOF_STR]        = "";
1020 static char opt_prefix[SIZEOF_STR]      = "";
1021 static char opt_git_dir[SIZEOF_STR]     = "";
1022 static signed char opt_is_inside_work_tree      = -1; /* set to TRUE or FALSE */
1023 static char opt_editor[SIZEOF_STR]      = "";
1024 static FILE *opt_tty                    = NULL;
1026 #define is_initial_commit()     (!*opt_head_rev)
1027 #define is_head_commit(rev)     (!strcmp((rev), "HEAD") || !strcmp(opt_head_rev, (rev)))
1028 #define mkdate(time)            string_date(time, opt_date)
1031 /*
1032  * Line-oriented content detection.
1033  */
1035 #define LINE_INFO \
1036 LINE(DIFF_HEADER,  "diff --git ",       COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1037 LINE(DIFF_CHUNK,   "@@",                COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1038 LINE(DIFF_ADD,     "+",                 COLOR_GREEN,    COLOR_DEFAULT,  0), \
1039 LINE(DIFF_DEL,     "-",                 COLOR_RED,      COLOR_DEFAULT,  0), \
1040 LINE(DIFF_INDEX,        "index ",         COLOR_BLUE,   COLOR_DEFAULT,  0), \
1041 LINE(DIFF_OLDMODE,      "old file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
1042 LINE(DIFF_NEWMODE,      "new file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
1043 LINE(DIFF_COPY_FROM,    "copy from",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
1044 LINE(DIFF_COPY_TO,      "copy to",        COLOR_YELLOW, COLOR_DEFAULT,  0), \
1045 LINE(DIFF_RENAME_FROM,  "rename from",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
1046 LINE(DIFF_RENAME_TO,    "rename to",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
1047 LINE(DIFF_SIMILARITY,   "similarity ",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
1048 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
1049 LINE(DIFF_TREE,         "diff-tree ",     COLOR_BLUE,   COLOR_DEFAULT,  0), \
1050 LINE(PP_AUTHOR,    "Author: ",          COLOR_CYAN,     COLOR_DEFAULT,  0), \
1051 LINE(PP_COMMIT,    "Commit: ",          COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1052 LINE(PP_MERGE,     "Merge: ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
1053 LINE(PP_DATE,      "Date:   ",          COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1054 LINE(PP_ADATE,     "AuthorDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1055 LINE(PP_CDATE,     "CommitDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1056 LINE(PP_REFS,      "Refs: ",            COLOR_RED,      COLOR_DEFAULT,  0), \
1057 LINE(COMMIT,       "commit ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
1058 LINE(PARENT,       "parent ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
1059 LINE(TREE,         "tree ",             COLOR_BLUE,     COLOR_DEFAULT,  0), \
1060 LINE(AUTHOR,       "author ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
1061 LINE(COMMITTER,    "committer ",        COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1062 LINE(SIGNOFF,      "    Signed-off-by", COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1063 LINE(ACKED,        "    Acked-by",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1064 LINE(DEFAULT,      "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
1065 LINE(CURSOR,       "",                  COLOR_WHITE,    COLOR_GREEN,    A_BOLD), \
1066 LINE(STATUS,       "",                  COLOR_GREEN,    COLOR_DEFAULT,  0), \
1067 LINE(DELIMITER,    "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1068 LINE(DATE,         "",                  COLOR_BLUE,     COLOR_DEFAULT,  0), \
1069 LINE(MODE,         "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1070 LINE(LINE_NUMBER,  "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1071 LINE(TITLE_BLUR,   "",                  COLOR_WHITE,    COLOR_BLUE,     0), \
1072 LINE(TITLE_FOCUS,  "",                  COLOR_WHITE,    COLOR_BLUE,     A_BOLD), \
1073 LINE(MAIN_COMMIT,  "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
1074 LINE(MAIN_TAG,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  A_BOLD), \
1075 LINE(MAIN_LOCAL_TAG,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1076 LINE(MAIN_REMOTE,  "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1077 LINE(MAIN_TRACKED, "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_BOLD), \
1078 LINE(MAIN_REF,     "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1079 LINE(MAIN_HEAD,    "",                  COLOR_CYAN,     COLOR_DEFAULT,  A_BOLD), \
1080 LINE(MAIN_REVGRAPH,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1081 LINE(TREE_HEAD,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_BOLD), \
1082 LINE(TREE_DIR,     "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_NORMAL), \
1083 LINE(TREE_FILE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
1084 LINE(STAT_HEAD,    "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1085 LINE(STAT_SECTION, "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1086 LINE(STAT_NONE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
1087 LINE(STAT_STAGED,  "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1088 LINE(STAT_UNSTAGED,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1089 LINE(STAT_UNTRACKED,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1090 LINE(HELP_KEYMAP,  "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1091 LINE(HELP_GROUP,   "",                  COLOR_BLUE,     COLOR_DEFAULT,  0), \
1092 LINE(BLAME_ID,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0)
1094 enum line_type {
1095 #define LINE(type, line, fg, bg, attr) \
1096         LINE_##type
1097         LINE_INFO,
1098         LINE_NONE
1099 #undef  LINE
1100 };
1102 struct line_info {
1103         const char *name;       /* Option name. */
1104         int namelen;            /* Size of option name. */
1105         const char *line;       /* The start of line to match. */
1106         int linelen;            /* Size of string to match. */
1107         int fg, bg, attr;       /* Color and text attributes for the lines. */
1108 };
1110 static struct line_info line_info[] = {
1111 #define LINE(type, line, fg, bg, attr) \
1112         { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
1113         LINE_INFO
1114 #undef  LINE
1115 };
1117 static enum line_type
1118 get_line_type(const char *line)
1120         int linelen = strlen(line);
1121         enum line_type type;
1123         for (type = 0; type < ARRAY_SIZE(line_info); type++)
1124                 /* Case insensitive search matches Signed-off-by lines better. */
1125                 if (linelen >= line_info[type].linelen &&
1126                     !strncasecmp(line_info[type].line, line, line_info[type].linelen))
1127                         return type;
1129         return LINE_DEFAULT;
1132 static inline int
1133 get_line_attr(enum line_type type)
1135         assert(type < ARRAY_SIZE(line_info));
1136         return COLOR_PAIR(type) | line_info[type].attr;
1139 static struct line_info *
1140 get_line_info(const char *name)
1142         size_t namelen = strlen(name);
1143         enum line_type type;
1145         for (type = 0; type < ARRAY_SIZE(line_info); type++)
1146                 if (namelen == line_info[type].namelen &&
1147                     !string_enum_compare(line_info[type].name, name, namelen))
1148                         return &line_info[type];
1150         return NULL;
1153 static void
1154 init_colors(void)
1156         int default_bg = line_info[LINE_DEFAULT].bg;
1157         int default_fg = line_info[LINE_DEFAULT].fg;
1158         enum line_type type;
1160         start_color();
1162         if (assume_default_colors(default_fg, default_bg) == ERR) {
1163                 default_bg = COLOR_BLACK;
1164                 default_fg = COLOR_WHITE;
1165         }
1167         for (type = 0; type < ARRAY_SIZE(line_info); type++) {
1168                 struct line_info *info = &line_info[type];
1169                 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
1170                 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
1172                 init_pair(type, fg, bg);
1173         }
1176 struct line {
1177         enum line_type type;
1179         /* State flags */
1180         unsigned int selected:1;
1181         unsigned int dirty:1;
1182         unsigned int cleareol:1;
1183         unsigned int other:16;
1185         void *data;             /* User data */
1186 };
1189 /*
1190  * Keys
1191  */
1193 struct keybinding {
1194         int alias;
1195         enum request request;
1196 };
1198 static const struct keybinding default_keybindings[] = {
1199         /* View switching */
1200         { 'm',          REQ_VIEW_MAIN },
1201         { 'd',          REQ_VIEW_DIFF },
1202         { 'l',          REQ_VIEW_LOG },
1203         { 't',          REQ_VIEW_TREE },
1204         { 'f',          REQ_VIEW_BLOB },
1205         { 'B',          REQ_VIEW_BLAME },
1206         { 'H',          REQ_VIEW_BRANCH },
1207         { 'p',          REQ_VIEW_PAGER },
1208         { 'h',          REQ_VIEW_HELP },
1209         { 'S',          REQ_VIEW_STATUS },
1210         { 'c',          REQ_VIEW_STAGE },
1212         /* View manipulation */
1213         { 'q',          REQ_VIEW_CLOSE },
1214         { KEY_TAB,      REQ_VIEW_NEXT },
1215         { KEY_RETURN,   REQ_ENTER },
1216         { KEY_UP,       REQ_PREVIOUS },
1217         { KEY_DOWN,     REQ_NEXT },
1218         { 'R',          REQ_REFRESH },
1219         { KEY_F(5),     REQ_REFRESH },
1220         { 'O',          REQ_MAXIMIZE },
1222         /* Cursor navigation */
1223         { 'k',          REQ_MOVE_UP },
1224         { 'j',          REQ_MOVE_DOWN },
1225         { KEY_HOME,     REQ_MOVE_FIRST_LINE },
1226         { KEY_END,      REQ_MOVE_LAST_LINE },
1227         { KEY_NPAGE,    REQ_MOVE_PAGE_DOWN },
1228         { ' ',          REQ_MOVE_PAGE_DOWN },
1229         { KEY_PPAGE,    REQ_MOVE_PAGE_UP },
1230         { 'b',          REQ_MOVE_PAGE_UP },
1231         { '-',          REQ_MOVE_PAGE_UP },
1233         /* Scrolling */
1234         { KEY_LEFT,     REQ_SCROLL_LEFT },
1235         { KEY_RIGHT,    REQ_SCROLL_RIGHT },
1236         { KEY_IC,       REQ_SCROLL_LINE_UP },
1237         { KEY_DC,       REQ_SCROLL_LINE_DOWN },
1238         { 'w',          REQ_SCROLL_PAGE_UP },
1239         { 's',          REQ_SCROLL_PAGE_DOWN },
1241         /* Searching */
1242         { '/',          REQ_SEARCH },
1243         { '?',          REQ_SEARCH_BACK },
1244         { 'n',          REQ_FIND_NEXT },
1245         { 'N',          REQ_FIND_PREV },
1247         /* Misc */
1248         { 'Q',          REQ_QUIT },
1249         { 'z',          REQ_STOP_LOADING },
1250         { 'v',          REQ_SHOW_VERSION },
1251         { 'r',          REQ_SCREEN_REDRAW },
1252         { 'o',          REQ_OPTIONS },
1253         { '.',          REQ_TOGGLE_LINENO },
1254         { 'D',          REQ_TOGGLE_DATE },
1255         { 'A',          REQ_TOGGLE_AUTHOR },
1256         { 'g',          REQ_TOGGLE_REV_GRAPH },
1257         { 'F',          REQ_TOGGLE_REFS },
1258         { 'I',          REQ_TOGGLE_SORT_ORDER },
1259         { 'i',          REQ_TOGGLE_SORT_FIELD },
1260         { ':',          REQ_PROMPT },
1261         { 'u',          REQ_STATUS_UPDATE },
1262         { '!',          REQ_STATUS_REVERT },
1263         { 'M',          REQ_STATUS_MERGE },
1264         { '@',          REQ_STAGE_NEXT },
1265         { ',',          REQ_PARENT },
1266         { 'e',          REQ_EDIT },
1267 };
1269 #define KEYMAP_INFO \
1270         KEYMAP_(GENERIC), \
1271         KEYMAP_(MAIN), \
1272         KEYMAP_(DIFF), \
1273         KEYMAP_(LOG), \
1274         KEYMAP_(TREE), \
1275         KEYMAP_(BLOB), \
1276         KEYMAP_(BLAME), \
1277         KEYMAP_(BRANCH), \
1278         KEYMAP_(PAGER), \
1279         KEYMAP_(HELP), \
1280         KEYMAP_(STATUS), \
1281         KEYMAP_(STAGE)
1283 enum keymap {
1284 #define KEYMAP_(name) KEYMAP_##name
1285         KEYMAP_INFO
1286 #undef  KEYMAP_
1287 };
1289 static const struct enum_map keymap_table[] = {
1290 #define KEYMAP_(name) ENUM_MAP(#name, KEYMAP_##name)
1291         KEYMAP_INFO
1292 #undef  KEYMAP_
1293 };
1295 #define set_keymap(map, name) map_enum(map, keymap_table, name)
1297 struct keybinding_table {
1298         struct keybinding *data;
1299         size_t size;
1300 };
1302 static struct keybinding_table keybindings[ARRAY_SIZE(keymap_table)];
1304 static void
1305 add_keybinding(enum keymap keymap, enum request request, int key)
1307         struct keybinding_table *table = &keybindings[keymap];
1309         table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data));
1310         if (!table->data)
1311                 die("Failed to allocate keybinding");
1312         table->data[table->size].alias = key;
1313         table->data[table->size++].request = request;
1316 /* Looks for a key binding first in the given map, then in the generic map, and
1317  * lastly in the default keybindings. */
1318 static enum request
1319 get_keybinding(enum keymap keymap, int key)
1321         size_t i;
1323         for (i = 0; i < keybindings[keymap].size; i++)
1324                 if (keybindings[keymap].data[i].alias == key)
1325                         return keybindings[keymap].data[i].request;
1327         for (i = 0; i < keybindings[KEYMAP_GENERIC].size; i++)
1328                 if (keybindings[KEYMAP_GENERIC].data[i].alias == key)
1329                         return keybindings[KEYMAP_GENERIC].data[i].request;
1331         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1332                 if (default_keybindings[i].alias == key)
1333                         return default_keybindings[i].request;
1335         return (enum request) key;
1339 struct key {
1340         const char *name;
1341         int value;
1342 };
1344 static const struct key key_table[] = {
1345         { "Enter",      KEY_RETURN },
1346         { "Space",      ' ' },
1347         { "Backspace",  KEY_BACKSPACE },
1348         { "Tab",        KEY_TAB },
1349         { "Escape",     KEY_ESC },
1350         { "Left",       KEY_LEFT },
1351         { "Right",      KEY_RIGHT },
1352         { "Up",         KEY_UP },
1353         { "Down",       KEY_DOWN },
1354         { "Insert",     KEY_IC },
1355         { "Delete",     KEY_DC },
1356         { "Hash",       '#' },
1357         { "Home",       KEY_HOME },
1358         { "End",        KEY_END },
1359         { "PageUp",     KEY_PPAGE },
1360         { "PageDown",   KEY_NPAGE },
1361         { "F1",         KEY_F(1) },
1362         { "F2",         KEY_F(2) },
1363         { "F3",         KEY_F(3) },
1364         { "F4",         KEY_F(4) },
1365         { "F5",         KEY_F(5) },
1366         { "F6",         KEY_F(6) },
1367         { "F7",         KEY_F(7) },
1368         { "F8",         KEY_F(8) },
1369         { "F9",         KEY_F(9) },
1370         { "F10",        KEY_F(10) },
1371         { "F11",        KEY_F(11) },
1372         { "F12",        KEY_F(12) },
1373 };
1375 static int
1376 get_key_value(const char *name)
1378         int i;
1380         for (i = 0; i < ARRAY_SIZE(key_table); i++)
1381                 if (!strcasecmp(key_table[i].name, name))
1382                         return key_table[i].value;
1384         if (strlen(name) == 1 && isprint(*name))
1385                 return (int) *name;
1387         return ERR;
1390 static const char *
1391 get_key_name(int key_value)
1393         static char key_char[] = "'X'";
1394         const char *seq = NULL;
1395         int key;
1397         for (key = 0; key < ARRAY_SIZE(key_table); key++)
1398                 if (key_table[key].value == key_value)
1399                         seq = key_table[key].name;
1401         if (seq == NULL &&
1402             key_value < 127 &&
1403             isprint(key_value)) {
1404                 key_char[1] = (char) key_value;
1405                 seq = key_char;
1406         }
1408         return seq ? seq : "(no key)";
1411 static bool
1412 append_key(char *buf, size_t *pos, const struct keybinding *keybinding)
1414         const char *sep = *pos > 0 ? ", " : "";
1415         const char *keyname = get_key_name(keybinding->alias);
1417         return string_nformat(buf, BUFSIZ, pos, "%s%s", sep, keyname);
1420 static bool
1421 append_keymap_request_keys(char *buf, size_t *pos, enum request request,
1422                            enum keymap keymap, bool all)
1424         int i;
1426         for (i = 0; i < keybindings[keymap].size; i++) {
1427                 if (keybindings[keymap].data[i].request == request) {
1428                         if (!append_key(buf, pos, &keybindings[keymap].data[i]))
1429                                 return FALSE;
1430                         if (!all)
1431                                 break;
1432                 }
1433         }
1435         return TRUE;
1438 #define get_key(keymap, request) get_keys(keymap, request, FALSE)
1440 static const char *
1441 get_keys(enum keymap keymap, enum request request, bool all)
1443         static char buf[BUFSIZ];
1444         size_t pos = 0;
1445         int i;
1447         buf[pos] = 0;
1449         if (!append_keymap_request_keys(buf, &pos, request, keymap, all))
1450                 return "Too many keybindings!";
1451         if (pos > 0 && !all)
1452                 return buf;
1454         if (keymap != KEYMAP_GENERIC) {
1455                 /* Only the generic keymap includes the default keybindings when
1456                  * listing all keys. */
1457                 if (all)
1458                         return buf;
1460                 if (!append_keymap_request_keys(buf, &pos, request, KEYMAP_GENERIC, all))
1461                         return "Too many keybindings!";
1462                 if (pos)
1463                         return buf;
1464         }
1466         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1467                 if (default_keybindings[i].request == request) {
1468                         if (!append_key(buf, &pos, &default_keybindings[i]))
1469                                 return "Too many keybindings!";
1470                         if (!all)
1471                                 return buf;
1472                 }
1473         }
1475         return buf;
1478 struct run_request {
1479         enum keymap keymap;
1480         int key;
1481         const char *argv[SIZEOF_ARG];
1482 };
1484 static struct run_request *run_request;
1485 static size_t run_requests;
1487 DEFINE_ALLOCATOR(realloc_run_requests, struct run_request, 8)
1489 static enum request
1490 add_run_request(enum keymap keymap, int key, int argc, const char **argv)
1492         struct run_request *req;
1494         if (argc >= ARRAY_SIZE(req->argv) - 1)
1495                 return REQ_NONE;
1497         if (!realloc_run_requests(&run_request, run_requests, 1))
1498                 return REQ_NONE;
1500         req = &run_request[run_requests];
1501         req->keymap = keymap;
1502         req->key = key;
1503         req->argv[0] = NULL;
1505         if (!format_argv(req->argv, argv, FORMAT_NONE))
1506                 return REQ_NONE;
1508         return REQ_NONE + ++run_requests;
1511 static struct run_request *
1512 get_run_request(enum request request)
1514         if (request <= REQ_NONE)
1515                 return NULL;
1516         return &run_request[request - REQ_NONE - 1];
1519 static void
1520 add_builtin_run_requests(void)
1522         const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1523         const char *commit[] = { "git", "commit", NULL };
1524         const char *gc[] = { "git", "gc", NULL };
1525         struct {
1526                 enum keymap keymap;
1527                 int key;
1528                 int argc;
1529                 const char **argv;
1530         } reqs[] = {
1531                 { KEYMAP_MAIN,    'C', ARRAY_SIZE(cherry_pick) - 1, cherry_pick },
1532                 { KEYMAP_STATUS,  'C', ARRAY_SIZE(commit) - 1, commit },
1533                 { KEYMAP_GENERIC, 'G', ARRAY_SIZE(gc) - 1, gc },
1534         };
1535         int i;
1537         for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1538                 enum request req;
1540                 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argc, reqs[i].argv);
1541                 if (req != REQ_NONE)
1542                         add_keybinding(reqs[i].keymap, req, reqs[i].key);
1543         }
1546 /*
1547  * User config file handling.
1548  */
1550 static int   config_lineno;
1551 static bool  config_errors;
1552 static const char *config_msg;
1554 static const struct enum_map color_map[] = {
1555 #define COLOR_MAP(name) ENUM_MAP(#name, COLOR_##name)
1556         COLOR_MAP(DEFAULT),
1557         COLOR_MAP(BLACK),
1558         COLOR_MAP(BLUE),
1559         COLOR_MAP(CYAN),
1560         COLOR_MAP(GREEN),
1561         COLOR_MAP(MAGENTA),
1562         COLOR_MAP(RED),
1563         COLOR_MAP(WHITE),
1564         COLOR_MAP(YELLOW),
1565 };
1567 static const struct enum_map attr_map[] = {
1568 #define ATTR_MAP(name) ENUM_MAP(#name, A_##name)
1569         ATTR_MAP(NORMAL),
1570         ATTR_MAP(BLINK),
1571         ATTR_MAP(BOLD),
1572         ATTR_MAP(DIM),
1573         ATTR_MAP(REVERSE),
1574         ATTR_MAP(STANDOUT),
1575         ATTR_MAP(UNDERLINE),
1576 };
1578 #define set_attribute(attr, name)       map_enum(attr, attr_map, name)
1580 static int parse_step(double *opt, const char *arg)
1582         *opt = atoi(arg);
1583         if (!strchr(arg, '%'))
1584                 return OK;
1586         /* "Shift down" so 100% and 1 does not conflict. */
1587         *opt = (*opt - 1) / 100;
1588         if (*opt >= 1.0) {
1589                 *opt = 0.99;
1590                 config_msg = "Step value larger than 100%";
1591                 return ERR;
1592         }
1593         if (*opt < 0.0) {
1594                 *opt = 1;
1595                 config_msg = "Invalid step value";
1596                 return ERR;
1597         }
1598         return OK;
1601 static int
1602 parse_int(int *opt, const char *arg, int min, int max)
1604         int value = atoi(arg);
1606         if (min <= value && value <= max) {
1607                 *opt = value;
1608                 return OK;
1609         }
1611         config_msg = "Integer value out of bound";
1612         return ERR;
1615 static bool
1616 set_color(int *color, const char *name)
1618         if (map_enum(color, color_map, name))
1619                 return TRUE;
1620         if (!prefixcmp(name, "color"))
1621                 return parse_int(color, name + 5, 0, 255) == OK;
1622         return FALSE;
1625 /* Wants: object fgcolor bgcolor [attribute] */
1626 static int
1627 option_color_command(int argc, const char *argv[])
1629         struct line_info *info;
1631         if (argc < 3) {
1632                 config_msg = "Wrong number of arguments given to color command";
1633                 return ERR;
1634         }
1636         info = get_line_info(argv[0]);
1637         if (!info) {
1638                 static const struct enum_map obsolete[] = {
1639                         ENUM_MAP("main-delim",  LINE_DELIMITER),
1640                         ENUM_MAP("main-date",   LINE_DATE),
1641                         ENUM_MAP("main-author", LINE_AUTHOR),
1642                 };
1643                 int index;
1645                 if (!map_enum(&index, obsolete, argv[0])) {
1646                         config_msg = "Unknown color name";
1647                         return ERR;
1648                 }
1649                 info = &line_info[index];
1650         }
1652         if (!set_color(&info->fg, argv[1]) ||
1653             !set_color(&info->bg, argv[2])) {
1654                 config_msg = "Unknown color";
1655                 return ERR;
1656         }
1658         info->attr = 0;
1659         while (argc-- > 3) {
1660                 int attr;
1662                 if (!set_attribute(&attr, argv[argc])) {
1663                         config_msg = "Unknown attribute";
1664                         return ERR;
1665                 }
1666                 info->attr |= attr;
1667         }
1669         return OK;
1672 static int parse_bool(bool *opt, const char *arg)
1674         *opt = (!strcmp(arg, "1") || !strcmp(arg, "true") || !strcmp(arg, "yes"))
1675                 ? TRUE : FALSE;
1676         return OK;
1679 static int
1680 parse_string(char *opt, const char *arg, size_t optsize)
1682         int arglen = strlen(arg);
1684         switch (arg[0]) {
1685         case '\"':
1686         case '\'':
1687                 if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1688                         config_msg = "Unmatched quotation";
1689                         return ERR;
1690                 }
1691                 arg += 1; arglen -= 2;
1692         default:
1693                 string_ncopy_do(opt, optsize, arg, arglen);
1694                 return OK;
1695         }
1698 /* Wants: name = value */
1699 static int
1700 option_set_command(int argc, const char *argv[])
1702         if (argc != 3) {
1703                 config_msg = "Wrong number of arguments given to set command";
1704                 return ERR;
1705         }
1707         if (strcmp(argv[1], "=")) {
1708                 config_msg = "No value assigned";
1709                 return ERR;
1710         }
1712         if (!strcmp(argv[0], "show-author"))
1713                 return parse_bool(&opt_author, argv[2]);
1715         if (!strcmp(argv[0], "show-date")) {
1716                 bool show_date;
1718                 if (!strcmp(argv[2], "relative")) {
1719                         opt_date = DATE_RELATIVE;
1720                         return OK;
1721                 } else if (!strcmp(argv[2], "short")) {
1722                         opt_date = DATE_SHORT;
1723                         return OK;
1724                 } else if (parse_bool(&show_date, argv[2]) == OK) {
1725                         opt_date = show_date ? DATE_DEFAULT : DATE_NONE;
1726                         return OK;
1727                 }
1728                 return ERR;
1729         }
1731         if (!strcmp(argv[0], "show-rev-graph"))
1732                 return parse_bool(&opt_rev_graph, argv[2]);
1734         if (!strcmp(argv[0], "show-refs"))
1735                 return parse_bool(&opt_show_refs, argv[2]);
1737         if (!strcmp(argv[0], "show-line-numbers"))
1738                 return parse_bool(&opt_line_number, argv[2]);
1740         if (!strcmp(argv[0], "line-graphics"))
1741                 return parse_bool(&opt_line_graphics, argv[2]);
1743         if (!strcmp(argv[0], "line-number-interval"))
1744                 return parse_int(&opt_num_interval, argv[2], 1, 1024);
1746         if (!strcmp(argv[0], "author-width"))
1747                 return parse_int(&opt_author_cols, argv[2], 0, 1024);
1749         if (!strcmp(argv[0], "horizontal-scroll"))
1750                 return parse_step(&opt_hscroll, argv[2]);
1752         if (!strcmp(argv[0], "split-view-height"))
1753                 return parse_step(&opt_scale_split_view, argv[2]);
1755         if (!strcmp(argv[0], "tab-size"))
1756                 return parse_int(&opt_tab_size, argv[2], 1, 1024);
1758         if (!strcmp(argv[0], "commit-encoding"))
1759                 return parse_string(opt_encoding, argv[2], sizeof(opt_encoding));
1761         config_msg = "Unknown variable name";
1762         return ERR;
1765 /* Wants: mode request key */
1766 static int
1767 option_bind_command(int argc, const char *argv[])
1769         enum request request;
1770         int keymap = -1;
1771         int key;
1773         if (argc < 3) {
1774                 config_msg = "Wrong number of arguments given to bind command";
1775                 return ERR;
1776         }
1778         if (set_keymap(&keymap, argv[0]) == ERR) {
1779                 config_msg = "Unknown key map";
1780                 return ERR;
1781         }
1783         key = get_key_value(argv[1]);
1784         if (key == ERR) {
1785                 config_msg = "Unknown key";
1786                 return ERR;
1787         }
1789         request = get_request(argv[2]);
1790         if (request == REQ_NONE) {
1791                 static const struct enum_map obsolete[] = {
1792                         ENUM_MAP("cherry-pick",         REQ_NONE),
1793                         ENUM_MAP("screen-resize",       REQ_NONE),
1794                         ENUM_MAP("tree-parent",         REQ_PARENT),
1795                 };
1796                 int alias;
1798                 if (map_enum(&alias, obsolete, argv[2])) {
1799                         if (alias != REQ_NONE)
1800                                 add_keybinding(keymap, alias, key);
1801                         config_msg = "Obsolete request name";
1802                         return ERR;
1803                 }
1804         }
1805         if (request == REQ_NONE && *argv[2]++ == '!')
1806                 request = add_run_request(keymap, key, argc - 2, argv + 2);
1807         if (request == REQ_NONE) {
1808                 config_msg = "Unknown request name";
1809                 return ERR;
1810         }
1812         add_keybinding(keymap, request, key);
1814         return OK;
1817 static int
1818 set_option(const char *opt, char *value)
1820         const char *argv[SIZEOF_ARG];
1821         int argc = 0;
1823         if (!argv_from_string(argv, &argc, value)) {
1824                 config_msg = "Too many option arguments";
1825                 return ERR;
1826         }
1828         if (!strcmp(opt, "color"))
1829                 return option_color_command(argc, argv);
1831         if (!strcmp(opt, "set"))
1832                 return option_set_command(argc, argv);
1834         if (!strcmp(opt, "bind"))
1835                 return option_bind_command(argc, argv);
1837         config_msg = "Unknown option command";
1838         return ERR;
1841 static int
1842 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
1844         int status = OK;
1846         config_lineno++;
1847         config_msg = "Internal error";
1849         /* Check for comment markers, since read_properties() will
1850          * only ensure opt and value are split at first " \t". */
1851         optlen = strcspn(opt, "#");
1852         if (optlen == 0)
1853                 return OK;
1855         if (opt[optlen] != 0) {
1856                 config_msg = "No option value";
1857                 status = ERR;
1859         }  else {
1860                 /* Look for comment endings in the value. */
1861                 size_t len = strcspn(value, "#");
1863                 if (len < valuelen) {
1864                         valuelen = len;
1865                         value[valuelen] = 0;
1866                 }
1868                 status = set_option(opt, value);
1869         }
1871         if (status == ERR) {
1872                 warn("Error on line %d, near '%.*s': %s",
1873                      config_lineno, (int) optlen, opt, config_msg);
1874                 config_errors = TRUE;
1875         }
1877         /* Always keep going if errors are encountered. */
1878         return OK;
1881 static void
1882 load_option_file(const char *path)
1884         struct io io = {};
1886         /* It's OK that the file doesn't exist. */
1887         if (!io_open(&io, "%s", path))
1888                 return;
1890         config_lineno = 0;
1891         config_errors = FALSE;
1893         if (io_load(&io, " \t", read_option) == ERR ||
1894             config_errors == TRUE)
1895                 warn("Errors while loading %s.", path);
1898 static int
1899 load_options(void)
1901         const char *home = getenv("HOME");
1902         const char *tigrc_user = getenv("TIGRC_USER");
1903         const char *tigrc_system = getenv("TIGRC_SYSTEM");
1904         char buf[SIZEOF_STR];
1906         add_builtin_run_requests();
1908         if (!tigrc_system)
1909                 tigrc_system = SYSCONFDIR "/tigrc";
1910         load_option_file(tigrc_system);
1912         if (!tigrc_user) {
1913                 if (!home || !string_format(buf, "%s/.tigrc", home))
1914                         return ERR;
1915                 tigrc_user = buf;
1916         }
1917         load_option_file(tigrc_user);
1919         return OK;
1923 /*
1924  * The viewer
1925  */
1927 struct view;
1928 struct view_ops;
1930 /* The display array of active views and the index of the current view. */
1931 static struct view *display[2];
1932 static unsigned int current_view;
1934 #define foreach_displayed_view(view, i) \
1935         for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1937 #define displayed_views()       (display[1] != NULL ? 2 : 1)
1939 /* Current head and commit ID */
1940 static char ref_blob[SIZEOF_REF]        = "";
1941 static char ref_commit[SIZEOF_REF]      = "HEAD";
1942 static char ref_head[SIZEOF_REF]        = "HEAD";
1944 struct view {
1945         const char *name;       /* View name */
1946         const char *cmd_env;    /* Command line set via environment */
1947         const char *id;         /* Points to either of ref_{head,commit,blob} */
1949         struct view_ops *ops;   /* View operations */
1951         enum keymap keymap;     /* What keymap does this view have */
1952         bool git_dir;           /* Whether the view requires a git directory. */
1954         char ref[SIZEOF_REF];   /* Hovered commit reference */
1955         char vid[SIZEOF_REF];   /* View ID. Set to id member when updating. */
1957         int height, width;      /* The width and height of the main window */
1958         WINDOW *win;            /* The main window */
1959         WINDOW *title;          /* The title window living below the main window */
1961         /* Navigation */
1962         unsigned long offset;   /* Offset of the window top */
1963         unsigned long yoffset;  /* Offset from the window side. */
1964         unsigned long lineno;   /* Current line number */
1965         unsigned long p_offset; /* Previous offset of the window top */
1966         unsigned long p_yoffset;/* Previous offset from the window side */
1967         unsigned long p_lineno; /* Previous current line number */
1968         bool p_restore;         /* Should the previous position be restored. */
1970         /* Searching */
1971         char grep[SIZEOF_STR];  /* Search string */
1972         regex_t *regex;         /* Pre-compiled regexp */
1974         /* If non-NULL, points to the view that opened this view. If this view
1975          * is closed tig will switch back to the parent view. */
1976         struct view *parent;
1978         /* Buffering */
1979         size_t lines;           /* Total number of lines */
1980         struct line *line;      /* Line index */
1981         unsigned int digits;    /* Number of digits in the lines member. */
1983         /* Drawing */
1984         struct line *curline;   /* Line currently being drawn. */
1985         enum line_type curtype; /* Attribute currently used for drawing. */
1986         unsigned long col;      /* Column when drawing. */
1987         bool has_scrolled;      /* View was scrolled. */
1989         /* Loading */
1990         struct io io;
1991         struct io *pipe;
1992         time_t start_time;
1993         time_t update_secs;
1994 };
1996 struct view_ops {
1997         /* What type of content being displayed. Used in the title bar. */
1998         const char *type;
1999         /* Default command arguments. */
2000         const char **argv;
2001         /* Open and reads in all view content. */
2002         bool (*open)(struct view *view);
2003         /* Read one line; updates view->line. */
2004         bool (*read)(struct view *view, char *data);
2005         /* Draw one line; @lineno must be < view->height. */
2006         bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
2007         /* Depending on view handle a special requests. */
2008         enum request (*request)(struct view *view, enum request request, struct line *line);
2009         /* Search for regexp in a line. */
2010         bool (*grep)(struct view *view, struct line *line);
2011         /* Select line */
2012         void (*select)(struct view *view, struct line *line);
2013         /* Prepare view for loading */
2014         bool (*prepare)(struct view *view);
2015 };
2017 static struct view_ops blame_ops;
2018 static struct view_ops blob_ops;
2019 static struct view_ops diff_ops;
2020 static struct view_ops help_ops;
2021 static struct view_ops log_ops;
2022 static struct view_ops main_ops;
2023 static struct view_ops pager_ops;
2024 static struct view_ops stage_ops;
2025 static struct view_ops status_ops;
2026 static struct view_ops tree_ops;
2027 static struct view_ops branch_ops;
2029 #define VIEW_STR(name, env, ref, ops, map, git) \
2030         { name, #env, ref, ops, map, git }
2032 #define VIEW_(id, name, ops, git, ref) \
2033         VIEW_STR(name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
2036 static struct view views[] = {
2037         VIEW_(MAIN,   "main",   &main_ops,   TRUE,  ref_head),
2038         VIEW_(DIFF,   "diff",   &diff_ops,   TRUE,  ref_commit),
2039         VIEW_(LOG,    "log",    &log_ops,    TRUE,  ref_head),
2040         VIEW_(TREE,   "tree",   &tree_ops,   TRUE,  ref_commit),
2041         VIEW_(BLOB,   "blob",   &blob_ops,   TRUE,  ref_blob),
2042         VIEW_(BLAME,  "blame",  &blame_ops,  TRUE,  ref_commit),
2043         VIEW_(BRANCH, "branch", &branch_ops, TRUE,  ref_head),
2044         VIEW_(HELP,   "help",   &help_ops,   FALSE, ""),
2045         VIEW_(PAGER,  "pager",  &pager_ops,  FALSE, "stdin"),
2046         VIEW_(STATUS, "status", &status_ops, TRUE,  ""),
2047         VIEW_(STAGE,  "stage",  &stage_ops,  TRUE,  ""),
2048 };
2050 #define VIEW(req)       (&views[(req) - REQ_OFFSET - 1])
2051 #define VIEW_REQ(view)  ((view) - views + REQ_OFFSET + 1)
2053 #define foreach_view(view, i) \
2054         for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
2056 #define view_is_displayed(view) \
2057         (view == display[0] || view == display[1])
2060 enum line_graphic {
2061         LINE_GRAPHIC_VLINE
2062 };
2064 static chtype line_graphics[] = {
2065         /* LINE_GRAPHIC_VLINE: */ '|'
2066 };
2068 static inline void
2069 set_view_attr(struct view *view, enum line_type type)
2071         if (!view->curline->selected && view->curtype != type) {
2072                 wattrset(view->win, get_line_attr(type));
2073                 wchgat(view->win, -1, 0, type, NULL);
2074                 view->curtype = type;
2075         }
2078 static int
2079 draw_chars(struct view *view, enum line_type type, const char *string,
2080            int max_len, bool use_tilde)
2082         static char out_buffer[BUFSIZ * 2];
2083         int len = 0;
2084         int col = 0;
2085         int trimmed = FALSE;
2086         size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2088         if (max_len <= 0)
2089                 return 0;
2091         len = utf8_length(&string, skip, &col, max_len, &trimmed, use_tilde);
2093         set_view_attr(view, type);
2094         if (len > 0) {
2095                 if (opt_iconv_out != ICONV_NONE) {
2096                         ICONV_CONST char *inbuf = (ICONV_CONST char *) string;
2097                         size_t inlen = len + 1;
2099                         char *outbuf = out_buffer;
2100                         size_t outlen = sizeof(out_buffer);
2102                         size_t ret;
2104                         ret = iconv(opt_iconv_out, &inbuf, &inlen, &outbuf, &outlen);
2105                         if (ret != (size_t) -1) {
2106                                 string = out_buffer;
2107                                 len = sizeof(out_buffer) - outlen;
2108                         }
2109                 }
2111                 waddnstr(view->win, string, len);
2112         }
2113         if (trimmed && use_tilde) {
2114                 set_view_attr(view, LINE_DELIMITER);
2115                 waddch(view->win, '~');
2116                 col++;
2117         }
2119         return col;
2122 static int
2123 draw_space(struct view *view, enum line_type type, int max, int spaces)
2125         static char space[] = "                    ";
2126         int col = 0;
2128         spaces = MIN(max, spaces);
2130         while (spaces > 0) {
2131                 int len = MIN(spaces, sizeof(space) - 1);
2133                 col += draw_chars(view, type, space, len, FALSE);
2134                 spaces -= len;
2135         }
2137         return col;
2140 static bool
2141 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
2143         view->col += draw_chars(view, type, string, view->width + view->yoffset - view->col, trim);
2144         return view->width + view->yoffset <= view->col;
2147 static bool
2148 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
2150         size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2151         int max = view->width + view->yoffset - view->col;
2152         int i;
2154         if (max < size)
2155                 size = max;
2157         set_view_attr(view, type);
2158         /* Using waddch() instead of waddnstr() ensures that
2159          * they'll be rendered correctly for the cursor line. */
2160         for (i = skip; i < size; i++)
2161                 waddch(view->win, graphic[i]);
2163         view->col += size;
2164         if (size < max && skip <= size)
2165                 waddch(view->win, ' ');
2166         view->col++;
2168         return view->width + view->yoffset <= view->col;
2171 static bool
2172 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
2174         int max = MIN(view->width + view->yoffset - view->col, len);
2175         int col;
2177         if (text)
2178                 col = draw_chars(view, type, text, max - 1, trim);
2179         else
2180                 col = draw_space(view, type, max - 1, max - 1);
2182         view->col += col;
2183         view->col += draw_space(view, LINE_DEFAULT, max - col, max - col);
2184         return view->width + view->yoffset <= view->col;
2187 static bool
2188 draw_date(struct view *view, time_t *time)
2190         const char *date = time ? mkdate(time) : "";
2191         int cols = opt_date == DATE_SHORT ? DATE_SHORT_COLS : DATE_COLS;
2193         return draw_field(view, LINE_DATE, date, cols, FALSE);
2196 static bool
2197 draw_author(struct view *view, const char *author)
2199         bool trim = opt_author_cols == 0 || opt_author_cols > 5 || !author;
2201         if (!trim) {
2202                 static char initials[10];
2203                 size_t pos;
2205 #define is_initial_sep(c) (isspace(c) || ispunct(c) || (c) == '@')
2207                 memset(initials, 0, sizeof(initials));
2208                 for (pos = 0; *author && pos < opt_author_cols - 1; author++, pos++) {
2209                         while (is_initial_sep(*author))
2210                                 author++;
2211                         strncpy(&initials[pos], author, sizeof(initials) - 1 - pos);
2212                         while (*author && !is_initial_sep(author[1]))
2213                                 author++;
2214                 }
2216                 author = initials;
2217         }
2219         return draw_field(view, LINE_AUTHOR, author, opt_author_cols, trim);
2222 static bool
2223 draw_mode(struct view *view, mode_t mode)
2225         const char *str;
2227         if (S_ISDIR(mode))
2228                 str = "drwxr-xr-x";
2229         else if (S_ISLNK(mode))
2230                 str = "lrwxrwxrwx";
2231         else if (S_ISGITLINK(mode))
2232                 str = "m---------";
2233         else if (S_ISREG(mode) && mode & S_IXUSR)
2234                 str = "-rwxr-xr-x";
2235         else if (S_ISREG(mode))
2236                 str = "-rw-r--r--";
2237         else
2238                 str = "----------";
2240         return draw_field(view, LINE_MODE, str, STRING_SIZE("-rw-r--r-- "), FALSE);
2243 static bool
2244 draw_lineno(struct view *view, unsigned int lineno)
2246         char number[10];
2247         int digits3 = view->digits < 3 ? 3 : view->digits;
2248         int max = MIN(view->width + view->yoffset - view->col, digits3);
2249         char *text = NULL;
2251         lineno += view->offset + 1;
2252         if (lineno == 1 || (lineno % opt_num_interval) == 0) {
2253                 static char fmt[] = "%1ld";
2255                 fmt[1] = '0' + (view->digits <= 9 ? digits3 : 1);
2256                 if (string_format(number, fmt, lineno))
2257                         text = number;
2258         }
2259         if (text)
2260                 view->col += draw_chars(view, LINE_LINE_NUMBER, text, max, TRUE);
2261         else
2262                 view->col += draw_space(view, LINE_LINE_NUMBER, max, digits3);
2263         return draw_graphic(view, LINE_DEFAULT, &line_graphics[LINE_GRAPHIC_VLINE], 1);
2266 static bool
2267 draw_view_line(struct view *view, unsigned int lineno)
2269         struct line *line;
2270         bool selected = (view->offset + lineno == view->lineno);
2272         assert(view_is_displayed(view));
2274         if (view->offset + lineno >= view->lines)
2275                 return FALSE;
2277         line = &view->line[view->offset + lineno];
2279         wmove(view->win, lineno, 0);
2280         if (line->cleareol)
2281                 wclrtoeol(view->win);
2282         view->col = 0;
2283         view->curline = line;
2284         view->curtype = LINE_NONE;
2285         line->selected = FALSE;
2286         line->dirty = line->cleareol = 0;
2288         if (selected) {
2289                 set_view_attr(view, LINE_CURSOR);
2290                 line->selected = TRUE;
2291                 view->ops->select(view, line);
2292         }
2294         return view->ops->draw(view, line, lineno);
2297 static void
2298 redraw_view_dirty(struct view *view)
2300         bool dirty = FALSE;
2301         int lineno;
2303         for (lineno = 0; lineno < view->height; lineno++) {
2304                 if (view->offset + lineno >= view->lines)
2305                         break;
2306                 if (!view->line[view->offset + lineno].dirty)
2307                         continue;
2308                 dirty = TRUE;
2309                 if (!draw_view_line(view, lineno))
2310                         break;
2311         }
2313         if (!dirty)
2314                 return;
2315         wnoutrefresh(view->win);
2318 static void
2319 redraw_view_from(struct view *view, int lineno)
2321         assert(0 <= lineno && lineno < view->height);
2323         for (; lineno < view->height; lineno++) {
2324                 if (!draw_view_line(view, lineno))
2325                         break;
2326         }
2328         wnoutrefresh(view->win);
2331 static void
2332 redraw_view(struct view *view)
2334         werase(view->win);
2335         redraw_view_from(view, 0);
2339 static void
2340 update_view_title(struct view *view)
2342         char buf[SIZEOF_STR];
2343         char state[SIZEOF_STR];
2344         size_t bufpos = 0, statelen = 0;
2346         assert(view_is_displayed(view));
2348         if (view != VIEW(REQ_VIEW_STATUS) && view->lines) {
2349                 unsigned int view_lines = view->offset + view->height;
2350                 unsigned int lines = view->lines
2351                                    ? MIN(view_lines, view->lines) * 100 / view->lines
2352                                    : 0;
2354                 string_format_from(state, &statelen, " - %s %d of %d (%d%%)",
2355                                    view->ops->type,
2356                                    view->lineno + 1,
2357                                    view->lines,
2358                                    lines);
2360         }
2362         if (view->pipe) {
2363                 time_t secs = time(NULL) - view->start_time;
2365                 /* Three git seconds are a long time ... */
2366                 if (secs > 2)
2367                         string_format_from(state, &statelen, " loading %lds", secs);
2368         }
2370         string_format_from(buf, &bufpos, "[%s]", view->name);
2371         if (*view->ref && bufpos < view->width) {
2372                 size_t refsize = strlen(view->ref);
2373                 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2375                 if (minsize < view->width)
2376                         refsize = view->width - minsize + 7;
2377                 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2378         }
2380         if (statelen && bufpos < view->width) {
2381                 string_format_from(buf, &bufpos, "%s", state);
2382         }
2384         if (view == display[current_view])
2385                 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2386         else
2387                 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2389         mvwaddnstr(view->title, 0, 0, buf, bufpos);
2390         wclrtoeol(view->title);
2391         wnoutrefresh(view->title);
2394 static int
2395 apply_step(double step, int value)
2397         if (step >= 1)
2398                 return (int) step;
2399         value *= step + 0.01;
2400         return value ? value : 1;
2403 static void
2404 resize_display(void)
2406         int offset, i;
2407         struct view *base = display[0];
2408         struct view *view = display[1] ? display[1] : display[0];
2410         /* Setup window dimensions */
2412         getmaxyx(stdscr, base->height, base->width);
2414         /* Make room for the status window. */
2415         base->height -= 1;
2417         if (view != base) {
2418                 /* Horizontal split. */
2419                 view->width   = base->width;
2420                 view->height  = apply_step(opt_scale_split_view, base->height);
2421                 view->height  = MAX(view->height, MIN_VIEW_HEIGHT);
2422                 view->height  = MIN(view->height, base->height - MIN_VIEW_HEIGHT);
2423                 base->height -= view->height;
2425                 /* Make room for the title bar. */
2426                 view->height -= 1;
2427         }
2429         /* Make room for the title bar. */
2430         base->height -= 1;
2432         offset = 0;
2434         foreach_displayed_view (view, i) {
2435                 if (!view->win) {
2436                         view->win = newwin(view->height, 0, offset, 0);
2437                         if (!view->win)
2438                                 die("Failed to create %s view", view->name);
2440                         scrollok(view->win, FALSE);
2442                         view->title = newwin(1, 0, offset + view->height, 0);
2443                         if (!view->title)
2444                                 die("Failed to create title window");
2446                 } else {
2447                         wresize(view->win, view->height, view->width);
2448                         mvwin(view->win,   offset, 0);
2449                         mvwin(view->title, offset + view->height, 0);
2450                 }
2452                 offset += view->height + 1;
2453         }
2456 static void
2457 redraw_display(bool clear)
2459         struct view *view;
2460         int i;
2462         foreach_displayed_view (view, i) {
2463                 if (clear)
2464                         wclear(view->win);
2465                 redraw_view(view);
2466                 update_view_title(view);
2467         }
2470 static void
2471 toggle_date_option(enum date *date)
2473         static const char *help[] = {
2474                 "no",
2475                 "default",
2476                 "relative",
2477                 "short"
2478         };
2480         *date = (*date + 1) % ARRAY_SIZE(help);
2481         redraw_display(FALSE);
2482         report("Displaying %s dates", help[*date]);
2485 static void
2486 toggle_view_option(bool *option, const char *help)
2488         *option = !*option;
2489         redraw_display(FALSE);
2490         report("%sabling %s", *option ? "En" : "Dis", help);
2493 static void
2494 open_option_menu(void)
2496         const struct menu_item menu[] = {
2497                 { '.', "line numbers", &opt_line_number },
2498                 { 'D', "date display", &opt_date },
2499                 { 'A', "author display", &opt_author },
2500                 { 'g', "revision graph display", &opt_rev_graph },
2501                 { 'F', "reference display", &opt_show_refs },
2502                 { 0 }
2503         };
2504         int selected = 0;
2506         if (prompt_menu("Toggle option", menu, &selected)) {
2507                 if (menu[selected].data == &opt_date)
2508                         toggle_date_option(menu[selected].data);
2509                 else
2510                         toggle_view_option(menu[selected].data, menu[selected].text);
2511         }
2514 static void
2515 maximize_view(struct view *view)
2517         memset(display, 0, sizeof(display));
2518         current_view = 0;
2519         display[current_view] = view;
2520         resize_display();
2521         redraw_display(FALSE);
2522         report("");
2526 /*
2527  * Navigation
2528  */
2530 static bool
2531 goto_view_line(struct view *view, unsigned long offset, unsigned long lineno)
2533         if (lineno >= view->lines)
2534                 lineno = view->lines > 0 ? view->lines - 1 : 0;
2536         if (offset > lineno || offset + view->height <= lineno) {
2537                 unsigned long half = view->height / 2;
2539                 if (lineno > half)
2540                         offset = lineno - half;
2541                 else
2542                         offset = 0;
2543         }
2545         if (offset != view->offset || lineno != view->lineno) {
2546                 view->offset = offset;
2547                 view->lineno = lineno;
2548                 return TRUE;
2549         }
2551         return FALSE;
2554 /* Scrolling backend */
2555 static void
2556 do_scroll_view(struct view *view, int lines)
2558         bool redraw_current_line = FALSE;
2560         /* The rendering expects the new offset. */
2561         view->offset += lines;
2563         assert(0 <= view->offset && view->offset < view->lines);
2564         assert(lines);
2566         /* Move current line into the view. */
2567         if (view->lineno < view->offset) {
2568                 view->lineno = view->offset;
2569                 redraw_current_line = TRUE;
2570         } else if (view->lineno >= view->offset + view->height) {
2571                 view->lineno = view->offset + view->height - 1;
2572                 redraw_current_line = TRUE;
2573         }
2575         assert(view->offset <= view->lineno && view->lineno < view->lines);
2577         /* Redraw the whole screen if scrolling is pointless. */
2578         if (view->height < ABS(lines)) {
2579                 redraw_view(view);
2581         } else {
2582                 int line = lines > 0 ? view->height - lines : 0;
2583                 int end = line + ABS(lines);
2585                 scrollok(view->win, TRUE);
2586                 wscrl(view->win, lines);
2587                 scrollok(view->win, FALSE);
2589                 while (line < end && draw_view_line(view, line))
2590                         line++;
2592                 if (redraw_current_line)
2593                         draw_view_line(view, view->lineno - view->offset);
2594                 wnoutrefresh(view->win);
2595         }
2597         view->has_scrolled = TRUE;
2598         report("");
2601 /* Scroll frontend */
2602 static void
2603 scroll_view(struct view *view, enum request request)
2605         int lines = 1;
2607         assert(view_is_displayed(view));
2609         switch (request) {
2610         case REQ_SCROLL_LEFT:
2611                 if (view->yoffset == 0) {
2612                         report("Cannot scroll beyond the first column");
2613                         return;
2614                 }
2615                 if (view->yoffset <= apply_step(opt_hscroll, view->width))
2616                         view->yoffset = 0;
2617                 else
2618                         view->yoffset -= apply_step(opt_hscroll, view->width);
2619                 redraw_view_from(view, 0);
2620                 report("");
2621                 return;
2622         case REQ_SCROLL_RIGHT:
2623                 view->yoffset += apply_step(opt_hscroll, view->width);
2624                 redraw_view(view);
2625                 report("");
2626                 return;
2627         case REQ_SCROLL_PAGE_DOWN:
2628                 lines = view->height;
2629         case REQ_SCROLL_LINE_DOWN:
2630                 if (view->offset + lines > view->lines)
2631                         lines = view->lines - view->offset;
2633                 if (lines == 0 || view->offset + view->height >= view->lines) {
2634                         report("Cannot scroll beyond the last line");
2635                         return;
2636                 }
2637                 break;
2639         case REQ_SCROLL_PAGE_UP:
2640                 lines = view->height;
2641         case REQ_SCROLL_LINE_UP:
2642                 if (lines > view->offset)
2643                         lines = view->offset;
2645                 if (lines == 0) {
2646                         report("Cannot scroll beyond the first line");
2647                         return;
2648                 }
2650                 lines = -lines;
2651                 break;
2653         default:
2654                 die("request %d not handled in switch", request);
2655         }
2657         do_scroll_view(view, lines);
2660 /* Cursor moving */
2661 static void
2662 move_view(struct view *view, enum request request)
2664         int scroll_steps = 0;
2665         int steps;
2667         switch (request) {
2668         case REQ_MOVE_FIRST_LINE:
2669                 steps = -view->lineno;
2670                 break;
2672         case REQ_MOVE_LAST_LINE:
2673                 steps = view->lines - view->lineno - 1;
2674                 break;
2676         case REQ_MOVE_PAGE_UP:
2677                 steps = view->height > view->lineno
2678                       ? -view->lineno : -view->height;
2679                 break;
2681         case REQ_MOVE_PAGE_DOWN:
2682                 steps = view->lineno + view->height >= view->lines
2683                       ? view->lines - view->lineno - 1 : view->height;
2684                 break;
2686         case REQ_MOVE_UP:
2687                 steps = -1;
2688                 break;
2690         case REQ_MOVE_DOWN:
2691                 steps = 1;
2692                 break;
2694         default:
2695                 die("request %d not handled in switch", request);
2696         }
2698         if (steps <= 0 && view->lineno == 0) {
2699                 report("Cannot move beyond the first line");
2700                 return;
2702         } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2703                 report("Cannot move beyond the last line");
2704                 return;
2705         }
2707         /* Move the current line */
2708         view->lineno += steps;
2709         assert(0 <= view->lineno && view->lineno < view->lines);
2711         /* Check whether the view needs to be scrolled */
2712         if (view->lineno < view->offset ||
2713             view->lineno >= view->offset + view->height) {
2714                 scroll_steps = steps;
2715                 if (steps < 0 && -steps > view->offset) {
2716                         scroll_steps = -view->offset;
2718                 } else if (steps > 0) {
2719                         if (view->lineno == view->lines - 1 &&
2720                             view->lines > view->height) {
2721                                 scroll_steps = view->lines - view->offset - 1;
2722                                 if (scroll_steps >= view->height)
2723                                         scroll_steps -= view->height - 1;
2724                         }
2725                 }
2726         }
2728         if (!view_is_displayed(view)) {
2729                 view->offset += scroll_steps;
2730                 assert(0 <= view->offset && view->offset < view->lines);
2731                 view->ops->select(view, &view->line[view->lineno]);
2732                 return;
2733         }
2735         /* Repaint the old "current" line if we be scrolling */
2736         if (ABS(steps) < view->height)
2737                 draw_view_line(view, view->lineno - steps - view->offset);
2739         if (scroll_steps) {
2740                 do_scroll_view(view, scroll_steps);
2741                 return;
2742         }
2744         /* Draw the current line */
2745         draw_view_line(view, view->lineno - view->offset);
2747         wnoutrefresh(view->win);
2748         report("");
2752 /*
2753  * Searching
2754  */
2756 static void search_view(struct view *view, enum request request);
2758 static bool
2759 grep_text(struct view *view, const char *text[])
2761         regmatch_t pmatch;
2762         size_t i;
2764         for (i = 0; text[i]; i++)
2765                 if (*text[i] &&
2766                     regexec(view->regex, text[i], 1, &pmatch, 0) != REG_NOMATCH)
2767                         return TRUE;
2768         return FALSE;
2771 static void
2772 select_view_line(struct view *view, unsigned long lineno)
2774         unsigned long old_lineno = view->lineno;
2775         unsigned long old_offset = view->offset;
2777         if (goto_view_line(view, view->offset, lineno)) {
2778                 if (view_is_displayed(view)) {
2779                         if (old_offset != view->offset) {
2780                                 redraw_view(view);
2781                         } else {
2782                                 draw_view_line(view, old_lineno - view->offset);
2783                                 draw_view_line(view, view->lineno - view->offset);
2784                                 wnoutrefresh(view->win);
2785                         }
2786                 } else {
2787                         view->ops->select(view, &view->line[view->lineno]);
2788                 }
2789         }
2792 static void
2793 find_next(struct view *view, enum request request)
2795         unsigned long lineno = view->lineno;
2796         int direction;
2798         if (!*view->grep) {
2799                 if (!*opt_search)
2800                         report("No previous search");
2801                 else
2802                         search_view(view, request);
2803                 return;
2804         }
2806         switch (request) {
2807         case REQ_SEARCH:
2808         case REQ_FIND_NEXT:
2809                 direction = 1;
2810                 break;
2812         case REQ_SEARCH_BACK:
2813         case REQ_FIND_PREV:
2814                 direction = -1;
2815                 break;
2817         default:
2818                 return;
2819         }
2821         if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
2822                 lineno += direction;
2824         /* Note, lineno is unsigned long so will wrap around in which case it
2825          * will become bigger than view->lines. */
2826         for (; lineno < view->lines; lineno += direction) {
2827                 if (view->ops->grep(view, &view->line[lineno])) {
2828                         select_view_line(view, lineno);
2829                         report("Line %ld matches '%s'", lineno + 1, view->grep);
2830                         return;
2831                 }
2832         }
2834         report("No match found for '%s'", view->grep);
2837 static void
2838 search_view(struct view *view, enum request request)
2840         int regex_err;
2842         if (view->regex) {
2843                 regfree(view->regex);
2844                 *view->grep = 0;
2845         } else {
2846                 view->regex = calloc(1, sizeof(*view->regex));
2847                 if (!view->regex)
2848                         return;
2849         }
2851         regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
2852         if (regex_err != 0) {
2853                 char buf[SIZEOF_STR] = "unknown error";
2855                 regerror(regex_err, view->regex, buf, sizeof(buf));
2856                 report("Search failed: %s", buf);
2857                 return;
2858         }
2860         string_copy(view->grep, opt_search);
2862         find_next(view, request);
2865 /*
2866  * Incremental updating
2867  */
2869 static void
2870 reset_view(struct view *view)
2872         int i;
2874         for (i = 0; i < view->lines; i++)
2875                 free(view->line[i].data);
2876         free(view->line);
2878         view->p_offset = view->offset;
2879         view->p_yoffset = view->yoffset;
2880         view->p_lineno = view->lineno;
2882         view->line = NULL;
2883         view->offset = 0;
2884         view->yoffset = 0;
2885         view->lines  = 0;
2886         view->lineno = 0;
2887         view->vid[0] = 0;
2888         view->update_secs = 0;
2891 static void
2892 free_argv(const char *argv[])
2894         int argc;
2896         for (argc = 0; argv[argc]; argc++)
2897                 free((void *) argv[argc]);
2900 static bool
2901 format_argv(const char *dst_argv[], const char *src_argv[], enum format_flags flags)
2903         char buf[SIZEOF_STR];
2904         int argc;
2905         bool noreplace = flags == FORMAT_NONE;
2907         free_argv(dst_argv);
2909         for (argc = 0; src_argv[argc]; argc++) {
2910                 const char *arg = src_argv[argc];
2911                 size_t bufpos = 0;
2913                 while (arg) {
2914                         char *next = strstr(arg, "%(");
2915                         int len = next - arg;
2916                         const char *value;
2918                         if (!next || noreplace) {
2919                                 if (flags == FORMAT_DASH && !strcmp(arg, "--"))
2920                                         noreplace = TRUE;
2921                                 len = strlen(arg);
2922                                 value = "";
2924                         } else if (!prefixcmp(next, "%(directory)")) {
2925                                 value = opt_path;
2927                         } else if (!prefixcmp(next, "%(file)")) {
2928                                 value = opt_file;
2930                         } else if (!prefixcmp(next, "%(ref)")) {
2931                                 value = *opt_ref ? opt_ref : "HEAD";
2933                         } else if (!prefixcmp(next, "%(head)")) {
2934                                 value = ref_head;
2936                         } else if (!prefixcmp(next, "%(commit)")) {
2937                                 value = ref_commit;
2939                         } else if (!prefixcmp(next, "%(blob)")) {
2940                                 value = ref_blob;
2942                         } else {
2943                                 report("Unknown replacement: `%s`", next);
2944                                 return FALSE;
2945                         }
2947                         if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
2948                                 return FALSE;
2950                         arg = next && !noreplace ? strchr(next, ')') + 1 : NULL;
2951                 }
2953                 dst_argv[argc] = strdup(buf);
2954                 if (!dst_argv[argc])
2955                         break;
2956         }
2958         dst_argv[argc] = NULL;
2960         return src_argv[argc] == NULL;
2963 static bool
2964 restore_view_position(struct view *view)
2966         if (!view->p_restore || (view->pipe && view->lines <= view->p_lineno))
2967                 return FALSE;
2969         /* Changing the view position cancels the restoring. */
2970         /* FIXME: Changing back to the first line is not detected. */
2971         if (view->offset != 0 || view->lineno != 0) {
2972                 view->p_restore = FALSE;
2973                 return FALSE;
2974         }
2976         if (goto_view_line(view, view->p_offset, view->p_lineno) &&
2977             view_is_displayed(view))
2978                 werase(view->win);
2980         view->yoffset = view->p_yoffset;
2981         view->p_restore = FALSE;
2983         return TRUE;
2986 static void
2987 end_update(struct view *view, bool force)
2989         if (!view->pipe)
2990                 return;
2991         while (!view->ops->read(view, NULL))
2992                 if (!force)
2993                         return;
2994         set_nonblocking_input(FALSE);
2995         if (force)
2996                 kill_io(view->pipe);
2997         done_io(view->pipe);
2998         view->pipe = NULL;
3001 static void
3002 setup_update(struct view *view, const char *vid)
3004         set_nonblocking_input(TRUE);
3005         reset_view(view);
3006         string_copy_rev(view->vid, vid);
3007         view->pipe = &view->io;
3008         view->start_time = time(NULL);
3011 static bool
3012 prepare_update(struct view *view, const char *argv[], const char *dir,
3013                enum format_flags flags)
3015         if (view->pipe)
3016                 end_update(view, TRUE);
3017         return init_io_rd(&view->io, argv, dir, flags);
3020 static bool
3021 prepare_update_file(struct view *view, const char *name)
3023         if (view->pipe)
3024                 end_update(view, TRUE);
3025         return io_open(&view->io, "%s", name);
3028 static bool
3029 begin_update(struct view *view, bool refresh)
3031         if (view->pipe)
3032                 end_update(view, TRUE);
3034         if (!refresh) {
3035                 if (view->ops->prepare) {
3036                         if (!view->ops->prepare(view))
3037                                 return FALSE;
3038                 } else if (!init_io_rd(&view->io, view->ops->argv, NULL, FORMAT_ALL)) {
3039                         return FALSE;
3040                 }
3042                 /* Put the current ref_* value to the view title ref
3043                  * member. This is needed by the blob view. Most other
3044                  * views sets it automatically after loading because the
3045                  * first line is a commit line. */
3046                 string_copy_rev(view->ref, view->id);
3047         }
3049         if (!start_io(&view->io))
3050                 return FALSE;
3052         setup_update(view, view->id);
3054         return TRUE;
3057 static bool
3058 update_view(struct view *view)
3060         char out_buffer[BUFSIZ * 2];
3061         char *line;
3062         /* Clear the view and redraw everything since the tree sorting
3063          * might have rearranged things. */
3064         bool redraw = view->lines == 0;
3065         bool can_read = TRUE;
3067         if (!view->pipe)
3068                 return TRUE;
3070         if (!io_can_read(view->pipe)) {
3071                 if (view->lines == 0 && view_is_displayed(view)) {
3072                         time_t secs = time(NULL) - view->start_time;
3074                         if (secs > 1 && secs > view->update_secs) {
3075                                 if (view->update_secs == 0)
3076                                         redraw_view(view);
3077                                 update_view_title(view);
3078                                 view->update_secs = secs;
3079                         }
3080                 }
3081                 return TRUE;
3082         }
3084         for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
3085                 if (opt_iconv_in != ICONV_NONE) {
3086                         ICONV_CONST char *inbuf = line;
3087                         size_t inlen = strlen(line) + 1;
3089                         char *outbuf = out_buffer;
3090                         size_t outlen = sizeof(out_buffer);
3092                         size_t ret;
3094                         ret = iconv(opt_iconv_in, &inbuf, &inlen, &outbuf, &outlen);
3095                         if (ret != (size_t) -1)
3096                                 line = out_buffer;
3097                 }
3099                 if (!view->ops->read(view, line)) {
3100                         report("Allocation failure");
3101                         end_update(view, TRUE);
3102                         return FALSE;
3103                 }
3104         }
3106         {
3107                 unsigned long lines = view->lines;
3108                 int digits;
3110                 for (digits = 0; lines; digits++)
3111                         lines /= 10;
3113                 /* Keep the displayed view in sync with line number scaling. */
3114                 if (digits != view->digits) {
3115                         view->digits = digits;
3116                         if (opt_line_number || view == VIEW(REQ_VIEW_BLAME))
3117                                 redraw = TRUE;
3118                 }
3119         }
3121         if (io_error(view->pipe)) {
3122                 report("Failed to read: %s", io_strerror(view->pipe));
3123                 end_update(view, TRUE);
3125         } else if (io_eof(view->pipe)) {
3126                 report("");
3127                 end_update(view, FALSE);
3128         }
3130         if (restore_view_position(view))
3131                 redraw = TRUE;
3133         if (!view_is_displayed(view))
3134                 return TRUE;
3136         if (redraw)
3137                 redraw_view_from(view, 0);
3138         else
3139                 redraw_view_dirty(view);
3141         /* Update the title _after_ the redraw so that if the redraw picks up a
3142          * commit reference in view->ref it'll be available here. */
3143         update_view_title(view);
3144         return TRUE;
3147 DEFINE_ALLOCATOR(realloc_lines, struct line, 256)
3149 static struct line *
3150 add_line_data(struct view *view, void *data, enum line_type type)
3152         struct line *line;
3154         if (!realloc_lines(&view->line, view->lines, 1))
3155                 return NULL;
3157         line = &view->line[view->lines++];
3158         memset(line, 0, sizeof(*line));
3159         line->type = type;
3160         line->data = data;
3161         line->dirty = 1;
3163         return line;
3166 static struct line *
3167 add_line_text(struct view *view, const char *text, enum line_type type)
3169         char *data = text ? strdup(text) : NULL;
3171         return data ? add_line_data(view, data, type) : NULL;
3174 static struct line *
3175 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
3177         char buf[SIZEOF_STR];
3178         va_list args;
3180         va_start(args, fmt);
3181         if (vsnprintf(buf, sizeof(buf), fmt, args) >= sizeof(buf))
3182                 buf[0] = 0;
3183         va_end(args);
3185         return buf[0] ? add_line_text(view, buf, type) : NULL;
3188 /*
3189  * View opening
3190  */
3192 enum open_flags {
3193         OPEN_DEFAULT = 0,       /* Use default view switching. */
3194         OPEN_SPLIT = 1,         /* Split current view. */
3195         OPEN_RELOAD = 4,        /* Reload view even if it is the current. */
3196         OPEN_REFRESH = 16,      /* Refresh view using previous command. */
3197         OPEN_PREPARED = 32,     /* Open already prepared command. */
3198 };
3200 static void
3201 open_view(struct view *prev, enum request request, enum open_flags flags)
3203         bool split = !!(flags & OPEN_SPLIT);
3204         bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
3205         bool nomaximize = !!(flags & OPEN_REFRESH);
3206         struct view *view = VIEW(request);
3207         int nviews = displayed_views();
3208         struct view *base_view = display[0];
3210         if (view == prev && nviews == 1 && !reload) {
3211                 report("Already in %s view", view->name);
3212                 return;
3213         }
3215         if (view->git_dir && !opt_git_dir[0]) {
3216                 report("The %s view is disabled in pager view", view->name);
3217                 return;
3218         }
3220         if (split) {
3221                 display[1] = view;
3222                 current_view = 1;
3223         } else if (!nomaximize) {
3224                 /* Maximize the current view. */
3225                 memset(display, 0, sizeof(display));
3226                 current_view = 0;
3227                 display[current_view] = view;
3228         }
3230         /* No parent signals that this is the first loaded view. */
3231         if (prev && view != prev) {
3232                 view->parent = prev;
3233         }
3235         /* Resize the view when switching between split- and full-screen,
3236          * or when switching between two different full-screen views. */
3237         if (nviews != displayed_views() ||
3238             (nviews == 1 && base_view != display[0]))
3239                 resize_display();
3241         if (view->ops->open) {
3242                 if (view->pipe)
3243                         end_update(view, TRUE);
3244                 if (!view->ops->open(view)) {
3245                         report("Failed to load %s view", view->name);
3246                         return;
3247                 }
3248                 restore_view_position(view);
3250         } else if ((reload || strcmp(view->vid, view->id)) &&
3251                    !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
3252                 report("Failed to load %s view", view->name);
3253                 return;
3254         }
3256         if (split && prev->lineno - prev->offset >= prev->height) {
3257                 /* Take the title line into account. */
3258                 int lines = prev->lineno - prev->offset - prev->height + 1;
3260                 /* Scroll the view that was split if the current line is
3261                  * outside the new limited view. */
3262                 do_scroll_view(prev, lines);
3263         }
3265         if (prev && view != prev && split && view_is_displayed(prev)) {
3266                 /* "Blur" the previous view. */
3267                 update_view_title(prev);
3268         }
3270         if (view->pipe && view->lines == 0) {
3271                 /* Clear the old view and let the incremental updating refill
3272                  * the screen. */
3273                 werase(view->win);
3274                 view->p_restore = flags & (OPEN_RELOAD | OPEN_REFRESH);
3275                 report("");
3276         } else if (view_is_displayed(view)) {
3277                 redraw_view(view);
3278                 report("");
3279         }
3282 static void
3283 open_external_viewer(const char *argv[], const char *dir)
3285         def_prog_mode();           /* save current tty modes */
3286         endwin();                  /* restore original tty modes */
3287         run_io_fg(argv, dir);
3288         fprintf(stderr, "Press Enter to continue");
3289         getc(opt_tty);
3290         reset_prog_mode();
3291         redraw_display(TRUE);
3294 static void
3295 open_mergetool(const char *file)
3297         const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
3299         open_external_viewer(mergetool_argv, opt_cdup);
3302 static void
3303 open_editor(bool from_root, const char *file)
3305         const char *editor_argv[] = { "vi", file, NULL };
3306         const char *editor;
3308         editor = getenv("GIT_EDITOR");
3309         if (!editor && *opt_editor)
3310                 editor = opt_editor;
3311         if (!editor)
3312                 editor = getenv("VISUAL");
3313         if (!editor)
3314                 editor = getenv("EDITOR");
3315         if (!editor)
3316                 editor = "vi";
3318         editor_argv[0] = editor;
3319         open_external_viewer(editor_argv, from_root ? opt_cdup : NULL);
3322 static void
3323 open_run_request(enum request request)
3325         struct run_request *req = get_run_request(request);
3326         const char *argv[ARRAY_SIZE(req->argv)] = { NULL };
3328         if (!req) {
3329                 report("Unknown run request");
3330                 return;
3331         }
3333         if (format_argv(argv, req->argv, FORMAT_ALL))
3334                 open_external_viewer(argv, NULL);
3335         free_argv(argv);
3338 /*
3339  * User request switch noodle
3340  */
3342 static int
3343 view_driver(struct view *view, enum request request)
3345         int i;
3347         if (request == REQ_NONE)
3348                 return TRUE;
3350         if (request > REQ_NONE) {
3351                 open_run_request(request);
3352                 /* FIXME: When all views can refresh always do this. */
3353                 if (view == VIEW(REQ_VIEW_STATUS) ||
3354                     view == VIEW(REQ_VIEW_MAIN) ||
3355                     view == VIEW(REQ_VIEW_LOG) ||
3356                     view == VIEW(REQ_VIEW_BRANCH) ||
3357                     view == VIEW(REQ_VIEW_STAGE))
3358                         request = REQ_REFRESH;
3359                 else
3360                         return TRUE;
3361         }
3363         if (view && view->lines) {
3364                 request = view->ops->request(view, request, &view->line[view->lineno]);
3365                 if (request == REQ_NONE)
3366                         return TRUE;
3367         }
3369         switch (request) {
3370         case REQ_MOVE_UP:
3371         case REQ_MOVE_DOWN:
3372         case REQ_MOVE_PAGE_UP:
3373         case REQ_MOVE_PAGE_DOWN:
3374         case REQ_MOVE_FIRST_LINE:
3375         case REQ_MOVE_LAST_LINE:
3376                 move_view(view, request);
3377                 break;
3379         case REQ_SCROLL_LEFT:
3380         case REQ_SCROLL_RIGHT:
3381         case REQ_SCROLL_LINE_DOWN:
3382         case REQ_SCROLL_LINE_UP:
3383         case REQ_SCROLL_PAGE_DOWN:
3384         case REQ_SCROLL_PAGE_UP:
3385                 scroll_view(view, request);
3386                 break;
3388         case REQ_VIEW_BLAME:
3389                 if (!opt_file[0]) {
3390                         report("No file chosen, press %s to open tree view",
3391                                get_key(view->keymap, REQ_VIEW_TREE));
3392                         break;
3393                 }
3394                 open_view(view, request, OPEN_DEFAULT);
3395                 break;
3397         case REQ_VIEW_BLOB:
3398                 if (!ref_blob[0]) {
3399                         report("No file chosen, press %s to open tree view",
3400                                get_key(view->keymap, REQ_VIEW_TREE));
3401                         break;
3402                 }
3403                 open_view(view, request, OPEN_DEFAULT);
3404                 break;
3406         case REQ_VIEW_PAGER:
3407                 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3408                         report("No pager content, press %s to run command from prompt",
3409                                get_key(view->keymap, REQ_PROMPT));
3410                         break;
3411                 }
3412                 open_view(view, request, OPEN_DEFAULT);
3413                 break;
3415         case REQ_VIEW_STAGE:
3416                 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3417                         report("No stage content, press %s to open the status view and choose file",
3418                                get_key(view->keymap, REQ_VIEW_STATUS));
3419                         break;
3420                 }
3421                 open_view(view, request, OPEN_DEFAULT);
3422                 break;
3424         case REQ_VIEW_STATUS:
3425                 if (opt_is_inside_work_tree == FALSE) {
3426                         report("The status view requires a working tree");
3427                         break;
3428                 }
3429                 open_view(view, request, OPEN_DEFAULT);
3430                 break;
3432         case REQ_VIEW_MAIN:
3433         case REQ_VIEW_DIFF:
3434         case REQ_VIEW_LOG:
3435         case REQ_VIEW_TREE:
3436         case REQ_VIEW_HELP:
3437         case REQ_VIEW_BRANCH:
3438                 open_view(view, request, OPEN_DEFAULT);
3439                 break;
3441         case REQ_NEXT:
3442         case REQ_PREVIOUS:
3443                 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3445                 if ((view == VIEW(REQ_VIEW_DIFF) &&
3446                      view->parent == VIEW(REQ_VIEW_MAIN)) ||
3447                    (view == VIEW(REQ_VIEW_DIFF) &&
3448                      view->parent == VIEW(REQ_VIEW_BLAME)) ||
3449                    (view == VIEW(REQ_VIEW_STAGE) &&
3450                      view->parent == VIEW(REQ_VIEW_STATUS)) ||
3451                    (view == VIEW(REQ_VIEW_BLOB) &&
3452                      view->parent == VIEW(REQ_VIEW_TREE)) ||
3453                    (view == VIEW(REQ_VIEW_MAIN) &&
3454                      view->parent == VIEW(REQ_VIEW_BRANCH))) {
3455                         int line;
3457                         view = view->parent;
3458                         line = view->lineno;
3459                         move_view(view, request);
3460                         if (view_is_displayed(view))
3461                                 update_view_title(view);
3462                         if (line != view->lineno)
3463                                 view->ops->request(view, REQ_ENTER,
3464                                                    &view->line[view->lineno]);
3466                 } else {
3467                         move_view(view, request);
3468                 }
3469                 break;
3471         case REQ_VIEW_NEXT:
3472         {
3473                 int nviews = displayed_views();
3474                 int next_view = (current_view + 1) % nviews;
3476                 if (next_view == current_view) {
3477                         report("Only one view is displayed");
3478                         break;
3479                 }
3481                 current_view = next_view;
3482                 /* Blur out the title of the previous view. */
3483                 update_view_title(view);
3484                 report("");
3485                 break;
3486         }
3487         case REQ_REFRESH:
3488                 report("Refreshing is not yet supported for the %s view", view->name);
3489                 break;
3491         case REQ_MAXIMIZE:
3492                 if (displayed_views() == 2)
3493                         maximize_view(view);
3494                 break;
3496         case REQ_OPTIONS:
3497                 open_option_menu();
3498                 break;
3500         case REQ_TOGGLE_LINENO:
3501                 toggle_view_option(&opt_line_number, "line numbers");
3502                 break;
3504         case REQ_TOGGLE_DATE:
3505                 toggle_date_option(&opt_date);
3506                 break;
3508         case REQ_TOGGLE_AUTHOR:
3509                 toggle_view_option(&opt_author, "author display");
3510                 break;
3512         case REQ_TOGGLE_REV_GRAPH:
3513                 toggle_view_option(&opt_rev_graph, "revision graph display");
3514                 break;
3516         case REQ_TOGGLE_REFS:
3517                 toggle_view_option(&opt_show_refs, "reference display");
3518                 break;
3520         case REQ_TOGGLE_SORT_FIELD:
3521         case REQ_TOGGLE_SORT_ORDER:
3522                 report("Sorting is not yet supported for the %s view", view->name);
3523                 break;
3525         case REQ_SEARCH:
3526         case REQ_SEARCH_BACK:
3527                 search_view(view, request);
3528                 break;
3530         case REQ_FIND_NEXT:
3531         case REQ_FIND_PREV:
3532                 find_next(view, request);
3533                 break;
3535         case REQ_STOP_LOADING:
3536                 for (i = 0; i < ARRAY_SIZE(views); i++) {
3537                         view = &views[i];
3538                         if (view->pipe)
3539                                 report("Stopped loading the %s view", view->name),
3540                         end_update(view, TRUE);
3541                 }
3542                 break;
3544         case REQ_SHOW_VERSION:
3545                 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3546                 return TRUE;
3548         case REQ_SCREEN_REDRAW:
3549                 redraw_display(TRUE);
3550                 break;
3552         case REQ_EDIT:
3553                 report("Nothing to edit");
3554                 break;
3556         case REQ_ENTER:
3557                 report("Nothing to enter");
3558                 break;
3560         case REQ_VIEW_CLOSE:
3561                 /* XXX: Mark closed views by letting view->parent point to the
3562                  * view itself. Parents to closed view should never be
3563                  * followed. */
3564                 if (view->parent &&
3565                     view->parent->parent != view->parent) {
3566                         maximize_view(view->parent);
3567                         view->parent = view;
3568                         break;
3569                 }
3570                 /* Fall-through */
3571         case REQ_QUIT:
3572                 return FALSE;
3574         default:
3575                 report("Unknown key, press %s for help",
3576                        get_key(view->keymap, REQ_VIEW_HELP));
3577                 return TRUE;
3578         }
3580         return TRUE;
3584 /*
3585  * View backend utilities
3586  */
3588 enum sort_field {
3589         ORDERBY_NAME,
3590         ORDERBY_DATE,
3591         ORDERBY_AUTHOR,
3592 };
3594 struct sort_state {
3595         const enum sort_field *fields;
3596         size_t size, current;
3597         bool reverse;
3598 };
3600 #define SORT_STATE(fields) { fields, ARRAY_SIZE(fields), 0 }
3601 #define get_sort_field(state) ((state).fields[(state).current])
3602 #define sort_order(state, result) ((state).reverse ? -(result) : (result))
3604 static void
3605 sort_view(struct view *view, enum request request, struct sort_state *state,
3606           int (*compare)(const void *, const void *))
3608         switch (request) {
3609         case REQ_TOGGLE_SORT_FIELD:
3610                 state->current = (state->current + 1) % state->size;
3611                 break;
3613         case REQ_TOGGLE_SORT_ORDER:
3614                 state->reverse = !state->reverse;
3615                 break;
3616         default:
3617                 die("Not a sort request");
3618         }
3620         qsort(view->line, view->lines, sizeof(*view->line), compare);
3621         redraw_view(view);
3624 DEFINE_ALLOCATOR(realloc_authors, const char *, 256)
3626 /* Small author cache to reduce memory consumption. It uses binary
3627  * search to lookup or find place to position new entries. No entries
3628  * are ever freed. */
3629 static const char *
3630 get_author(const char *name)
3632         static const char **authors;
3633         static size_t authors_size;
3634         int from = 0, to = authors_size - 1;
3636         while (from <= to) {
3637                 size_t pos = (to + from) / 2;
3638                 int cmp = strcmp(name, authors[pos]);
3640                 if (!cmp)
3641                         return authors[pos];
3643                 if (cmp < 0)
3644                         to = pos - 1;
3645                 else
3646                         from = pos + 1;
3647         }
3649         if (!realloc_authors(&authors, authors_size, 1))
3650                 return NULL;
3651         name = strdup(name);
3652         if (!name)
3653                 return NULL;
3655         memmove(authors + from + 1, authors + from, (authors_size - from) * sizeof(*authors));
3656         authors[from] = name;
3657         authors_size++;
3659         return name;
3662 static void
3663 parse_timezone(time_t *time, const char *zone)
3665         long tz;
3667         tz  = ('0' - zone[1]) * 60 * 60 * 10;
3668         tz += ('0' - zone[2]) * 60 * 60;
3669         tz += ('0' - zone[3]) * 60;
3670         tz += ('0' - zone[4]);
3672         if (zone[0] == '-')
3673                 tz = -tz;
3675         *time -= tz;
3678 /* Parse author lines where the name may be empty:
3679  *      author  <email@address.tld> 1138474660 +0100
3680  */
3681 static void
3682 parse_author_line(char *ident, const char **author, time_t *time)
3684         char *nameend = strchr(ident, '<');
3685         char *emailend = strchr(ident, '>');
3687         if (nameend && emailend)
3688                 *nameend = *emailend = 0;
3689         ident = chomp_string(ident);
3690         if (!*ident) {
3691                 if (nameend)
3692                         ident = chomp_string(nameend + 1);
3693                 if (!*ident)
3694                         ident = "Unknown";
3695         }
3697         *author = get_author(ident);
3699         /* Parse epoch and timezone */
3700         if (emailend && emailend[1] == ' ') {
3701                 char *secs = emailend + 2;
3702                 char *zone = strchr(secs, ' ');
3704                 *time = (time_t) atol(secs);
3706                 if (zone && strlen(zone) == STRING_SIZE(" +0700"))
3707                         parse_timezone(time, zone + 1);
3708         }
3711 static bool
3712 open_commit_parent_menu(char buf[SIZEOF_STR], int *parents)
3714         char rev[SIZEOF_REV];
3715         const char *revlist_argv[] = {
3716                 "git", "log", "--no-color", "-1", "--pretty=format:%s", rev, NULL
3717         };
3718         struct menu_item *items;
3719         char text[SIZEOF_STR];
3720         bool ok = TRUE;
3721         int i;
3723         items = calloc(*parents + 1, sizeof(*items));
3724         if (!items)
3725                 return FALSE;
3727         for (i = 0; i < *parents; i++) {
3728                 string_copy_rev(rev, &buf[SIZEOF_REV * i]);
3729                 if (!run_io_buf(revlist_argv, text, sizeof(text)) ||
3730                     !(items[i].text = strdup(text))) {
3731                         ok = FALSE;
3732                         break;
3733                 }
3734         }
3736         if (ok) {
3737                 *parents = 0;
3738                 ok = prompt_menu("Select parent", items, parents);
3739         }
3740         for (i = 0; items[i].text; i++)
3741                 free((char *) items[i].text);
3742         free(items);
3743         return ok;
3746 static bool
3747 select_commit_parent(const char *id, char rev[SIZEOF_REV], const char *path)
3749         char buf[SIZEOF_STR * 4];
3750         const char *revlist_argv[] = {
3751                 "git", "log", "--no-color", "-1",
3752                         "--pretty=format:%P", id, "--", path, NULL
3753         };
3754         int parents;
3756         if (!run_io_buf(revlist_argv, buf, sizeof(buf)) ||
3757             (parents = strlen(buf) / 40) < 0) {
3758                 report("Failed to get parent information");
3759                 return FALSE;
3761         } else if (parents == 0) {
3762                 if (path)
3763                         report("Path '%s' does not exist in the parent", path);
3764                 else
3765                         report("The selected commit has no parents");
3766                 return FALSE;
3767         }
3769         if (parents > 1 && !open_commit_parent_menu(buf, &parents))
3770                 return FALSE;
3772         string_copy_rev(rev, &buf[41 * parents]);
3773         return TRUE;
3776 /*
3777  * Pager backend
3778  */
3780 static bool
3781 pager_draw(struct view *view, struct line *line, unsigned int lineno)
3783         char text[SIZEOF_STR];
3785         if (opt_line_number && draw_lineno(view, lineno))
3786                 return TRUE;
3788         string_expand(text, sizeof(text), line->data, opt_tab_size);
3789         draw_text(view, line->type, text, TRUE);
3790         return TRUE;
3793 static bool
3794 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
3796         const char *describe_argv[] = { "git", "describe", commit_id, NULL };
3797         char ref[SIZEOF_STR];
3799         if (!run_io_buf(describe_argv, ref, sizeof(ref)) || !*ref)
3800                 return TRUE;
3802         /* This is the only fatal call, since it can "corrupt" the buffer. */
3803         if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
3804                 return FALSE;
3806         return TRUE;
3809 static void
3810 add_pager_refs(struct view *view, struct line *line)
3812         char buf[SIZEOF_STR];
3813         char *commit_id = (char *)line->data + STRING_SIZE("commit ");
3814         struct ref_list *list;
3815         size_t bufpos = 0, i;
3816         const char *sep = "Refs: ";
3817         bool is_tag = FALSE;
3819         assert(line->type == LINE_COMMIT);
3821         list = get_ref_list(commit_id);
3822         if (!list) {
3823                 if (view == VIEW(REQ_VIEW_DIFF))
3824                         goto try_add_describe_ref;
3825                 return;
3826         }
3828         for (i = 0; i < list->size; i++) {
3829                 struct ref *ref = list->refs[i];
3830                 const char *fmt = ref->tag    ? "%s[%s]" :
3831                                   ref->remote ? "%s<%s>" : "%s%s";
3833                 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
3834                         return;
3835                 sep = ", ";
3836                 if (ref->tag)
3837                         is_tag = TRUE;
3838         }
3840         if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
3841 try_add_describe_ref:
3842                 /* Add <tag>-g<commit_id> "fake" reference. */
3843                 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
3844                         return;
3845         }
3847         if (bufpos == 0)
3848                 return;
3850         add_line_text(view, buf, LINE_PP_REFS);
3853 static bool
3854 pager_read(struct view *view, char *data)
3856         struct line *line;
3858         if (!data)
3859                 return TRUE;
3861         line = add_line_text(view, data, get_line_type(data));
3862         if (!line)
3863                 return FALSE;
3865         if (line->type == LINE_COMMIT &&
3866             (view == VIEW(REQ_VIEW_DIFF) ||
3867              view == VIEW(REQ_VIEW_LOG)))
3868                 add_pager_refs(view, line);
3870         return TRUE;
3873 static enum request
3874 pager_request(struct view *view, enum request request, struct line *line)
3876         int split = 0;
3878         if (request != REQ_ENTER)
3879                 return request;
3881         if (line->type == LINE_COMMIT &&
3882            (view == VIEW(REQ_VIEW_LOG) ||
3883             view == VIEW(REQ_VIEW_PAGER))) {
3884                 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
3885                 split = 1;
3886         }
3888         /* Always scroll the view even if it was split. That way
3889          * you can use Enter to scroll through the log view and
3890          * split open each commit diff. */
3891         scroll_view(view, REQ_SCROLL_LINE_DOWN);
3893         /* FIXME: A minor workaround. Scrolling the view will call report("")
3894          * but if we are scrolling a non-current view this won't properly
3895          * update the view title. */
3896         if (split)
3897                 update_view_title(view);
3899         return REQ_NONE;
3902 static bool
3903 pager_grep(struct view *view, struct line *line)
3905         const char *text[] = { line->data, NULL };
3907         return grep_text(view, text);
3910 static void
3911 pager_select(struct view *view, struct line *line)
3913         if (line->type == LINE_COMMIT) {
3914                 char *text = (char *)line->data + STRING_SIZE("commit ");
3916                 if (view != VIEW(REQ_VIEW_PAGER))
3917                         string_copy_rev(view->ref, text);
3918                 string_copy_rev(ref_commit, text);
3919         }
3922 static struct view_ops pager_ops = {
3923         "line",
3924         NULL,
3925         NULL,
3926         pager_read,
3927         pager_draw,
3928         pager_request,
3929         pager_grep,
3930         pager_select,
3931 };
3933 static const char *log_argv[SIZEOF_ARG] = {
3934         "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
3935 };
3937 static enum request
3938 log_request(struct view *view, enum request request, struct line *line)
3940         switch (request) {
3941         case REQ_REFRESH:
3942                 load_refs();
3943                 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
3944                 return REQ_NONE;
3945         default:
3946                 return pager_request(view, request, line);
3947         }
3950 static struct view_ops log_ops = {
3951         "line",
3952         log_argv,
3953         NULL,
3954         pager_read,
3955         pager_draw,
3956         log_request,
3957         pager_grep,
3958         pager_select,
3959 };
3961 static const char *diff_argv[SIZEOF_ARG] = {
3962         "git", "show", "--pretty=fuller", "--no-color", "--root",
3963                 "--patch-with-stat", "--find-copies-harder", "-C", "%(commit)", NULL
3964 };
3966 static struct view_ops diff_ops = {
3967         "line",
3968         diff_argv,
3969         NULL,
3970         pager_read,
3971         pager_draw,
3972         pager_request,
3973         pager_grep,
3974         pager_select,
3975 };
3977 /*
3978  * Help backend
3979  */
3981 static bool help_keymap_hidden[ARRAY_SIZE(keymap_table)];
3983 static bool
3984 help_open_keymap_title(struct view *view, enum keymap keymap)
3986         struct line *line;
3988         line = add_line_format(view, LINE_HELP_KEYMAP, "[%c] %s bindings",
3989                                help_keymap_hidden[keymap] ? '+' : '-',
3990                                enum_name(keymap_table[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                                 enum_name(req_info[i]), req_info[i].help);
4032         }
4034         group = "External commands:";
4036         for (i = 0; i < run_requests; i++) {
4037                 struct run_request *req = get_run_request(REQ_NONE + i + 1);
4038                 const char *key;
4039                 int argc;
4041                 if (!req || req->keymap != keymap)
4042                         continue;
4044                 key = get_key_name(req->key);
4045                 if (!*key)
4046                         key = "(no key defined)";
4048                 if (add_title && help_open_keymap_title(view, keymap))
4049                         return;
4050                 if (group) {
4051                         add_line_text(view, group, LINE_HELP_GROUP);
4052                         group = NULL;
4053                 }
4055                 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
4056                         if (!string_format_from(buf, &bufpos, "%s%s",
4057                                                 argc ? " " : "", req->argv[argc]))
4058                                 return;
4060                 add_line_format(view, LINE_DEFAULT, "    %-25s `%s`", key, buf);
4061         }
4064 static bool
4065 help_open(struct view *view)
4067         enum keymap keymap;
4069         reset_view(view);
4070         add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
4071         add_line_text(view, "", LINE_DEFAULT);
4073         for (keymap = 0; keymap < ARRAY_SIZE(keymap_table); keymap++)
4074                 help_open_keymap(view, keymap);
4076         return TRUE;
4079 static enum request
4080 help_request(struct view *view, enum request request, struct line *line)
4082         switch (request) {
4083         case REQ_ENTER:
4084                 if (line->type == LINE_HELP_KEYMAP) {
4085                         help_keymap_hidden[line->other] =
4086                                 !help_keymap_hidden[line->other];
4087                         view->p_restore = TRUE;
4088                         open_view(view, REQ_VIEW_HELP, OPEN_REFRESH);
4089                 }
4091                 return REQ_NONE;
4092         default:
4093                 return pager_request(view, request, line);
4094         }
4097 static struct view_ops help_ops = {
4098         "line",
4099         NULL,
4100         help_open,
4101         NULL,
4102         pager_draw,
4103         help_request,
4104         pager_grep,
4105         pager_select,
4106 };
4109 /*
4110  * Tree backend
4111  */
4113 struct tree_stack_entry {
4114         struct tree_stack_entry *prev;  /* Entry below this in the stack */
4115         unsigned long lineno;           /* Line number to restore */
4116         char *name;                     /* Position of name in opt_path */
4117 };
4119 /* The top of the path stack. */
4120 static struct tree_stack_entry *tree_stack = NULL;
4121 unsigned long tree_lineno = 0;
4123 static void
4124 pop_tree_stack_entry(void)
4126         struct tree_stack_entry *entry = tree_stack;
4128         tree_lineno = entry->lineno;
4129         entry->name[0] = 0;
4130         tree_stack = entry->prev;
4131         free(entry);
4134 static void
4135 push_tree_stack_entry(const char *name, unsigned long lineno)
4137         struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
4138         size_t pathlen = strlen(opt_path);
4140         if (!entry)
4141                 return;
4143         entry->prev = tree_stack;
4144         entry->name = opt_path + pathlen;
4145         tree_stack = entry;
4147         if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
4148                 pop_tree_stack_entry();
4149                 return;
4150         }
4152         /* Move the current line to the first tree entry. */
4153         tree_lineno = 1;
4154         entry->lineno = lineno;
4157 /* Parse output from git-ls-tree(1):
4158  *
4159  * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
4160  */
4162 #define SIZEOF_TREE_ATTR \
4163         STRING_SIZE("100644 blob f931e1d229c3e185caad4449bf5b66ed72462657\t")
4165 #define SIZEOF_TREE_MODE \
4166         STRING_SIZE("100644 ")
4168 #define TREE_ID_OFFSET \
4169         STRING_SIZE("100644 blob ")
4171 struct tree_entry {
4172         char id[SIZEOF_REV];
4173         mode_t mode;
4174         time_t time;                    /* Date from the author ident. */
4175         const char *author;             /* Author of the commit. */
4176         char name[1];
4177 };
4179 static const char *
4180 tree_path(const struct line *line)
4182         return ((struct tree_entry *) line->data)->name;
4185 static int
4186 tree_compare_entry(const struct line *line1, const struct line *line2)
4188         if (line1->type != line2->type)
4189                 return line1->type == LINE_TREE_DIR ? -1 : 1;
4190         return strcmp(tree_path(line1), tree_path(line2));
4193 static const enum sort_field tree_sort_fields[] = {
4194         ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
4195 };
4196 static struct sort_state tree_sort_state = SORT_STATE(tree_sort_fields);
4198 static int
4199 tree_compare(const void *l1, const void *l2)
4201         const struct line *line1 = (const struct line *) l1;
4202         const struct line *line2 = (const struct line *) l2;
4203         const struct tree_entry *entry1 = ((const struct line *) l1)->data;
4204         const struct tree_entry *entry2 = ((const struct line *) l2)->data;
4206         if (line1->type == LINE_TREE_HEAD)
4207                 return -1;
4208         if (line2->type == LINE_TREE_HEAD)
4209                 return 1;
4211         switch (get_sort_field(tree_sort_state)) {
4212         case ORDERBY_DATE:
4213                 return sort_order(tree_sort_state, entry1->time - entry2->time);
4215         case ORDERBY_AUTHOR:
4216                 return sort_order(tree_sort_state, strcmp(entry1->author, entry2->author));
4218         case ORDERBY_NAME:
4219         default:
4220                 return sort_order(tree_sort_state, tree_compare_entry(line1, line2));
4221         }
4225 static struct line *
4226 tree_entry(struct view *view, enum line_type type, const char *path,
4227            const char *mode, const char *id)
4229         struct tree_entry *entry = calloc(1, sizeof(*entry) + strlen(path));
4230         struct line *line = entry ? add_line_data(view, entry, type) : NULL;
4232         if (!entry || !line) {
4233                 free(entry);
4234                 return NULL;
4235         }
4237         strncpy(entry->name, path, strlen(path));
4238         if (mode)
4239                 entry->mode = strtoul(mode, NULL, 8);
4240         if (id)
4241                 string_copy_rev(entry->id, id);
4243         return line;
4246 static bool
4247 tree_read_date(struct view *view, char *text, bool *read_date)
4249         static const char *author_name;
4250         static time_t author_time;
4252         if (!text && *read_date) {
4253                 *read_date = FALSE;
4254                 return TRUE;
4256         } else if (!text) {
4257                 char *path = *opt_path ? opt_path : ".";
4258                 /* Find next entry to process */
4259                 const char *log_file[] = {
4260                         "git", "log", "--no-color", "--pretty=raw",
4261                                 "--cc", "--raw", view->id, "--", path, NULL
4262                 };
4263                 struct io io = {};
4265                 if (!view->lines) {
4266                         tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL);
4267                         report("Tree is empty");
4268                         return TRUE;
4269                 }
4271                 if (!run_io_rd(&io, log_file, opt_cdup, FORMAT_NONE)) {
4272                         report("Failed to load tree data");
4273                         return TRUE;
4274                 }
4276                 done_io(view->pipe);
4277                 view->io = io;
4278                 *read_date = TRUE;
4279                 return FALSE;
4281         } else if (*text == 'a' && get_line_type(text) == LINE_AUTHOR) {
4282                 parse_author_line(text + STRING_SIZE("author "),
4283                                   &author_name, &author_time);
4285         } else if (*text == ':') {
4286                 char *pos;
4287                 size_t annotated = 1;
4288                 size_t i;
4290                 pos = strchr(text, '\t');
4291                 if (!pos)
4292                         return TRUE;
4293                 text = pos + 1;
4294                 if (*opt_path && !strncmp(text, opt_path, strlen(opt_path)))
4295                         text += strlen(opt_path);
4296                 pos = strchr(text, '/');
4297                 if (pos)
4298                         *pos = 0;
4300                 for (i = 1; i < view->lines; i++) {
4301                         struct line *line = &view->line[i];
4302                         struct tree_entry *entry = line->data;
4304                         annotated += !!entry->author;
4305                         if (entry->author || strcmp(entry->name, text))
4306                                 continue;
4308                         entry->author = author_name;
4309                         entry->time = author_time;
4310                         line->dirty = 1;
4311                         break;
4312                 }
4314                 if (annotated == view->lines)
4315                         kill_io(view->pipe);
4316         }
4317         return TRUE;
4320 static bool
4321 tree_read(struct view *view, char *text)
4323         static bool read_date = FALSE;
4324         struct tree_entry *data;
4325         struct line *entry, *line;
4326         enum line_type type;
4327         size_t textlen = text ? strlen(text) : 0;
4328         char *path = text + SIZEOF_TREE_ATTR;
4330         if (read_date || !text)
4331                 return tree_read_date(view, text, &read_date);
4333         if (textlen <= SIZEOF_TREE_ATTR)
4334                 return FALSE;
4335         if (view->lines == 0 &&
4336             !tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL))
4337                 return FALSE;
4339         /* Strip the path part ... */
4340         if (*opt_path) {
4341                 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
4342                 size_t striplen = strlen(opt_path);
4344                 if (pathlen > striplen)
4345                         memmove(path, path + striplen,
4346                                 pathlen - striplen + 1);
4348                 /* Insert "link" to parent directory. */
4349                 if (view->lines == 1 &&
4350                     !tree_entry(view, LINE_TREE_DIR, "..", "040000", view->ref))
4351                         return FALSE;
4352         }
4354         type = text[SIZEOF_TREE_MODE] == 't' ? LINE_TREE_DIR : LINE_TREE_FILE;
4355         entry = tree_entry(view, type, path, text, text + TREE_ID_OFFSET);
4356         if (!entry)
4357                 return FALSE;
4358         data = entry->data;
4360         /* Skip "Directory ..." and ".." line. */
4361         for (line = &view->line[1 + !!*opt_path]; line < entry; line++) {
4362                 if (tree_compare_entry(line, entry) <= 0)
4363                         continue;
4365                 memmove(line + 1, line, (entry - line) * sizeof(*entry));
4367                 line->data = data;
4368                 line->type = type;
4369                 for (; line <= entry; line++)
4370                         line->dirty = line->cleareol = 1;
4371                 return TRUE;
4372         }
4374         if (tree_lineno > view->lineno) {
4375                 view->lineno = tree_lineno;
4376                 tree_lineno = 0;
4377         }
4379         return TRUE;
4382 static bool
4383 tree_draw(struct view *view, struct line *line, unsigned int lineno)
4385         struct tree_entry *entry = line->data;
4387         if (line->type == LINE_TREE_HEAD) {
4388                 if (draw_text(view, line->type, "Directory path /", TRUE))
4389                         return TRUE;
4390         } else {
4391                 if (draw_mode(view, entry->mode))
4392                         return TRUE;
4394                 if (opt_author && draw_author(view, entry->author))
4395                         return TRUE;
4397                 if (opt_date && draw_date(view, entry->author ? &entry->time : NULL))
4398                         return TRUE;
4399         }
4400         if (draw_text(view, line->type, entry->name, TRUE))
4401                 return TRUE;
4402         return TRUE;
4405 static void
4406 open_blob_editor()
4408         char file[SIZEOF_STR] = "/tmp/tigblob.XXXXXX";
4409         int fd = mkstemp(file);
4411         if (fd == -1)
4412                 report("Failed to create temporary file");
4413         else if (!run_io_append(blob_ops.argv, FORMAT_ALL, fd))
4414                 report("Failed to save blob data to file");
4415         else
4416                 open_editor(FALSE, file);
4417         if (fd != -1)
4418                 unlink(file);
4421 static enum request
4422 tree_request(struct view *view, enum request request, struct line *line)
4424         enum open_flags flags;
4426         switch (request) {
4427         case REQ_VIEW_BLAME:
4428                 if (line->type != LINE_TREE_FILE) {
4429                         report("Blame only supported for files");
4430                         return REQ_NONE;
4431                 }
4433                 string_copy(opt_ref, view->vid);
4434                 return request;
4436         case REQ_EDIT:
4437                 if (line->type != LINE_TREE_FILE) {
4438                         report("Edit only supported for files");
4439                 } else if (!is_head_commit(view->vid)) {
4440                         open_blob_editor();
4441                 } else {
4442                         open_editor(TRUE, opt_file);
4443                 }
4444                 return REQ_NONE;
4446         case REQ_TOGGLE_SORT_FIELD:
4447         case REQ_TOGGLE_SORT_ORDER:
4448                 sort_view(view, request, &tree_sort_state, tree_compare);
4449                 return REQ_NONE;
4451         case REQ_PARENT:
4452                 if (!*opt_path) {
4453                         /* quit view if at top of tree */
4454                         return REQ_VIEW_CLOSE;
4455                 }
4456                 /* fake 'cd  ..' */
4457                 line = &view->line[1];
4458                 break;
4460         case REQ_ENTER:
4461                 break;
4463         default:
4464                 return request;
4465         }
4467         /* Cleanup the stack if the tree view is at a different tree. */
4468         while (!*opt_path && tree_stack)
4469                 pop_tree_stack_entry();
4471         switch (line->type) {
4472         case LINE_TREE_DIR:
4473                 /* Depending on whether it is a subdirectory or parent link
4474                  * mangle the path buffer. */
4475                 if (line == &view->line[1] && *opt_path) {
4476                         pop_tree_stack_entry();
4478                 } else {
4479                         const char *basename = tree_path(line);
4481                         push_tree_stack_entry(basename, view->lineno);
4482                 }
4484                 /* Trees and subtrees share the same ID, so they are not not
4485                  * unique like blobs. */
4486                 flags = OPEN_RELOAD;
4487                 request = REQ_VIEW_TREE;
4488                 break;
4490         case LINE_TREE_FILE:
4491                 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4492                 request = REQ_VIEW_BLOB;
4493                 break;
4495         default:
4496                 return REQ_NONE;
4497         }
4499         open_view(view, request, flags);
4500         if (request == REQ_VIEW_TREE)
4501                 view->lineno = tree_lineno;
4503         return REQ_NONE;
4506 static bool
4507 tree_grep(struct view *view, struct line *line)
4509         struct tree_entry *entry = line->data;
4510         const char *text[] = {
4511                 entry->name,
4512                 opt_author ? entry->author : "",
4513                 opt_date ? mkdate(&entry->time) : "",
4514                 NULL
4515         };
4517         return grep_text(view, text);
4520 static void
4521 tree_select(struct view *view, struct line *line)
4523         struct tree_entry *entry = line->data;
4525         if (line->type == LINE_TREE_FILE) {
4526                 string_copy_rev(ref_blob, entry->id);
4527                 string_format(opt_file, "%s%s", opt_path, tree_path(line));
4529         } else if (line->type != LINE_TREE_DIR) {
4530                 return;
4531         }
4533         string_copy_rev(view->ref, entry->id);
4536 static bool
4537 tree_prepare(struct view *view)
4539         if (view->lines == 0 && opt_prefix[0]) {
4540                 char *pos = opt_prefix;
4542                 while (pos && *pos) {
4543                         char *end = strchr(pos, '/');
4545                         if (end)
4546                                 *end = 0;
4547                         push_tree_stack_entry(pos, 0);
4548                         pos = end;
4549                         if (end) {
4550                                 *end = '/';
4551                                 pos++;
4552                         }
4553                 }
4555         } else if (strcmp(view->vid, view->id)) {
4556                 opt_path[0] = 0;
4557         }
4559         return init_io_rd(&view->io, view->ops->argv, opt_cdup, FORMAT_ALL);
4562 static const char *tree_argv[SIZEOF_ARG] = {
4563         "git", "ls-tree", "%(commit)", "%(directory)", NULL
4564 };
4566 static struct view_ops tree_ops = {
4567         "file",
4568         tree_argv,
4569         NULL,
4570         tree_read,
4571         tree_draw,
4572         tree_request,
4573         tree_grep,
4574         tree_select,
4575         tree_prepare,
4576 };
4578 static bool
4579 blob_read(struct view *view, char *line)
4581         if (!line)
4582                 return TRUE;
4583         return add_line_text(view, line, LINE_DEFAULT) != NULL;
4586 static enum request
4587 blob_request(struct view *view, enum request request, struct line *line)
4589         switch (request) {
4590         case REQ_EDIT:
4591                 open_blob_editor();
4592                 return REQ_NONE;
4593         default:
4594                 return pager_request(view, request, line);
4595         }
4598 static const char *blob_argv[SIZEOF_ARG] = {
4599         "git", "cat-file", "blob", "%(blob)", NULL
4600 };
4602 static struct view_ops blob_ops = {
4603         "line",
4604         blob_argv,
4605         NULL,
4606         blob_read,
4607         pager_draw,
4608         blob_request,
4609         pager_grep,
4610         pager_select,
4611 };
4613 /*
4614  * Blame backend
4615  *
4616  * Loading the blame view is a two phase job:
4617  *
4618  *  1. File content is read either using opt_file from the
4619  *     filesystem or using git-cat-file.
4620  *  2. Then blame information is incrementally added by
4621  *     reading output from git-blame.
4622  */
4624 static const char *blame_head_argv[] = {
4625         "git", "blame", "--incremental", "--", "%(file)", NULL
4626 };
4628 static const char *blame_ref_argv[] = {
4629         "git", "blame", "--incremental", "%(ref)", "--", "%(file)", NULL
4630 };
4632 static const char *blame_cat_file_argv[] = {
4633         "git", "cat-file", "blob", "%(ref):%(file)", NULL
4634 };
4636 struct blame_commit {
4637         char id[SIZEOF_REV];            /* SHA1 ID. */
4638         char title[128];                /* First line of the commit message. */
4639         const char *author;             /* Author of the commit. */
4640         time_t time;                    /* Date from the author ident. */
4641         char filename[128];             /* Name of file. */
4642         bool has_previous;              /* Was a "previous" line detected. */
4643 };
4645 struct blame {
4646         struct blame_commit *commit;
4647         unsigned long lineno;
4648         char text[1];
4649 };
4651 static bool
4652 blame_open(struct view *view)
4654         char path[SIZEOF_STR];
4656         if (!view->parent && *opt_prefix) {
4657                 string_copy(path, opt_file);
4658                 if (!string_format(opt_file, "%s%s", opt_prefix, path))
4659                         return FALSE;
4660         }
4662         if (*opt_ref || !io_open(&view->io, "%s%s", opt_cdup, opt_file)) {
4663                 if (!run_io_rd(&view->io, blame_cat_file_argv, opt_cdup, FORMAT_ALL))
4664                         return FALSE;
4665         }
4667         setup_update(view, opt_file);
4668         string_format(view->ref, "%s ...", opt_file);
4670         return TRUE;
4673 static struct blame_commit *
4674 get_blame_commit(struct view *view, const char *id)
4676         size_t i;
4678         for (i = 0; i < view->lines; i++) {
4679                 struct blame *blame = view->line[i].data;
4681                 if (!blame->commit)
4682                         continue;
4684                 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
4685                         return blame->commit;
4686         }
4688         {
4689                 struct blame_commit *commit = calloc(1, sizeof(*commit));
4691                 if (commit)
4692                         string_ncopy(commit->id, id, SIZEOF_REV);
4693                 return commit;
4694         }
4697 static bool
4698 parse_number(const char **posref, size_t *number, size_t min, size_t max)
4700         const char *pos = *posref;
4702         *posref = NULL;
4703         pos = strchr(pos + 1, ' ');
4704         if (!pos || !isdigit(pos[1]))
4705                 return FALSE;
4706         *number = atoi(pos + 1);
4707         if (*number < min || *number > max)
4708                 return FALSE;
4710         *posref = pos;
4711         return TRUE;
4714 static struct blame_commit *
4715 parse_blame_commit(struct view *view, const char *text, int *blamed)
4717         struct blame_commit *commit;
4718         struct blame *blame;
4719         const char *pos = text + SIZEOF_REV - 2;
4720         size_t orig_lineno = 0;
4721         size_t lineno;
4722         size_t group;
4724         if (strlen(text) <= SIZEOF_REV || pos[1] != ' ')
4725                 return NULL;
4727         if (!parse_number(&pos, &orig_lineno, 1, 9999999) ||
4728             !parse_number(&pos, &lineno, 1, view->lines) ||
4729             !parse_number(&pos, &group, 1, view->lines - lineno + 1))
4730                 return NULL;
4732         commit = get_blame_commit(view, text);
4733         if (!commit)
4734                 return NULL;
4736         *blamed += group;
4737         while (group--) {
4738                 struct line *line = &view->line[lineno + group - 1];
4740                 blame = line->data;
4741                 blame->commit = commit;
4742                 blame->lineno = orig_lineno + group - 1;
4743                 line->dirty = 1;
4744         }
4746         return commit;
4749 static bool
4750 blame_read_file(struct view *view, const char *line, bool *read_file)
4752         if (!line) {
4753                 const char **argv = *opt_ref ? blame_ref_argv : blame_head_argv;
4754                 struct io io = {};
4756                 if (view->lines == 0 && !view->parent)
4757                         die("No blame exist for %s", view->vid);
4759                 if (view->lines == 0 || !run_io_rd(&io, argv, opt_cdup, FORMAT_ALL)) {
4760                         report("Failed to load blame data");
4761                         return TRUE;
4762                 }
4764                 done_io(view->pipe);
4765                 view->io = io;
4766                 *read_file = FALSE;
4767                 return FALSE;
4769         } else {
4770                 size_t linelen = strlen(line);
4771                 struct blame *blame = malloc(sizeof(*blame) + linelen);
4773                 if (!blame)
4774                         return FALSE;
4776                 blame->commit = NULL;
4777                 strncpy(blame->text, line, linelen);
4778                 blame->text[linelen] = 0;
4779                 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
4780         }
4783 static bool
4784 match_blame_header(const char *name, char **line)
4786         size_t namelen = strlen(name);
4787         bool matched = !strncmp(name, *line, namelen);
4789         if (matched)
4790                 *line += namelen;
4792         return matched;
4795 static bool
4796 blame_read(struct view *view, char *line)
4798         static struct blame_commit *commit = NULL;
4799         static int blamed = 0;
4800         static bool read_file = TRUE;
4802         if (read_file)
4803                 return blame_read_file(view, line, &read_file);
4805         if (!line) {
4806                 /* Reset all! */
4807                 commit = NULL;
4808                 blamed = 0;
4809                 read_file = TRUE;
4810                 string_format(view->ref, "%s", view->vid);
4811                 if (view_is_displayed(view)) {
4812                         update_view_title(view);
4813                         redraw_view_from(view, 0);
4814                 }
4815                 return TRUE;
4816         }
4818         if (!commit) {
4819                 commit = parse_blame_commit(view, line, &blamed);
4820                 string_format(view->ref, "%s %2d%%", view->vid,
4821                               view->lines ? blamed * 100 / view->lines : 0);
4823         } else if (match_blame_header("author ", &line)) {
4824                 commit->author = get_author(line);
4826         } else if (match_blame_header("author-time ", &line)) {
4827                 commit->time = (time_t) atol(line);
4829         } else if (match_blame_header("author-tz ", &line)) {
4830                 parse_timezone(&commit->time, line);
4832         } else if (match_blame_header("summary ", &line)) {
4833                 string_ncopy(commit->title, line, strlen(line));
4835         } else if (match_blame_header("previous ", &line)) {
4836                 commit->has_previous = TRUE;
4838         } else if (match_blame_header("filename ", &line)) {
4839                 string_ncopy(commit->filename, line, strlen(line));
4840                 commit = NULL;
4841         }
4843         return TRUE;
4846 static bool
4847 blame_draw(struct view *view, struct line *line, unsigned int lineno)
4849         struct blame *blame = line->data;
4850         time_t *time = NULL;
4851         const char *id = NULL, *author = NULL;
4852         char text[SIZEOF_STR];
4854         if (blame->commit && *blame->commit->filename) {
4855                 id = blame->commit->id;
4856                 author = blame->commit->author;
4857                 time = &blame->commit->time;
4858         }
4860         if (opt_date && draw_date(view, time))
4861                 return TRUE;
4863         if (opt_author && draw_author(view, author))
4864                 return TRUE;
4866         if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
4867                 return TRUE;
4869         if (draw_lineno(view, lineno))
4870                 return TRUE;
4872         string_expand(text, sizeof(text), blame->text, opt_tab_size);
4873         draw_text(view, LINE_DEFAULT, text, TRUE);
4874         return TRUE;
4877 static bool
4878 check_blame_commit(struct blame *blame, bool check_null_id)
4880         if (!blame->commit)
4881                 report("Commit data not loaded yet");
4882         else if (check_null_id && !strcmp(blame->commit->id, NULL_ID))
4883                 report("No commit exist for the selected line");
4884         else
4885                 return TRUE;
4886         return FALSE;
4889 static void
4890 setup_blame_parent_line(struct view *view, struct blame *blame)
4892         const char *diff_tree_argv[] = {
4893                 "git", "diff-tree", "-U0", blame->commit->id,
4894                         "--", blame->commit->filename, NULL
4895         };
4896         struct io io = {};
4897         int parent_lineno = -1;
4898         int blamed_lineno = -1;
4899         char *line;
4901         if (!run_io(&io, diff_tree_argv, NULL, IO_RD))
4902                 return;
4904         while ((line = io_get(&io, '\n', TRUE))) {
4905                 if (*line == '@') {
4906                         char *pos = strchr(line, '+');
4908                         parent_lineno = atoi(line + 4);
4909                         if (pos)
4910                                 blamed_lineno = atoi(pos + 1);
4912                 } else if (*line == '+' && parent_lineno != -1) {
4913                         if (blame->lineno == blamed_lineno - 1 &&
4914                             !strcmp(blame->text, line + 1)) {
4915                                 view->lineno = parent_lineno ? parent_lineno - 1 : 0;
4916                                 break;
4917                         }
4918                         blamed_lineno++;
4919                 }
4920         }
4922         done_io(&io);
4925 static enum request
4926 blame_request(struct view *view, enum request request, struct line *line)
4928         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4929         struct blame *blame = line->data;
4931         switch (request) {
4932         case REQ_VIEW_BLAME:
4933                 if (check_blame_commit(blame, TRUE)) {
4934                         string_copy(opt_ref, blame->commit->id);
4935                         string_copy(opt_file, blame->commit->filename);
4936                         if (blame->lineno)
4937                                 view->lineno = blame->lineno;
4938                         open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4939                 }
4940                 break;
4942         case REQ_PARENT:
4943                 if (check_blame_commit(blame, TRUE) &&
4944                     select_commit_parent(blame->commit->id, opt_ref,
4945                                          blame->commit->filename)) {
4946                         string_copy(opt_file, blame->commit->filename);
4947                         setup_blame_parent_line(view, blame);
4948                         open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4949                 }
4950                 break;
4952         case REQ_ENTER:
4953                 if (!check_blame_commit(blame, FALSE))
4954                         break;
4956                 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
4957                     !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
4958                         break;
4960                 if (!strcmp(blame->commit->id, NULL_ID)) {
4961                         struct view *diff = VIEW(REQ_VIEW_DIFF);
4962                         const char *diff_index_argv[] = {
4963                                 "git", "diff-index", "--root", "--patch-with-stat",
4964                                         "-C", "-M", "HEAD", "--", view->vid, NULL
4965                         };
4967                         if (!blame->commit->has_previous) {
4968                                 diff_index_argv[1] = "diff";
4969                                 diff_index_argv[2] = "--no-color";
4970                                 diff_index_argv[6] = "--";
4971                                 diff_index_argv[7] = "/dev/null";
4972                         }
4974                         if (!prepare_update(diff, diff_index_argv, NULL, FORMAT_DASH)) {
4975                                 report("Failed to allocate diff command");
4976                                 break;
4977                         }
4978                         flags |= OPEN_PREPARED;
4979                 }
4981                 open_view(view, REQ_VIEW_DIFF, flags);
4982                 if (VIEW(REQ_VIEW_DIFF)->pipe && !strcmp(blame->commit->id, NULL_ID))
4983                         string_copy_rev(VIEW(REQ_VIEW_DIFF)->ref, NULL_ID);
4984                 break;
4986         default:
4987                 return request;
4988         }
4990         return REQ_NONE;
4993 static bool
4994 blame_grep(struct view *view, struct line *line)
4996         struct blame *blame = line->data;
4997         struct blame_commit *commit = blame->commit;
4998         const char *text[] = {
4999                 blame->text,
5000                 commit ? commit->title : "",
5001                 commit ? commit->id : "",
5002                 commit && opt_author ? commit->author : "",
5003                 commit && opt_date ? mkdate(&commit->time) : "",
5004                 NULL
5005         };
5007         return grep_text(view, text);
5010 static void
5011 blame_select(struct view *view, struct line *line)
5013         struct blame *blame = line->data;
5014         struct blame_commit *commit = blame->commit;
5016         if (!commit)
5017                 return;
5019         if (!strcmp(commit->id, NULL_ID))
5020                 string_ncopy(ref_commit, "HEAD", 4);
5021         else
5022                 string_copy_rev(ref_commit, commit->id);
5025 static struct view_ops blame_ops = {
5026         "line",
5027         NULL,
5028         blame_open,
5029         blame_read,
5030         blame_draw,
5031         blame_request,
5032         blame_grep,
5033         blame_select,
5034 };
5036 /*
5037  * Branch backend
5038  */
5040 struct branch {
5041         const char *author;             /* Author of the last commit. */
5042         time_t time;                    /* Date of the last activity. */
5043         const struct ref *ref;          /* Name and commit ID information. */
5044 };
5046 static const struct ref branch_all;
5048 static const enum sort_field branch_sort_fields[] = {
5049         ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
5050 };
5051 static struct sort_state branch_sort_state = SORT_STATE(branch_sort_fields);
5053 static int
5054 branch_compare(const void *l1, const void *l2)
5056         const struct branch *branch1 = ((const struct line *) l1)->data;
5057         const struct branch *branch2 = ((const struct line *) l2)->data;
5059         switch (get_sort_field(branch_sort_state)) {
5060         case ORDERBY_DATE:
5061                 return sort_order(branch_sort_state, branch1->time - branch2->time);
5063         case ORDERBY_AUTHOR:
5064                 return sort_order(branch_sort_state, strcmp(branch1->author, branch2->author));
5066         case ORDERBY_NAME:
5067         default:
5068                 return sort_order(branch_sort_state, strcmp(branch1->ref->name, branch2->ref->name));
5069         }
5072 static bool
5073 branch_draw(struct view *view, struct line *line, unsigned int lineno)
5075         struct branch *branch = line->data;
5076         enum line_type type = branch->ref->head ? LINE_MAIN_HEAD : LINE_DEFAULT;
5078         if (opt_date && draw_date(view, branch->author ? &branch->time : NULL))
5079                 return TRUE;
5081         if (opt_author && draw_author(view, branch->author))
5082                 return TRUE;
5084         draw_text(view, type, branch->ref == &branch_all ? "All branches" : branch->ref->name, TRUE);
5085         return TRUE;
5088 static enum request
5089 branch_request(struct view *view, enum request request, struct line *line)
5091         struct branch *branch = line->data;
5093         switch (request) {
5094         case REQ_REFRESH:
5095                 load_refs();
5096                 open_view(view, REQ_VIEW_BRANCH, OPEN_REFRESH);
5097                 return REQ_NONE;
5099         case REQ_TOGGLE_SORT_FIELD:
5100         case REQ_TOGGLE_SORT_ORDER:
5101                 sort_view(view, request, &branch_sort_state, branch_compare);
5102                 return REQ_NONE;
5104         case REQ_ENTER:
5105                 if (branch->ref == &branch_all) {
5106                         const char *all_branches_argv[] = {
5107                                 "git", "log", "--no-color", "--pretty=raw", "--parents",
5108                                       "--topo-order", "--all", NULL
5109                         };
5110                         struct view *main_view = VIEW(REQ_VIEW_MAIN);
5112                         if (!prepare_update(main_view, all_branches_argv, NULL, FORMAT_NONE)) {
5113                                 report("Failed to load view of all branches");
5114                                 return REQ_NONE;
5115                         }
5116                         open_view(view, REQ_VIEW_MAIN, OPEN_PREPARED | OPEN_SPLIT);
5117                 } else {
5118                         open_view(view, REQ_VIEW_MAIN, OPEN_SPLIT);
5119                 }
5120                 return REQ_NONE;
5122         default:
5123                 return request;
5124         }
5127 static bool
5128 branch_read(struct view *view, char *line)
5130         static char id[SIZEOF_REV];
5131         struct branch *reference;
5132         size_t i;
5134         if (!line)
5135                 return TRUE;
5137         switch (get_line_type(line)) {
5138         case LINE_COMMIT:
5139                 string_copy_rev(id, line + STRING_SIZE("commit "));
5140                 return TRUE;
5142         case LINE_AUTHOR:
5143                 for (i = 0, reference = NULL; i < view->lines; i++) {
5144                         struct branch *branch = view->line[i].data;
5146                         if (strcmp(branch->ref->id, id))
5147                                 continue;
5149                         view->line[i].dirty = TRUE;
5150                         if (reference) {
5151                                 branch->author = reference->author;
5152                                 branch->time = reference->time;
5153                                 continue;
5154                         }
5156                         parse_author_line(line + STRING_SIZE("author "),
5157                                           &branch->author, &branch->time);
5158                         reference = branch;
5159                 }
5160                 return TRUE;
5162         default:
5163                 return TRUE;
5164         }
5168 static bool
5169 branch_open_visitor(void *data, const struct ref *ref)
5171         struct view *view = data;
5172         struct branch *branch;
5174         if (ref->tag || ref->ltag || ref->remote)
5175                 return TRUE;
5177         branch = calloc(1, sizeof(*branch));
5178         if (!branch)
5179                 return FALSE;
5181         branch->ref = ref;
5182         return !!add_line_data(view, branch, LINE_DEFAULT);
5185 static bool
5186 branch_open(struct view *view)
5188         const char *branch_log[] = {
5189                 "git", "log", "--no-color", "--pretty=raw",
5190                         "--simplify-by-decoration", "--all", NULL
5191         };
5193         if (!run_io_rd(&view->io, branch_log, NULL, FORMAT_NONE)) {
5194                 report("Failed to load branch data");
5195                 return TRUE;
5196         }
5198         setup_update(view, view->id);
5199         branch_open_visitor(view, &branch_all);
5200         foreach_ref(branch_open_visitor, view);
5201         view->p_restore = TRUE;
5203         return TRUE;
5206 static bool
5207 branch_grep(struct view *view, struct line *line)
5209         struct branch *branch = line->data;
5210         const char *text[] = {
5211                 branch->ref->name,
5212                 branch->author,
5213                 NULL
5214         };
5216         return grep_text(view, text);
5219 static void
5220 branch_select(struct view *view, struct line *line)
5222         struct branch *branch = line->data;
5224         string_copy_rev(view->ref, branch->ref->id);
5225         string_copy_rev(ref_commit, branch->ref->id);
5226         string_copy_rev(ref_head, branch->ref->id);
5229 static struct view_ops branch_ops = {
5230         "branch",
5231         NULL,
5232         branch_open,
5233         branch_read,
5234         branch_draw,
5235         branch_request,
5236         branch_grep,
5237         branch_select,
5238 };
5240 /*
5241  * Status backend
5242  */
5244 struct status {
5245         char status;
5246         struct {
5247                 mode_t mode;
5248                 char rev[SIZEOF_REV];
5249                 char name[SIZEOF_STR];
5250         } old;
5251         struct {
5252                 mode_t mode;
5253                 char rev[SIZEOF_REV];
5254                 char name[SIZEOF_STR];
5255         } new;
5256 };
5258 static char status_onbranch[SIZEOF_STR];
5259 static struct status stage_status;
5260 static enum line_type stage_line_type;
5261 static size_t stage_chunks;
5262 static int *stage_chunk;
5264 DEFINE_ALLOCATOR(realloc_ints, int, 32)
5266 /* This should work even for the "On branch" line. */
5267 static inline bool
5268 status_has_none(struct view *view, struct line *line)
5270         return line < view->line + view->lines && !line[1].data;
5273 /* Get fields from the diff line:
5274  * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
5275  */
5276 static inline bool
5277 status_get_diff(struct status *file, const char *buf, size_t bufsize)
5279         const char *old_mode = buf +  1;
5280         const char *new_mode = buf +  8;
5281         const char *old_rev  = buf + 15;
5282         const char *new_rev  = buf + 56;
5283         const char *status   = buf + 97;
5285         if (bufsize < 98 ||
5286             old_mode[-1] != ':' ||
5287             new_mode[-1] != ' ' ||
5288             old_rev[-1]  != ' ' ||
5289             new_rev[-1]  != ' ' ||
5290             status[-1]   != ' ')
5291                 return FALSE;
5293         file->status = *status;
5295         string_copy_rev(file->old.rev, old_rev);
5296         string_copy_rev(file->new.rev, new_rev);
5298         file->old.mode = strtoul(old_mode, NULL, 8);
5299         file->new.mode = strtoul(new_mode, NULL, 8);
5301         file->old.name[0] = file->new.name[0] = 0;
5303         return TRUE;
5306 static bool
5307 status_run(struct view *view, const char *argv[], char status, enum line_type type)
5309         struct status *unmerged = NULL;
5310         char *buf;
5311         struct io io = {};
5313         if (!run_io(&io, argv, opt_cdup, IO_RD))
5314                 return FALSE;
5316         add_line_data(view, NULL, type);
5318         while ((buf = io_get(&io, 0, TRUE))) {
5319                 struct status *file = unmerged;
5321                 if (!file) {
5322                         file = calloc(1, sizeof(*file));
5323                         if (!file || !add_line_data(view, file, type))
5324                                 goto error_out;
5325                 }
5327                 /* Parse diff info part. */
5328                 if (status) {
5329                         file->status = status;
5330                         if (status == 'A')
5331                                 string_copy(file->old.rev, NULL_ID);
5333                 } else if (!file->status || file == unmerged) {
5334                         if (!status_get_diff(file, buf, strlen(buf)))
5335                                 goto error_out;
5337                         buf = io_get(&io, 0, TRUE);
5338                         if (!buf)
5339                                 break;
5341                         /* Collapse all modified entries that follow an
5342                          * associated unmerged entry. */
5343                         if (unmerged == file) {
5344                                 unmerged->status = 'U';
5345                                 unmerged = NULL;
5346                         } else if (file->status == 'U') {
5347                                 unmerged = file;
5348                         }
5349                 }
5351                 /* Grab the old name for rename/copy. */
5352                 if (!*file->old.name &&
5353                     (file->status == 'R' || file->status == 'C')) {
5354                         string_ncopy(file->old.name, buf, strlen(buf));
5356                         buf = io_get(&io, 0, TRUE);
5357                         if (!buf)
5358                                 break;
5359                 }
5361                 /* git-ls-files just delivers a NUL separated list of
5362                  * file names similar to the second half of the
5363                  * git-diff-* output. */
5364                 string_ncopy(file->new.name, buf, strlen(buf));
5365                 if (!*file->old.name)
5366                         string_copy(file->old.name, file->new.name);
5367                 file = NULL;
5368         }
5370         if (io_error(&io)) {
5371 error_out:
5372                 done_io(&io);
5373                 return FALSE;
5374         }
5376         if (!view->line[view->lines - 1].data)
5377                 add_line_data(view, NULL, LINE_STAT_NONE);
5379         done_io(&io);
5380         return TRUE;
5383 /* Don't show unmerged entries in the staged section. */
5384 static const char *status_diff_index_argv[] = {
5385         "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
5386                              "--cached", "-M", "HEAD", NULL
5387 };
5389 static const char *status_diff_files_argv[] = {
5390         "git", "diff-files", "-z", NULL
5391 };
5393 static const char *status_list_other_argv[] = {
5394         "git", "ls-files", "-z", "--others", "--exclude-standard", NULL
5395 };
5397 static const char *status_list_no_head_argv[] = {
5398         "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
5399 };
5401 static const char *update_index_argv[] = {
5402         "git", "update-index", "-q", "--unmerged", "--refresh", NULL
5403 };
5405 /* Restore the previous line number to stay in the context or select a
5406  * line with something that can be updated. */
5407 static void
5408 status_restore(struct view *view)
5410         if (view->p_lineno >= view->lines)
5411                 view->p_lineno = view->lines - 1;
5412         while (view->p_lineno < view->lines && !view->line[view->p_lineno].data)
5413                 view->p_lineno++;
5414         while (view->p_lineno > 0 && !view->line[view->p_lineno].data)
5415                 view->p_lineno--;
5417         /* If the above fails, always skip the "On branch" line. */
5418         if (view->p_lineno < view->lines)
5419                 view->lineno = view->p_lineno;
5420         else
5421                 view->lineno = 1;
5423         if (view->lineno < view->offset)
5424                 view->offset = view->lineno;
5425         else if (view->offset + view->height <= view->lineno)
5426                 view->offset = view->lineno - view->height + 1;
5428         view->p_restore = FALSE;
5431 static void
5432 status_update_onbranch(void)
5434         static const char *paths[][2] = {
5435                 { "rebase-apply/rebasing",      "Rebasing" },
5436                 { "rebase-apply/applying",      "Applying mailbox" },
5437                 { "rebase-apply/",              "Rebasing mailbox" },
5438                 { "rebase-merge/interactive",   "Interactive rebase" },
5439                 { "rebase-merge/",              "Rebase merge" },
5440                 { "MERGE_HEAD",                 "Merging" },
5441                 { "BISECT_LOG",                 "Bisecting" },
5442                 { "HEAD",                       "On branch" },
5443         };
5444         char buf[SIZEOF_STR];
5445         struct stat stat;
5446         int i;
5448         if (is_initial_commit()) {
5449                 string_copy(status_onbranch, "Initial commit");
5450                 return;
5451         }
5453         for (i = 0; i < ARRAY_SIZE(paths); i++) {
5454                 char *head = opt_head;
5456                 if (!string_format(buf, "%s/%s", opt_git_dir, paths[i][0]) ||
5457                     lstat(buf, &stat) < 0)
5458                         continue;
5460                 if (!*opt_head) {
5461                         struct io io = {};
5463                         if (io_open(&io, "%s/rebase-merge/head-name", opt_git_dir) &&
5464                             io_read_buf(&io, buf, sizeof(buf))) {
5465                                 head = buf;
5466                                 if (!prefixcmp(head, "refs/heads/"))
5467                                         head += STRING_SIZE("refs/heads/");
5468                         }
5469                 }
5471                 if (!string_format(status_onbranch, "%s %s", paths[i][1], head))
5472                         string_copy(status_onbranch, opt_head);
5473                 return;
5474         }
5476         string_copy(status_onbranch, "Not currently on any branch");
5479 /* First parse staged info using git-diff-index(1), then parse unstaged
5480  * info using git-diff-files(1), and finally untracked files using
5481  * git-ls-files(1). */
5482 static bool
5483 status_open(struct view *view)
5485         reset_view(view);
5487         add_line_data(view, NULL, LINE_STAT_HEAD);
5488         status_update_onbranch();
5490         run_io_bg(update_index_argv);
5492         if (is_initial_commit()) {
5493                 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
5494                         return FALSE;
5495         } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
5496                 return FALSE;
5497         }
5499         if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
5500             !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
5501                 return FALSE;
5503         /* Restore the exact position or use the specialized restore
5504          * mode? */
5505         if (!view->p_restore)
5506                 status_restore(view);
5507         return TRUE;
5510 static bool
5511 status_draw(struct view *view, struct line *line, unsigned int lineno)
5513         struct status *status = line->data;
5514         enum line_type type;
5515         const char *text;
5517         if (!status) {
5518                 switch (line->type) {
5519                 case LINE_STAT_STAGED:
5520                         type = LINE_STAT_SECTION;
5521                         text = "Changes to be committed:";
5522                         break;
5524                 case LINE_STAT_UNSTAGED:
5525                         type = LINE_STAT_SECTION;
5526                         text = "Changed but not updated:";
5527                         break;
5529                 case LINE_STAT_UNTRACKED:
5530                         type = LINE_STAT_SECTION;
5531                         text = "Untracked files:";
5532                         break;
5534                 case LINE_STAT_NONE:
5535                         type = LINE_DEFAULT;
5536                         text = "  (no files)";
5537                         break;
5539                 case LINE_STAT_HEAD:
5540                         type = LINE_STAT_HEAD;
5541                         text = status_onbranch;
5542                         break;
5544                 default:
5545                         return FALSE;
5546                 }
5547         } else {
5548                 static char buf[] = { '?', ' ', ' ', ' ', 0 };
5550                 buf[0] = status->status;
5551                 if (draw_text(view, line->type, buf, TRUE))
5552                         return TRUE;
5553                 type = LINE_DEFAULT;
5554                 text = status->new.name;
5555         }
5557         draw_text(view, type, text, TRUE);
5558         return TRUE;
5561 static enum request
5562 status_load_error(struct view *view, struct view *stage, const char *path)
5564         if (displayed_views() == 2 || display[current_view] != view)
5565                 maximize_view(view);
5566         report("Failed to load '%s': %s", path, io_strerror(&stage->io));
5567         return REQ_NONE;
5570 static enum request
5571 status_enter(struct view *view, struct line *line)
5573         struct status *status = line->data;
5574         const char *oldpath = status ? status->old.name : NULL;
5575         /* Diffs for unmerged entries are empty when passing the new
5576          * path, so leave it empty. */
5577         const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
5578         const char *info;
5579         enum open_flags split;
5580         struct view *stage = VIEW(REQ_VIEW_STAGE);
5582         if (line->type == LINE_STAT_NONE ||
5583             (!status && line[1].type == LINE_STAT_NONE)) {
5584                 report("No file to diff");
5585                 return REQ_NONE;
5586         }
5588         switch (line->type) {
5589         case LINE_STAT_STAGED:
5590                 if (is_initial_commit()) {
5591                         const char *no_head_diff_argv[] = {
5592                                 "git", "diff", "--no-color", "--patch-with-stat",
5593                                         "--", "/dev/null", newpath, NULL
5594                         };
5596                         if (!prepare_update(stage, no_head_diff_argv, opt_cdup, FORMAT_DASH))
5597                                 return status_load_error(view, stage, newpath);
5598                 } else {
5599                         const char *index_show_argv[] = {
5600                                 "git", "diff-index", "--root", "--patch-with-stat",
5601                                         "-C", "-M", "--cached", "HEAD", "--",
5602                                         oldpath, newpath, NULL
5603                         };
5605                         if (!prepare_update(stage, index_show_argv, opt_cdup, FORMAT_DASH))
5606                                 return status_load_error(view, stage, newpath);
5607                 }
5609                 if (status)
5610                         info = "Staged changes to %s";
5611                 else
5612                         info = "Staged changes";
5613                 break;
5615         case LINE_STAT_UNSTAGED:
5616         {
5617                 const char *files_show_argv[] = {
5618                         "git", "diff-files", "--root", "--patch-with-stat",
5619                                 "-C", "-M", "--", oldpath, newpath, NULL
5620                 };
5622                 if (!prepare_update(stage, files_show_argv, opt_cdup, FORMAT_DASH))
5623                         return status_load_error(view, stage, newpath);
5624                 if (status)
5625                         info = "Unstaged changes to %s";
5626                 else
5627                         info = "Unstaged changes";
5628                 break;
5629         }
5630         case LINE_STAT_UNTRACKED:
5631                 if (!newpath) {
5632                         report("No file to show");
5633                         return REQ_NONE;
5634                 }
5636                 if (!suffixcmp(status->new.name, -1, "/")) {
5637                         report("Cannot display a directory");
5638                         return REQ_NONE;
5639                 }
5641                 if (!prepare_update_file(stage, newpath))
5642                         return status_load_error(view, stage, newpath);
5643                 info = "Untracked file %s";
5644                 break;
5646         case LINE_STAT_HEAD:
5647                 return REQ_NONE;
5649         default:
5650                 die("line type %d not handled in switch", line->type);
5651         }
5653         split = view_is_displayed(view) ? OPEN_SPLIT : 0;
5654         open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split);
5655         if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
5656                 if (status) {
5657                         stage_status = *status;
5658                 } else {
5659                         memset(&stage_status, 0, sizeof(stage_status));
5660                 }
5662                 stage_line_type = line->type;
5663                 stage_chunks = 0;
5664                 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
5665         }
5667         return REQ_NONE;
5670 static bool
5671 status_exists(struct status *status, enum line_type type)
5673         struct view *view = VIEW(REQ_VIEW_STATUS);
5674         unsigned long lineno;
5676         for (lineno = 0; lineno < view->lines; lineno++) {
5677                 struct line *line = &view->line[lineno];
5678                 struct status *pos = line->data;
5680                 if (line->type != type)
5681                         continue;
5682                 if (!pos && (!status || !status->status) && line[1].data) {
5683                         select_view_line(view, lineno);
5684                         return TRUE;
5685                 }
5686                 if (pos && !strcmp(status->new.name, pos->new.name)) {
5687                         select_view_line(view, lineno);
5688                         return TRUE;
5689                 }
5690         }
5692         return FALSE;
5696 static bool
5697 status_update_prepare(struct io *io, enum line_type type)
5699         const char *staged_argv[] = {
5700                 "git", "update-index", "-z", "--index-info", NULL
5701         };
5702         const char *others_argv[] = {
5703                 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
5704         };
5706         switch (type) {
5707         case LINE_STAT_STAGED:
5708                 return run_io(io, staged_argv, opt_cdup, IO_WR);
5710         case LINE_STAT_UNSTAGED:
5711         case LINE_STAT_UNTRACKED:
5712                 return run_io(io, others_argv, opt_cdup, IO_WR);
5714         default:
5715                 die("line type %d not handled in switch", type);
5716                 return FALSE;
5717         }
5720 static bool
5721 status_update_write(struct io *io, struct status *status, enum line_type type)
5723         char buf[SIZEOF_STR];
5724         size_t bufsize = 0;
5726         switch (type) {
5727         case LINE_STAT_STAGED:
5728                 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
5729                                         status->old.mode,
5730                                         status->old.rev,
5731                                         status->old.name, 0))
5732                         return FALSE;
5733                 break;
5735         case LINE_STAT_UNSTAGED:
5736         case LINE_STAT_UNTRACKED:
5737                 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
5738                         return FALSE;
5739                 break;
5741         default:
5742                 die("line type %d not handled in switch", type);
5743         }
5745         return io_write(io, buf, bufsize);
5748 static bool
5749 status_update_file(struct status *status, enum line_type type)
5751         struct io io = {};
5752         bool result;
5754         if (!status_update_prepare(&io, type))
5755                 return FALSE;
5757         result = status_update_write(&io, status, type);
5758         return done_io(&io) && result;
5761 static bool
5762 status_update_files(struct view *view, struct line *line)
5764         char buf[sizeof(view->ref)];
5765         struct io io = {};
5766         bool result = TRUE;
5767         struct line *pos = view->line + view->lines;
5768         int files = 0;
5769         int file, done;
5770         int cursor_y = -1, cursor_x = -1;
5772         if (!status_update_prepare(&io, line->type))
5773                 return FALSE;
5775         for (pos = line; pos < view->line + view->lines && pos->data; pos++)
5776                 files++;
5778         string_copy(buf, view->ref);
5779         getsyx(cursor_y, cursor_x);
5780         for (file = 0, done = 5; result && file < files; line++, file++) {
5781                 int almost_done = file * 100 / files;
5783                 if (almost_done > done) {
5784                         done = almost_done;
5785                         string_format(view->ref, "updating file %u of %u (%d%% done)",
5786                                       file, files, done);
5787                         update_view_title(view);
5788                         setsyx(cursor_y, cursor_x);
5789                         doupdate();
5790                 }
5791                 result = status_update_write(&io, line->data, line->type);
5792         }
5793         string_copy(view->ref, buf);
5795         return done_io(&io) && result;
5798 static bool
5799 status_update(struct view *view)
5801         struct line *line = &view->line[view->lineno];
5803         assert(view->lines);
5805         if (!line->data) {
5806                 /* This should work even for the "On branch" line. */
5807                 if (line < view->line + view->lines && !line[1].data) {
5808                         report("Nothing to update");
5809                         return FALSE;
5810                 }
5812                 if (!status_update_files(view, line + 1)) {
5813                         report("Failed to update file status");
5814                         return FALSE;
5815                 }
5817         } else if (!status_update_file(line->data, line->type)) {
5818                 report("Failed to update file status");
5819                 return FALSE;
5820         }
5822         return TRUE;
5825 static bool
5826 status_revert(struct status *status, enum line_type type, bool has_none)
5828         if (!status || type != LINE_STAT_UNSTAGED) {
5829                 if (type == LINE_STAT_STAGED) {
5830                         report("Cannot revert changes to staged files");
5831                 } else if (type == LINE_STAT_UNTRACKED) {
5832                         report("Cannot revert changes to untracked files");
5833                 } else if (has_none) {
5834                         report("Nothing to revert");
5835                 } else {
5836                         report("Cannot revert changes to multiple files");
5837                 }
5839         } else if (prompt_yesno("Are you sure you want to revert changes?")) {
5840                 char mode[10] = "100644";
5841                 const char *reset_argv[] = {
5842                         "git", "update-index", "--cacheinfo", mode,
5843                                 status->old.rev, status->old.name, NULL
5844                 };
5845                 const char *checkout_argv[] = {
5846                         "git", "checkout", "--", status->old.name, NULL
5847                 };
5849                 if (status->status == 'U') {
5850                         string_format(mode, "%5o", status->old.mode);
5852                         if (status->old.mode == 0 && status->new.mode == 0) {
5853                                 reset_argv[2] = "--force-remove";
5854                                 reset_argv[3] = status->old.name;
5855                                 reset_argv[4] = NULL;
5856                         }
5858                         if (!run_io_fg(reset_argv, opt_cdup))
5859                                 return FALSE;
5860                         if (status->old.mode == 0 && status->new.mode == 0)
5861                                 return TRUE;
5862                 }
5864                 return run_io_fg(checkout_argv, opt_cdup);
5865         }
5867         return FALSE;
5870 static enum request
5871 status_request(struct view *view, enum request request, struct line *line)
5873         struct status *status = line->data;
5875         switch (request) {
5876         case REQ_STATUS_UPDATE:
5877                 if (!status_update(view))
5878                         return REQ_NONE;
5879                 break;
5881         case REQ_STATUS_REVERT:
5882                 if (!status_revert(status, line->type, status_has_none(view, line)))
5883                         return REQ_NONE;
5884                 break;
5886         case REQ_STATUS_MERGE:
5887                 if (!status || status->status != 'U') {
5888                         report("Merging only possible for files with unmerged status ('U').");
5889                         return REQ_NONE;
5890                 }
5891                 open_mergetool(status->new.name);
5892                 break;
5894         case REQ_EDIT:
5895                 if (!status)
5896                         return request;
5897                 if (status->status == 'D') {
5898                         report("File has been deleted.");
5899                         return REQ_NONE;
5900                 }
5902                 open_editor(status->status != '?', status->new.name);
5903                 break;
5905         case REQ_VIEW_BLAME:
5906                 if (status)
5907                         opt_ref[0] = 0;
5908                 return request;
5910         case REQ_ENTER:
5911                 /* After returning the status view has been split to
5912                  * show the stage view. No further reloading is
5913                  * necessary. */
5914                 return status_enter(view, line);
5916         case REQ_REFRESH:
5917                 /* Simply reload the view. */
5918                 break;
5920         default:
5921                 return request;
5922         }
5924         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
5926         return REQ_NONE;
5929 static void
5930 status_select(struct view *view, struct line *line)
5932         struct status *status = line->data;
5933         char file[SIZEOF_STR] = "all files";
5934         const char *text;
5935         const char *key;
5937         if (status && !string_format(file, "'%s'", status->new.name))
5938                 return;
5940         if (!status && line[1].type == LINE_STAT_NONE)
5941                 line++;
5943         switch (line->type) {
5944         case LINE_STAT_STAGED:
5945                 text = "Press %s to unstage %s for commit";
5946                 break;
5948         case LINE_STAT_UNSTAGED:
5949                 text = "Press %s to stage %s for commit";
5950                 break;
5952         case LINE_STAT_UNTRACKED:
5953                 text = "Press %s to stage %s for addition";
5954                 break;
5956         case LINE_STAT_HEAD:
5957         case LINE_STAT_NONE:
5958                 text = "Nothing to update";
5959                 break;
5961         default:
5962                 die("line type %d not handled in switch", line->type);
5963         }
5965         if (status && status->status == 'U') {
5966                 text = "Press %s to resolve conflict in %s";
5967                 key = get_key(KEYMAP_STATUS, REQ_STATUS_MERGE);
5969         } else {
5970                 key = get_key(KEYMAP_STATUS, REQ_STATUS_UPDATE);
5971         }
5973         string_format(view->ref, text, key, file);
5974         if (status)
5975                 string_copy(opt_file, status->new.name);
5978 static bool
5979 status_grep(struct view *view, struct line *line)
5981         struct status *status = line->data;
5983         if (status) {
5984                 const char buf[2] = { status->status, 0 };
5985                 const char *text[] = { status->new.name, buf, NULL };
5987                 return grep_text(view, text);
5988         }
5990         return FALSE;
5993 static struct view_ops status_ops = {
5994         "file",
5995         NULL,
5996         status_open,
5997         NULL,
5998         status_draw,
5999         status_request,
6000         status_grep,
6001         status_select,
6002 };
6005 static bool
6006 stage_diff_write(struct io *io, struct line *line, struct line *end)
6008         while (line < end) {
6009                 if (!io_write(io, line->data, strlen(line->data)) ||
6010                     !io_write(io, "\n", 1))
6011                         return FALSE;
6012                 line++;
6013                 if (line->type == LINE_DIFF_CHUNK ||
6014                     line->type == LINE_DIFF_HEADER)
6015                         break;
6016         }
6018         return TRUE;
6021 static struct line *
6022 stage_diff_find(struct view *view, struct line *line, enum line_type type)
6024         for (; view->line < line; line--)
6025                 if (line->type == type)
6026                         return line;
6028         return NULL;
6031 static bool
6032 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
6034         const char *apply_argv[SIZEOF_ARG] = {
6035                 "git", "apply", "--whitespace=nowarn", NULL
6036         };
6037         struct line *diff_hdr;
6038         struct io io = {};
6039         int argc = 3;
6041         diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
6042         if (!diff_hdr)
6043                 return FALSE;
6045         if (!revert)
6046                 apply_argv[argc++] = "--cached";
6047         if (revert || stage_line_type == LINE_STAT_STAGED)
6048                 apply_argv[argc++] = "-R";
6049         apply_argv[argc++] = "-";
6050         apply_argv[argc++] = NULL;
6051         if (!run_io(&io, apply_argv, opt_cdup, IO_WR))
6052                 return FALSE;
6054         if (!stage_diff_write(&io, diff_hdr, chunk) ||
6055             !stage_diff_write(&io, chunk, view->line + view->lines))
6056                 chunk = NULL;
6058         done_io(&io);
6059         run_io_bg(update_index_argv);
6061         return chunk ? TRUE : FALSE;
6064 static bool
6065 stage_update(struct view *view, struct line *line)
6067         struct line *chunk = NULL;
6069         if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
6070                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6072         if (chunk) {
6073                 if (!stage_apply_chunk(view, chunk, FALSE)) {
6074                         report("Failed to apply chunk");
6075                         return FALSE;
6076                 }
6078         } else if (!stage_status.status) {
6079                 view = VIEW(REQ_VIEW_STATUS);
6081                 for (line = view->line; line < view->line + view->lines; line++)
6082                         if (line->type == stage_line_type)
6083                                 break;
6085                 if (!status_update_files(view, line + 1)) {
6086                         report("Failed to update files");
6087                         return FALSE;
6088                 }
6090         } else if (!status_update_file(&stage_status, stage_line_type)) {
6091                 report("Failed to update file");
6092                 return FALSE;
6093         }
6095         return TRUE;
6098 static bool
6099 stage_revert(struct view *view, struct line *line)
6101         struct line *chunk = NULL;
6103         if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
6104                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6106         if (chunk) {
6107                 if (!prompt_yesno("Are you sure you want to revert changes?"))
6108                         return FALSE;
6110                 if (!stage_apply_chunk(view, chunk, TRUE)) {
6111                         report("Failed to revert chunk");
6112                         return FALSE;
6113                 }
6114                 return TRUE;
6116         } else {
6117                 return status_revert(stage_status.status ? &stage_status : NULL,
6118                                      stage_line_type, FALSE);
6119         }
6123 static void
6124 stage_next(struct view *view, struct line *line)
6126         int i;
6128         if (!stage_chunks) {
6129                 for (line = view->line; line < view->line + view->lines; line++) {
6130                         if (line->type != LINE_DIFF_CHUNK)
6131                                 continue;
6133                         if (!realloc_ints(&stage_chunk, stage_chunks, 1)) {
6134                                 report("Allocation failure");
6135                                 return;
6136                         }
6138                         stage_chunk[stage_chunks++] = line - view->line;
6139                 }
6140         }
6142         for (i = 0; i < stage_chunks; i++) {
6143                 if (stage_chunk[i] > view->lineno) {
6144                         do_scroll_view(view, stage_chunk[i] - view->lineno);
6145                         report("Chunk %d of %d", i + 1, stage_chunks);
6146                         return;
6147                 }
6148         }
6150         report("No next chunk found");
6153 static enum request
6154 stage_request(struct view *view, enum request request, struct line *line)
6156         switch (request) {
6157         case REQ_STATUS_UPDATE:
6158                 if (!stage_update(view, line))
6159                         return REQ_NONE;
6160                 break;
6162         case REQ_STATUS_REVERT:
6163                 if (!stage_revert(view, line))
6164                         return REQ_NONE;
6165                 break;
6167         case REQ_STAGE_NEXT:
6168                 if (stage_line_type == LINE_STAT_UNTRACKED) {
6169                         report("File is untracked; press %s to add",
6170                                get_key(KEYMAP_STAGE, REQ_STATUS_UPDATE));
6171                         return REQ_NONE;
6172                 }
6173                 stage_next(view, line);
6174                 return REQ_NONE;
6176         case REQ_EDIT:
6177                 if (!stage_status.new.name[0])
6178                         return request;
6179                 if (stage_status.status == 'D') {
6180                         report("File has been deleted.");
6181                         return REQ_NONE;
6182                 }
6184                 open_editor(stage_status.status != '?', stage_status.new.name);
6185                 break;
6187         case REQ_REFRESH:
6188                 /* Reload everything ... */
6189                 break;
6191         case REQ_VIEW_BLAME:
6192                 if (stage_status.new.name[0]) {
6193                         string_copy(opt_file, stage_status.new.name);
6194                         opt_ref[0] = 0;
6195                 }
6196                 return request;
6198         case REQ_ENTER:
6199                 return pager_request(view, request, line);
6201         default:
6202                 return request;
6203         }
6205         VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
6206         open_view(view, REQ_VIEW_STATUS, OPEN_REFRESH);
6208         /* Check whether the staged entry still exists, and close the
6209          * stage view if it doesn't. */
6210         if (!status_exists(&stage_status, stage_line_type)) {
6211                 status_restore(VIEW(REQ_VIEW_STATUS));
6212                 return REQ_VIEW_CLOSE;
6213         }
6215         if (stage_line_type == LINE_STAT_UNTRACKED) {
6216                 if (!suffixcmp(stage_status.new.name, -1, "/")) {
6217                         report("Cannot display a directory");
6218                         return REQ_NONE;
6219                 }
6221                 if (!prepare_update_file(view, stage_status.new.name)) {
6222                         report("Failed to open file: %s", strerror(errno));
6223                         return REQ_NONE;
6224                 }
6225         }
6226         open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
6228         return REQ_NONE;
6231 static struct view_ops stage_ops = {
6232         "line",
6233         NULL,
6234         NULL,
6235         pager_read,
6236         pager_draw,
6237         stage_request,
6238         pager_grep,
6239         pager_select,
6240 };
6243 /*
6244  * Revision graph
6245  */
6247 struct commit {
6248         char id[SIZEOF_REV];            /* SHA1 ID. */
6249         char title[128];                /* First line of the commit message. */
6250         const char *author;             /* Author of the commit. */
6251         time_t time;                    /* Date from the author ident. */
6252         struct ref_list *refs;          /* Repository references. */
6253         chtype graph[SIZEOF_REVGRAPH];  /* Ancestry chain graphics. */
6254         size_t graph_size;              /* The width of the graph array. */
6255         bool has_parents;               /* Rewritten --parents seen. */
6256 };
6258 /* Size of rev graph with no  "padding" columns */
6259 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
6261 struct rev_graph {
6262         struct rev_graph *prev, *next, *parents;
6263         char rev[SIZEOF_REVITEMS][SIZEOF_REV];
6264         size_t size;
6265         struct commit *commit;
6266         size_t pos;
6267         unsigned int boundary:1;
6268 };
6270 /* Parents of the commit being visualized. */
6271 static struct rev_graph graph_parents[4];
6273 /* The current stack of revisions on the graph. */
6274 static struct rev_graph graph_stacks[4] = {
6275         { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
6276         { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
6277         { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
6278         { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
6279 };
6281 static inline bool
6282 graph_parent_is_merge(struct rev_graph *graph)
6284         return graph->parents->size > 1;
6287 static inline void
6288 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
6290         struct commit *commit = graph->commit;
6292         if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
6293                 commit->graph[commit->graph_size++] = symbol;
6296 static void
6297 clear_rev_graph(struct rev_graph *graph)
6299         graph->boundary = 0;
6300         graph->size = graph->pos = 0;
6301         graph->commit = NULL;
6302         memset(graph->parents, 0, sizeof(*graph->parents));
6305 static void
6306 done_rev_graph(struct rev_graph *graph)
6308         if (graph_parent_is_merge(graph) &&
6309             graph->pos < graph->size - 1 &&
6310             graph->next->size == graph->size + graph->parents->size - 1) {
6311                 size_t i = graph->pos + graph->parents->size - 1;
6313                 graph->commit->graph_size = i * 2;
6314                 while (i < graph->next->size - 1) {
6315                         append_to_rev_graph(graph, ' ');
6316                         append_to_rev_graph(graph, '\\');
6317                         i++;
6318                 }
6319         }
6321         clear_rev_graph(graph);
6324 static void
6325 push_rev_graph(struct rev_graph *graph, const char *parent)
6327         int i;
6329         /* "Collapse" duplicate parents lines.
6330          *
6331          * FIXME: This needs to also update update the drawn graph but
6332          * for now it just serves as a method for pruning graph lines. */
6333         for (i = 0; i < graph->size; i++)
6334                 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
6335                         return;
6337         if (graph->size < SIZEOF_REVITEMS) {
6338                 string_copy_rev(graph->rev[graph->size++], parent);
6339         }
6342 static chtype
6343 get_rev_graph_symbol(struct rev_graph *graph)
6345         chtype symbol;
6347         if (graph->boundary)
6348                 symbol = REVGRAPH_BOUND;
6349         else if (graph->parents->size == 0)
6350                 symbol = REVGRAPH_INIT;
6351         else if (graph_parent_is_merge(graph))
6352                 symbol = REVGRAPH_MERGE;
6353         else if (graph->pos >= graph->size)
6354                 symbol = REVGRAPH_BRANCH;
6355         else
6356                 symbol = REVGRAPH_COMMIT;
6358         return symbol;
6361 static void
6362 draw_rev_graph(struct rev_graph *graph)
6364         struct rev_filler {
6365                 chtype separator, line;
6366         };
6367         enum { DEFAULT, RSHARP, RDIAG, LDIAG };
6368         static struct rev_filler fillers[] = {
6369                 { ' ',  '|' },
6370                 { '`',  '.' },
6371                 { '\'', ' ' },
6372                 { '/',  ' ' },
6373         };
6374         chtype symbol = get_rev_graph_symbol(graph);
6375         struct rev_filler *filler;
6376         size_t i;
6378         if (opt_line_graphics)
6379                 fillers[DEFAULT].line = line_graphics[LINE_GRAPHIC_VLINE];
6381         filler = &fillers[DEFAULT];
6383         for (i = 0; i < graph->pos; i++) {
6384                 append_to_rev_graph(graph, filler->line);
6385                 if (graph_parent_is_merge(graph->prev) &&
6386                     graph->prev->pos == i)
6387                         filler = &fillers[RSHARP];
6389                 append_to_rev_graph(graph, filler->separator);
6390         }
6392         /* Place the symbol for this revision. */
6393         append_to_rev_graph(graph, symbol);
6395         if (graph->prev->size > graph->size)
6396                 filler = &fillers[RDIAG];
6397         else
6398                 filler = &fillers[DEFAULT];
6400         i++;
6402         for (; i < graph->size; i++) {
6403                 append_to_rev_graph(graph, filler->separator);
6404                 append_to_rev_graph(graph, filler->line);
6405                 if (graph_parent_is_merge(graph->prev) &&
6406                     i < graph->prev->pos + graph->parents->size)
6407                         filler = &fillers[RSHARP];
6408                 if (graph->prev->size > graph->size)
6409                         filler = &fillers[LDIAG];
6410         }
6412         if (graph->prev->size > graph->size) {
6413                 append_to_rev_graph(graph, filler->separator);
6414                 if (filler->line != ' ')
6415                         append_to_rev_graph(graph, filler->line);
6416         }
6419 /* Prepare the next rev graph */
6420 static void
6421 prepare_rev_graph(struct rev_graph *graph)
6423         size_t i;
6425         /* First, traverse all lines of revisions up to the active one. */
6426         for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
6427                 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
6428                         break;
6430                 push_rev_graph(graph->next, graph->rev[graph->pos]);
6431         }
6433         /* Interleave the new revision parent(s). */
6434         for (i = 0; !graph->boundary && i < graph->parents->size; i++)
6435                 push_rev_graph(graph->next, graph->parents->rev[i]);
6437         /* Lastly, put any remaining revisions. */
6438         for (i = graph->pos + 1; i < graph->size; i++)
6439                 push_rev_graph(graph->next, graph->rev[i]);
6442 static void
6443 update_rev_graph(struct view *view, struct rev_graph *graph)
6445         /* If this is the finalizing update ... */
6446         if (graph->commit)
6447                 prepare_rev_graph(graph);
6449         /* Graph visualization needs a one rev look-ahead,
6450          * so the first update doesn't visualize anything. */
6451         if (!graph->prev->commit)
6452                 return;
6454         if (view->lines > 2)
6455                 view->line[view->lines - 3].dirty = 1;
6456         if (view->lines > 1)
6457                 view->line[view->lines - 2].dirty = 1;
6458         draw_rev_graph(graph->prev);
6459         done_rev_graph(graph->prev->prev);
6463 /*
6464  * Main view backend
6465  */
6467 static const char *main_argv[SIZEOF_ARG] = {
6468         "git", "log", "--no-color", "--pretty=raw", "--parents",
6469                       "--topo-order", "%(head)", NULL
6470 };
6472 static bool
6473 main_draw(struct view *view, struct line *line, unsigned int lineno)
6475         struct commit *commit = line->data;
6477         if (!commit->author)
6478                 return FALSE;
6480         if (opt_date && draw_date(view, &commit->time))
6481                 return TRUE;
6483         if (opt_author && draw_author(view, commit->author))
6484                 return TRUE;
6486         if (opt_rev_graph && commit->graph_size &&
6487             draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
6488                 return TRUE;
6490         if (opt_show_refs && commit->refs) {
6491                 size_t i;
6493                 for (i = 0; i < commit->refs->size; i++) {
6494                         struct ref *ref = commit->refs->refs[i];
6495                         enum line_type type;
6497                         if (ref->head)
6498                                 type = LINE_MAIN_HEAD;
6499                         else if (ref->ltag)
6500                                 type = LINE_MAIN_LOCAL_TAG;
6501                         else if (ref->tag)
6502                                 type = LINE_MAIN_TAG;
6503                         else if (ref->tracked)
6504                                 type = LINE_MAIN_TRACKED;
6505                         else if (ref->remote)
6506                                 type = LINE_MAIN_REMOTE;
6507                         else
6508                                 type = LINE_MAIN_REF;
6510                         if (draw_text(view, type, "[", TRUE) ||
6511                             draw_text(view, type, ref->name, TRUE) ||
6512                             draw_text(view, type, "]", TRUE))
6513                                 return TRUE;
6515                         if (draw_text(view, LINE_DEFAULT, " ", TRUE))
6516                                 return TRUE;
6517                 }
6518         }
6520         draw_text(view, LINE_DEFAULT, commit->title, TRUE);
6521         return TRUE;
6524 /* Reads git log --pretty=raw output and parses it into the commit struct. */
6525 static bool
6526 main_read(struct view *view, char *line)
6528         static struct rev_graph *graph = graph_stacks;
6529         enum line_type type;
6530         struct commit *commit;
6532         if (!line) {
6533                 int i;
6535                 if (!view->lines && !view->parent)
6536                         die("No revisions match the given arguments.");
6537                 if (view->lines > 0) {
6538                         commit = view->line[view->lines - 1].data;
6539                         view->line[view->lines - 1].dirty = 1;
6540                         if (!commit->author) {
6541                                 view->lines--;
6542                                 free(commit);
6543                                 graph->commit = NULL;
6544                         }
6545                 }
6546                 update_rev_graph(view, graph);
6548                 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
6549                         clear_rev_graph(&graph_stacks[i]);
6550                 return TRUE;
6551         }
6553         type = get_line_type(line);
6554         if (type == LINE_COMMIT) {
6555                 commit = calloc(1, sizeof(struct commit));
6556                 if (!commit)
6557                         return FALSE;
6559                 line += STRING_SIZE("commit ");
6560                 if (*line == '-') {
6561                         graph->boundary = 1;
6562                         line++;
6563                 }
6565                 string_copy_rev(commit->id, line);
6566                 commit->refs = get_ref_list(commit->id);
6567                 graph->commit = commit;
6568                 add_line_data(view, commit, LINE_MAIN_COMMIT);
6570                 while ((line = strchr(line, ' '))) {
6571                         line++;
6572                         push_rev_graph(graph->parents, line);
6573                         commit->has_parents = TRUE;
6574                 }
6575                 return TRUE;
6576         }
6578         if (!view->lines)
6579                 return TRUE;
6580         commit = view->line[view->lines - 1].data;
6582         switch (type) {
6583         case LINE_PARENT:
6584                 if (commit->has_parents)
6585                         break;
6586                 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
6587                 break;
6589         case LINE_AUTHOR:
6590                 parse_author_line(line + STRING_SIZE("author "),
6591                                   &commit->author, &commit->time);
6592                 update_rev_graph(view, graph);
6593                 graph = graph->next;
6594                 break;
6596         default:
6597                 /* Fill in the commit title if it has not already been set. */
6598                 if (commit->title[0])
6599                         break;
6601                 /* Require titles to start with a non-space character at the
6602                  * offset used by git log. */
6603                 if (strncmp(line, "    ", 4))
6604                         break;
6605                 line += 4;
6606                 /* Well, if the title starts with a whitespace character,
6607                  * try to be forgiving.  Otherwise we end up with no title. */
6608                 while (isspace(*line))
6609                         line++;
6610                 if (*line == '\0')
6611                         break;
6612                 /* FIXME: More graceful handling of titles; append "..." to
6613                  * shortened titles, etc. */
6615                 string_expand(commit->title, sizeof(commit->title), line, 1);
6616                 view->line[view->lines - 1].dirty = 1;
6617         }
6619         return TRUE;
6622 static enum request
6623 main_request(struct view *view, enum request request, struct line *line)
6625         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
6627         switch (request) {
6628         case REQ_ENTER:
6629                 open_view(view, REQ_VIEW_DIFF, flags);
6630                 break;
6631         case REQ_REFRESH:
6632                 load_refs();
6633                 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
6634                 break;
6635         default:
6636                 return request;
6637         }
6639         return REQ_NONE;
6642 static bool
6643 grep_refs(struct ref_list *list, regex_t *regex)
6645         regmatch_t pmatch;
6646         size_t i;
6648         if (!opt_show_refs || !list)
6649                 return FALSE;
6651         for (i = 0; i < list->size; i++) {
6652                 if (regexec(regex, list->refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
6653                         return TRUE;
6654         }
6656         return FALSE;
6659 static bool
6660 main_grep(struct view *view, struct line *line)
6662         struct commit *commit = line->data;
6663         const char *text[] = {
6664                 commit->title,
6665                 opt_author ? commit->author : "",
6666                 opt_date ? mkdate(&commit->time) : "",
6667                 NULL
6668         };
6670         return grep_text(view, text) || grep_refs(commit->refs, view->regex);
6673 static void
6674 main_select(struct view *view, struct line *line)
6676         struct commit *commit = line->data;
6678         string_copy_rev(view->ref, commit->id);
6679         string_copy_rev(ref_commit, view->ref);
6682 static struct view_ops main_ops = {
6683         "commit",
6684         main_argv,
6685         NULL,
6686         main_read,
6687         main_draw,
6688         main_request,
6689         main_grep,
6690         main_select,
6691 };
6694 /*
6695  * Unicode / UTF-8 handling
6696  *
6697  * NOTE: Much of the following code for dealing with Unicode is derived from
6698  * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
6699  * src/intl/charset.c from the UTF-8 branch commit elinks-0.11.0-g31f2c28.
6700  */
6702 static inline int
6703 unicode_width(unsigned long c)
6705         if (c >= 0x1100 &&
6706            (c <= 0x115f                         /* Hangul Jamo */
6707             || c == 0x2329
6708             || c == 0x232a
6709             || (c >= 0x2e80  && c <= 0xa4cf && c != 0x303f)
6710                                                 /* CJK ... Yi */
6711             || (c >= 0xac00  && c <= 0xd7a3)    /* Hangul Syllables */
6712             || (c >= 0xf900  && c <= 0xfaff)    /* CJK Compatibility Ideographs */
6713             || (c >= 0xfe30  && c <= 0xfe6f)    /* CJK Compatibility Forms */
6714             || (c >= 0xff00  && c <= 0xff60)    /* Fullwidth Forms */
6715             || (c >= 0xffe0  && c <= 0xffe6)
6716             || (c >= 0x20000 && c <= 0x2fffd)
6717             || (c >= 0x30000 && c <= 0x3fffd)))
6718                 return 2;
6720         if (c == '\t')
6721                 return opt_tab_size;
6723         return 1;
6726 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
6727  * Illegal bytes are set one. */
6728 static const unsigned char utf8_bytes[256] = {
6729         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,
6730         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,
6731         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,
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         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,
6736         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,
6737 };
6739 /* Decode UTF-8 multi-byte representation into a Unicode character. */
6740 static inline unsigned long
6741 utf8_to_unicode(const char *string, size_t length)
6743         unsigned long unicode;
6745         switch (length) {
6746         case 1:
6747                 unicode  =   string[0];
6748                 break;
6749         case 2:
6750                 unicode  =  (string[0] & 0x1f) << 6;
6751                 unicode +=  (string[1] & 0x3f);
6752                 break;
6753         case 3:
6754                 unicode  =  (string[0] & 0x0f) << 12;
6755                 unicode += ((string[1] & 0x3f) << 6);
6756                 unicode +=  (string[2] & 0x3f);
6757                 break;
6758         case 4:
6759                 unicode  =  (string[0] & 0x0f) << 18;
6760                 unicode += ((string[1] & 0x3f) << 12);
6761                 unicode += ((string[2] & 0x3f) << 6);
6762                 unicode +=  (string[3] & 0x3f);
6763                 break;
6764         case 5:
6765                 unicode  =  (string[0] & 0x0f) << 24;
6766                 unicode += ((string[1] & 0x3f) << 18);
6767                 unicode += ((string[2] & 0x3f) << 12);
6768                 unicode += ((string[3] & 0x3f) << 6);
6769                 unicode +=  (string[4] & 0x3f);
6770                 break;
6771         case 6:
6772                 unicode  =  (string[0] & 0x01) << 30;
6773                 unicode += ((string[1] & 0x3f) << 24);
6774                 unicode += ((string[2] & 0x3f) << 18);
6775                 unicode += ((string[3] & 0x3f) << 12);
6776                 unicode += ((string[4] & 0x3f) << 6);
6777                 unicode +=  (string[5] & 0x3f);
6778                 break;
6779         default:
6780                 die("Invalid Unicode length");
6781         }
6783         /* Invalid characters could return the special 0xfffd value but NUL
6784          * should be just as good. */
6785         return unicode > 0xffff ? 0 : unicode;
6788 /* Calculates how much of string can be shown within the given maximum width
6789  * and sets trimmed parameter to non-zero value if all of string could not be
6790  * shown. If the reserve flag is TRUE, it will reserve at least one
6791  * trailing character, which can be useful when drawing a delimiter.
6792  *
6793  * Returns the number of bytes to output from string to satisfy max_width. */
6794 static size_t
6795 utf8_length(const char **start, size_t skip, int *width, size_t max_width, int *trimmed, bool reserve)
6797         const char *string = *start;
6798         const char *end = strchr(string, '\0');
6799         unsigned char last_bytes = 0;
6800         size_t last_ucwidth = 0;
6802         *width = 0;
6803         *trimmed = 0;
6805         while (string < end) {
6806                 int c = *(unsigned char *) string;
6807                 unsigned char bytes = utf8_bytes[c];
6808                 size_t ucwidth;
6809                 unsigned long unicode;
6811                 if (string + bytes > end)
6812                         break;
6814                 /* Change representation to figure out whether
6815                  * it is a single- or double-width character. */
6817                 unicode = utf8_to_unicode(string, bytes);
6818                 /* FIXME: Graceful handling of invalid Unicode character. */
6819                 if (!unicode)
6820                         break;
6822                 ucwidth = unicode_width(unicode);
6823                 if (skip > 0) {
6824                         skip -= ucwidth <= skip ? ucwidth : skip;
6825                         *start += bytes;
6826                 }
6827                 *width  += ucwidth;
6828                 if (*width > max_width) {
6829                         *trimmed = 1;
6830                         *width -= ucwidth;
6831                         if (reserve && *width == max_width) {
6832                                 string -= last_bytes;
6833                                 *width -= last_ucwidth;
6834                         }
6835                         break;
6836                 }
6838                 string  += bytes;
6839                 last_bytes = ucwidth ? bytes : 0;
6840                 last_ucwidth = ucwidth;
6841         }
6843         return string - *start;
6847 /*
6848  * Status management
6849  */
6851 /* Whether or not the curses interface has been initialized. */
6852 static bool cursed = FALSE;
6854 /* Terminal hacks and workarounds. */
6855 static bool use_scroll_redrawwin;
6856 static bool use_scroll_status_wclear;
6858 /* The status window is used for polling keystrokes. */
6859 static WINDOW *status_win;
6861 /* Reading from the prompt? */
6862 static bool input_mode = FALSE;
6864 static bool status_empty = FALSE;
6866 /* Update status and title window. */
6867 static void
6868 report(const char *msg, ...)
6870         struct view *view = display[current_view];
6872         if (input_mode)
6873                 return;
6875         if (!view) {
6876                 char buf[SIZEOF_STR];
6877                 va_list args;
6879                 va_start(args, msg);
6880                 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
6881                         buf[sizeof(buf) - 1] = 0;
6882                         buf[sizeof(buf) - 2] = '.';
6883                         buf[sizeof(buf) - 3] = '.';
6884                         buf[sizeof(buf) - 4] = '.';
6885                 }
6886                 va_end(args);
6887                 die("%s", buf);
6888         }
6890         if (!status_empty || *msg) {
6891                 va_list args;
6893                 va_start(args, msg);
6895                 wmove(status_win, 0, 0);
6896                 if (view->has_scrolled && use_scroll_status_wclear)
6897                         wclear(status_win);
6898                 if (*msg) {
6899                         vwprintw(status_win, msg, args);
6900                         status_empty = FALSE;
6901                 } else {
6902                         status_empty = TRUE;
6903                 }
6904                 wclrtoeol(status_win);
6905                 wnoutrefresh(status_win);
6907                 va_end(args);
6908         }
6910         update_view_title(view);
6913 /* Controls when nodelay should be in effect when polling user input. */
6914 static void
6915 set_nonblocking_input(bool loading)
6917         static unsigned int loading_views;
6919         if ((loading == FALSE && loading_views-- == 1) ||
6920             (loading == TRUE  && loading_views++ == 0))
6921                 nodelay(status_win, loading);
6924 static void
6925 init_display(void)
6927         const char *term;
6928         int x, y;
6930         /* Initialize the curses library */
6931         if (isatty(STDIN_FILENO)) {
6932                 cursed = !!initscr();
6933                 opt_tty = stdin;
6934         } else {
6935                 /* Leave stdin and stdout alone when acting as a pager. */
6936                 opt_tty = fopen("/dev/tty", "r+");
6937                 if (!opt_tty)
6938                         die("Failed to open /dev/tty");
6939                 cursed = !!newterm(NULL, opt_tty, opt_tty);
6940         }
6942         if (!cursed)
6943                 die("Failed to initialize curses");
6945         nonl();         /* Disable conversion and detect newlines from input. */
6946         cbreak();       /* Take input chars one at a time, no wait for \n */
6947         noecho();       /* Don't echo input */
6948         leaveok(stdscr, FALSE);
6950         if (has_colors())
6951                 init_colors();
6953         getmaxyx(stdscr, y, x);
6954         status_win = newwin(1, 0, y - 1, 0);
6955         if (!status_win)
6956                 die("Failed to create status window");
6958         /* Enable keyboard mapping */
6959         keypad(status_win, TRUE);
6960         wbkgdset(status_win, get_line_attr(LINE_STATUS));
6962         TABSIZE = opt_tab_size;
6963         if (opt_line_graphics) {
6964                 line_graphics[LINE_GRAPHIC_VLINE] = ACS_VLINE;
6965         }
6967         term = getenv("XTERM_VERSION") ? NULL : getenv("COLORTERM");
6968         if (term && !strcmp(term, "gnome-terminal")) {
6969                 /* In the gnome-terminal-emulator, the message from
6970                  * scrolling up one line when impossible followed by
6971                  * scrolling down one line causes corruption of the
6972                  * status line. This is fixed by calling wclear. */
6973                 use_scroll_status_wclear = TRUE;
6974                 use_scroll_redrawwin = FALSE;
6976         } else if (term && !strcmp(term, "xrvt-xpm")) {
6977                 /* No problems with full optimizations in xrvt-(unicode)
6978                  * and aterm. */
6979                 use_scroll_status_wclear = use_scroll_redrawwin = FALSE;
6981         } else {
6982                 /* When scrolling in (u)xterm the last line in the
6983                  * scrolling direction will update slowly. */
6984                 use_scroll_redrawwin = TRUE;
6985                 use_scroll_status_wclear = FALSE;
6986         }
6989 static int
6990 get_input(int prompt_position)
6992         struct view *view;
6993         int i, key, cursor_y, cursor_x;
6995         if (prompt_position)
6996                 input_mode = TRUE;
6998         while (TRUE) {
6999                 foreach_view (view, i) {
7000                         update_view(view);
7001                         if (view_is_displayed(view) && view->has_scrolled &&
7002                             use_scroll_redrawwin)
7003                                 redrawwin(view->win);
7004                         view->has_scrolled = FALSE;
7005                 }
7007                 /* Update the cursor position. */
7008                 if (prompt_position) {
7009                         getbegyx(status_win, cursor_y, cursor_x);
7010                         cursor_x = prompt_position;
7011                 } else {
7012                         view = display[current_view];
7013                         getbegyx(view->win, cursor_y, cursor_x);
7014                         cursor_x = view->width - 1;
7015                         cursor_y += view->lineno - view->offset;
7016                 }
7017                 setsyx(cursor_y, cursor_x);
7019                 /* Refresh, accept single keystroke of input */
7020                 doupdate();
7021                 key = wgetch(status_win);
7023                 /* wgetch() with nodelay() enabled returns ERR when
7024                  * there's no input. */
7025                 if (key == ERR) {
7027                 } else if (key == KEY_RESIZE) {
7028                         int height, width;
7030                         getmaxyx(stdscr, height, width);
7032                         wresize(status_win, 1, width);
7033                         mvwin(status_win, height - 1, 0);
7034                         wnoutrefresh(status_win);
7035                         resize_display();
7036                         redraw_display(TRUE);
7038                 } else {
7039                         input_mode = FALSE;
7040                         return key;
7041                 }
7042         }
7045 static char *
7046 prompt_input(const char *prompt, input_handler handler, void *data)
7048         enum input_status status = INPUT_OK;
7049         static char buf[SIZEOF_STR];
7050         size_t pos = 0;
7052         buf[pos] = 0;
7054         while (status == INPUT_OK || status == INPUT_SKIP) {
7055                 int key;
7057                 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
7058                 wclrtoeol(status_win);
7060                 key = get_input(pos + 1);
7061                 switch (key) {
7062                 case KEY_RETURN:
7063                 case KEY_ENTER:
7064                 case '\n':
7065                         status = pos ? INPUT_STOP : INPUT_CANCEL;
7066                         break;
7068                 case KEY_BACKSPACE:
7069                         if (pos > 0)
7070                                 buf[--pos] = 0;
7071                         else
7072                                 status = INPUT_CANCEL;
7073                         break;
7075                 case KEY_ESC:
7076                         status = INPUT_CANCEL;
7077                         break;
7079                 default:
7080                         if (pos >= sizeof(buf)) {
7081                                 report("Input string too long");
7082                                 return NULL;
7083                         }
7085                         status = handler(data, buf, key);
7086                         if (status == INPUT_OK)
7087                                 buf[pos++] = (char) key;
7088                 }
7089         }
7091         /* Clear the status window */
7092         status_empty = FALSE;
7093         report("");
7095         if (status == INPUT_CANCEL)
7096                 return NULL;
7098         buf[pos++] = 0;
7100         return buf;
7103 static enum input_status
7104 prompt_yesno_handler(void *data, char *buf, int c)
7106         if (c == 'y' || c == 'Y')
7107                 return INPUT_STOP;
7108         if (c == 'n' || c == 'N')
7109                 return INPUT_CANCEL;
7110         return INPUT_SKIP;
7113 static bool
7114 prompt_yesno(const char *prompt)
7116         char prompt2[SIZEOF_STR];
7118         if (!string_format(prompt2, "%s [Yy/Nn]", prompt))
7119                 return FALSE;
7121         return !!prompt_input(prompt2, prompt_yesno_handler, NULL);
7124 static enum input_status
7125 read_prompt_handler(void *data, char *buf, int c)
7127         return isprint(c) ? INPUT_OK : INPUT_SKIP;
7130 static char *
7131 read_prompt(const char *prompt)
7133         return prompt_input(prompt, read_prompt_handler, NULL);
7136 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected)
7138         enum input_status status = INPUT_OK;
7139         int size = 0;
7141         while (items[size].text)
7142                 size++;
7144         while (status == INPUT_OK) {
7145                 const struct menu_item *item = &items[*selected];
7146                 int key;
7147                 int i;
7149                 mvwprintw(status_win, 0, 0, "%s (%d of %d) ",
7150                           prompt, *selected + 1, size);
7151                 if (item->hotkey)
7152                         wprintw(status_win, "[%c] ", (char) item->hotkey);
7153                 wprintw(status_win, "%s", item->text);
7154                 wclrtoeol(status_win);
7156                 key = get_input(COLS - 1);
7157                 switch (key) {
7158                 case KEY_RETURN:
7159                 case KEY_ENTER:
7160                 case '\n':
7161                         status = INPUT_STOP;
7162                         break;
7164                 case KEY_LEFT:
7165                 case KEY_UP:
7166                         *selected = *selected - 1;
7167                         if (*selected < 0)
7168                                 *selected = size - 1;
7169                         break;
7171                 case KEY_RIGHT:
7172                 case KEY_DOWN:
7173                         *selected = (*selected + 1) % size;
7174                         break;
7176                 case KEY_ESC:
7177                         status = INPUT_CANCEL;
7178                         break;
7180                 default:
7181                         for (i = 0; items[i].text; i++)
7182                                 if (items[i].hotkey == key) {
7183                                         *selected = i;
7184                                         status = INPUT_STOP;
7185                                         break;
7186                                 }
7187                 }
7188         }
7190         /* Clear the status window */
7191         status_empty = FALSE;
7192         report("");
7194         return status != INPUT_CANCEL;
7197 /*
7198  * Repository properties
7199  */
7201 static struct ref **refs = NULL;
7202 static size_t refs_size = 0;
7204 static struct ref_list **ref_lists = NULL;
7205 static size_t ref_lists_size = 0;
7207 DEFINE_ALLOCATOR(realloc_refs, struct ref *, 256)
7208 DEFINE_ALLOCATOR(realloc_refs_list, struct ref *, 8)
7209 DEFINE_ALLOCATOR(realloc_ref_lists, struct ref_list *, 8)
7211 static int
7212 compare_refs(const void *ref1_, const void *ref2_)
7214         const struct ref *ref1 = *(const struct ref **)ref1_;
7215         const struct ref *ref2 = *(const struct ref **)ref2_;
7217         if (ref1->tag != ref2->tag)
7218                 return ref2->tag - ref1->tag;
7219         if (ref1->ltag != ref2->ltag)
7220                 return ref2->ltag - ref2->ltag;
7221         if (ref1->head != ref2->head)
7222                 return ref2->head - ref1->head;
7223         if (ref1->tracked != ref2->tracked)
7224                 return ref2->tracked - ref1->tracked;
7225         if (ref1->remote != ref2->remote)
7226                 return ref2->remote - ref1->remote;
7227         return strcmp(ref1->name, ref2->name);
7230 static void
7231 foreach_ref(bool (*visitor)(void *data, const struct ref *ref), void *data)
7233         size_t i;
7235         for (i = 0; i < refs_size; i++)
7236                 if (!visitor(data, refs[i]))
7237                         break;
7240 static struct ref_list *
7241 get_ref_list(const char *id)
7243         struct ref_list *list;
7244         size_t i;
7246         for (i = 0; i < ref_lists_size; i++)
7247                 if (!strcmp(id, ref_lists[i]->id))
7248                         return ref_lists[i];
7250         if (!realloc_ref_lists(&ref_lists, ref_lists_size, 1))
7251                 return NULL;
7252         list = calloc(1, sizeof(*list));
7253         if (!list)
7254                 return NULL;
7256         for (i = 0; i < refs_size; i++) {
7257                 if (!strcmp(id, refs[i]->id) &&
7258                     realloc_refs_list(&list->refs, list->size, 1))
7259                         list->refs[list->size++] = refs[i];
7260         }
7262         if (!list->refs) {
7263                 free(list);
7264                 return NULL;
7265         }
7267         qsort(list->refs, list->size, sizeof(*list->refs), compare_refs);
7268         ref_lists[ref_lists_size++] = list;
7269         return list;
7272 static int
7273 read_ref(char *id, size_t idlen, char *name, size_t namelen)
7275         struct ref *ref = NULL;
7276         bool tag = FALSE;
7277         bool ltag = FALSE;
7278         bool remote = FALSE;
7279         bool tracked = FALSE;
7280         bool head = FALSE;
7281         int from = 0, to = refs_size - 1;
7283         if (!prefixcmp(name, "refs/tags/")) {
7284                 if (!suffixcmp(name, namelen, "^{}")) {
7285                         namelen -= 3;
7286                         name[namelen] = 0;
7287                 } else {
7288                         ltag = TRUE;
7289                 }
7291                 tag = TRUE;
7292                 namelen -= STRING_SIZE("refs/tags/");
7293                 name    += STRING_SIZE("refs/tags/");
7295         } else if (!prefixcmp(name, "refs/remotes/")) {
7296                 remote = TRUE;
7297                 namelen -= STRING_SIZE("refs/remotes/");
7298                 name    += STRING_SIZE("refs/remotes/");
7299                 tracked  = !strcmp(opt_remote, name);
7301         } else if (!prefixcmp(name, "refs/heads/")) {
7302                 namelen -= STRING_SIZE("refs/heads/");
7303                 name    += STRING_SIZE("refs/heads/");
7304                 head     = !strncmp(opt_head, name, namelen);
7306         } else if (!strcmp(name, "HEAD")) {
7307                 string_ncopy(opt_head_rev, id, idlen);
7308                 return OK;
7309         }
7311         /* If we are reloading or it's an annotated tag, replace the
7312          * previous SHA1 with the resolved commit id; relies on the fact
7313          * git-ls-remote lists the commit id of an annotated tag right
7314          * before the commit id it points to. */
7315         while (from <= to) {
7316                 size_t pos = (to + from) / 2;
7317                 int cmp = strcmp(name, refs[pos]->name);
7319                 if (!cmp) {
7320                         ref = refs[pos];
7321                         break;
7322                 }
7324                 if (cmp < 0)
7325                         to = pos - 1;
7326                 else
7327                         from = pos + 1;
7328         }
7330         if (!ref) {
7331                 if (!realloc_refs(&refs, refs_size, 1))
7332                         return ERR;
7333                 ref = calloc(1, sizeof(*ref) + namelen);
7334                 if (!ref)
7335                         return ERR;
7336                 memmove(refs + from + 1, refs + from,
7337                         (refs_size - from) * sizeof(*refs));
7338                 refs[from] = ref;
7339                 strncpy(ref->name, name, namelen);
7340                 refs_size++;
7341         }
7343         ref->head = head;
7344         ref->tag = tag;
7345         ref->ltag = ltag;
7346         ref->remote = remote;
7347         ref->tracked = tracked;
7348         string_copy_rev(ref->id, id);
7350         return OK;
7353 static int
7354 load_refs(void)
7356         const char *head_argv[] = {
7357                 "git", "symbolic-ref", "HEAD", NULL
7358         };
7359         static const char *ls_remote_argv[SIZEOF_ARG] = {
7360                 "git", "ls-remote", opt_git_dir, NULL
7361         };
7362         static bool init = FALSE;
7363         size_t i;
7365         if (!init) {
7366                 argv_from_env(ls_remote_argv, "TIG_LS_REMOTE");
7367                 init = TRUE;
7368         }
7370         if (!*opt_git_dir)
7371                 return OK;
7373         if (run_io_buf(head_argv, opt_head, sizeof(opt_head)) &&
7374             !prefixcmp(opt_head, "refs/heads/")) {
7375                 char *offset = opt_head + STRING_SIZE("refs/heads/");
7377                 memmove(opt_head, offset, strlen(offset) + 1);
7378         }
7380         for (i = 0; i < refs_size; i++)
7381                 refs[i]->id[0] = 0;
7383         if (run_io_load(ls_remote_argv, "\t", read_ref) == ERR)
7384                 return ERR;
7386         /* Update the ref lists to reflect changes. */
7387         for (i = 0; i < ref_lists_size; i++) {
7388                 struct ref_list *list = ref_lists[i];
7389                 size_t old, new;
7391                 for (old = new = 0; old < list->size; old++)
7392                         if (!strcmp(list->id, list->refs[old]->id))
7393                                 list->refs[new++] = list->refs[old];
7394                 list->size = new;
7395         }
7397         return OK;
7400 static void
7401 set_remote_branch(const char *name, const char *value, size_t valuelen)
7403         if (!strcmp(name, ".remote")) {
7404                 string_ncopy(opt_remote, value, valuelen);
7406         } else if (*opt_remote && !strcmp(name, ".merge")) {
7407                 size_t from = strlen(opt_remote);
7409                 if (!prefixcmp(value, "refs/heads/"))
7410                         value += STRING_SIZE("refs/heads/");
7412                 if (!string_format_from(opt_remote, &from, "/%s", value))
7413                         opt_remote[0] = 0;
7414         }
7417 static void
7418 set_repo_config_option(char *name, char *value, int (*cmd)(int, const char **))
7420         const char *argv[SIZEOF_ARG] = { name, "=" };
7421         int argc = 1 + (cmd == option_set_command);
7422         int error = ERR;
7424         if (!argv_from_string(argv, &argc, value))
7425                 config_msg = "Too many option arguments";
7426         else
7427                 error = cmd(argc, argv);
7429         if (error == ERR)
7430                 warn("Option 'tig.%s': %s", name, config_msg);
7433 static bool
7434 set_environment_variable(const char *name, const char *value)
7436         size_t len = strlen(name) + 1 + strlen(value) + 1;
7437         char *env = malloc(len);
7439         if (env &&
7440             string_nformat(env, len, NULL, "%s=%s", name, value) &&
7441             putenv(env) == 0)
7442                 return TRUE;
7443         free(env);
7444         return FALSE;
7447 static void
7448 set_work_tree(const char *value)
7450         char cwd[SIZEOF_STR];
7452         if (!getcwd(cwd, sizeof(cwd)))
7453                 die("Failed to get cwd path: %s", strerror(errno));
7454         if (chdir(opt_git_dir) < 0)
7455                 die("Failed to chdir(%s): %s", strerror(errno));
7456         if (!getcwd(opt_git_dir, sizeof(opt_git_dir)))
7457                 die("Failed to get git path: %s", strerror(errno));
7458         if (chdir(cwd) < 0)
7459                 die("Failed to chdir(%s): %s", cwd, strerror(errno));
7460         if (chdir(value) < 0)
7461                 die("Failed to chdir(%s): %s", value, strerror(errno));
7462         if (!getcwd(cwd, sizeof(cwd)))
7463                 die("Failed to get cwd path: %s", strerror(errno));
7464         if (!set_environment_variable("GIT_WORK_TREE", cwd))
7465                 die("Failed to set GIT_WORK_TREE to '%s'", cwd);
7466         if (!set_environment_variable("GIT_DIR", opt_git_dir))
7467                 die("Failed to set GIT_DIR to '%s'", opt_git_dir);
7468         opt_is_inside_work_tree = TRUE;
7471 static int
7472 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
7474         if (!strcmp(name, "i18n.commitencoding"))
7475                 string_ncopy(opt_encoding, value, valuelen);
7477         else if (!strcmp(name, "core.editor"))
7478                 string_ncopy(opt_editor, value, valuelen);
7480         else if (!strcmp(name, "core.worktree"))
7481                 set_work_tree(value);
7483         else if (!prefixcmp(name, "tig.color."))
7484                 set_repo_config_option(name + 10, value, option_color_command);
7486         else if (!prefixcmp(name, "tig.bind."))
7487                 set_repo_config_option(name + 9, value, option_bind_command);
7489         else if (!prefixcmp(name, "tig."))
7490                 set_repo_config_option(name + 4, value, option_set_command);
7492         else if (*opt_head && !prefixcmp(name, "branch.") &&
7493                  !strncmp(name + 7, opt_head, strlen(opt_head)))
7494                 set_remote_branch(name + 7 + strlen(opt_head), value, valuelen);
7496         return OK;
7499 static int
7500 load_git_config(void)
7502         const char *config_list_argv[] = { "git", "config", "--list", NULL };
7504         return run_io_load(config_list_argv, "=", read_repo_config_option);
7507 static int
7508 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
7510         if (!opt_git_dir[0]) {
7511                 string_ncopy(opt_git_dir, name, namelen);
7513         } else if (opt_is_inside_work_tree == -1) {
7514                 /* This can be 3 different values depending on the
7515                  * version of git being used. If git-rev-parse does not
7516                  * understand --is-inside-work-tree it will simply echo
7517                  * the option else either "true" or "false" is printed.
7518                  * Default to true for the unknown case. */
7519                 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
7521         } else if (*name == '.') {
7522                 string_ncopy(opt_cdup, name, namelen);
7524         } else {
7525                 string_ncopy(opt_prefix, name, namelen);
7526         }
7528         return OK;
7531 static int
7532 load_repo_info(void)
7534         const char *rev_parse_argv[] = {
7535                 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
7536                         "--show-cdup", "--show-prefix", NULL
7537         };
7539         return run_io_load(rev_parse_argv, "=", read_repo_info);
7543 /*
7544  * Main
7545  */
7547 static const char usage[] =
7548 "tig " TIG_VERSION " (" __DATE__ ")\n"
7549 "\n"
7550 "Usage: tig        [options] [revs] [--] [paths]\n"
7551 "   or: tig show   [options] [revs] [--] [paths]\n"
7552 "   or: tig blame  [rev] path\n"
7553 "   or: tig status\n"
7554 "   or: tig <      [git command output]\n"
7555 "\n"
7556 "Options:\n"
7557 "  -v, --version   Show version and exit\n"
7558 "  -h, --help      Show help message and exit";
7560 static void __NORETURN
7561 quit(int sig)
7563         /* XXX: Restore tty modes and let the OS cleanup the rest! */
7564         if (cursed)
7565                 endwin();
7566         exit(0);
7569 static void __NORETURN
7570 die(const char *err, ...)
7572         va_list args;
7574         endwin();
7576         va_start(args, err);
7577         fputs("tig: ", stderr);
7578         vfprintf(stderr, err, args);
7579         fputs("\n", stderr);
7580         va_end(args);
7582         exit(1);
7585 static void
7586 warn(const char *msg, ...)
7588         va_list args;
7590         va_start(args, msg);
7591         fputs("tig warning: ", stderr);
7592         vfprintf(stderr, msg, args);
7593         fputs("\n", stderr);
7594         va_end(args);
7597 static enum request
7598 parse_options(int argc, const char *argv[])
7600         enum request request = REQ_VIEW_MAIN;
7601         const char *subcommand;
7602         bool seen_dashdash = FALSE;
7603         /* XXX: This is vulnerable to the user overriding options
7604          * required for the main view parser. */
7605         const char *custom_argv[SIZEOF_ARG] = {
7606                 "git", "log", "--no-color", "--pretty=raw", "--parents",
7607                         "--topo-order", NULL
7608         };
7609         int i, j = 6;
7611         if (!isatty(STDIN_FILENO)) {
7612                 io_open(&VIEW(REQ_VIEW_PAGER)->io, "");
7613                 return REQ_VIEW_PAGER;
7614         }
7616         if (argc <= 1)
7617                 return REQ_NONE;
7619         subcommand = argv[1];
7620         if (!strcmp(subcommand, "status")) {
7621                 if (argc > 2)
7622                         warn("ignoring arguments after `%s'", subcommand);
7623                 return REQ_VIEW_STATUS;
7625         } else if (!strcmp(subcommand, "blame")) {
7626                 if (argc <= 2 || argc > 4)
7627                         die("invalid number of options to blame\n\n%s", usage);
7629                 i = 2;
7630                 if (argc == 4) {
7631                         string_ncopy(opt_ref, argv[i], strlen(argv[i]));
7632                         i++;
7633                 }
7635                 string_ncopy(opt_file, argv[i], strlen(argv[i]));
7636                 return REQ_VIEW_BLAME;
7638         } else if (!strcmp(subcommand, "show")) {
7639                 request = REQ_VIEW_DIFF;
7641         } else {
7642                 subcommand = NULL;
7643         }
7645         if (subcommand) {
7646                 custom_argv[1] = subcommand;
7647                 j = 2;
7648         }
7650         for (i = 1 + !!subcommand; i < argc; i++) {
7651                 const char *opt = argv[i];
7653                 if (seen_dashdash || !strcmp(opt, "--")) {
7654                         seen_dashdash = TRUE;
7656                 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
7657                         printf("tig version %s\n", TIG_VERSION);
7658                         quit(0);
7660                 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
7661                         printf("%s\n", usage);
7662                         quit(0);
7663                 }
7665                 custom_argv[j++] = opt;
7666                 if (j >= ARRAY_SIZE(custom_argv))
7667                         die("command too long");
7668         }
7670         if (!prepare_update(VIEW(request), custom_argv, NULL, FORMAT_NONE))
7671                 die("Failed to format arguments");
7673         return request;
7676 int
7677 main(int argc, const char *argv[])
7679         enum request request = parse_options(argc, argv);
7680         struct view *view;
7681         size_t i;
7683         signal(SIGINT, quit);
7684         signal(SIGPIPE, SIG_IGN);
7686         if (setlocale(LC_ALL, "")) {
7687                 char *codeset = nl_langinfo(CODESET);
7689                 string_ncopy(opt_codeset, codeset, strlen(codeset));
7690         }
7692         if (load_repo_info() == ERR)
7693                 die("Failed to load repo info.");
7695         if (load_options() == ERR)
7696                 die("Failed to load user config.");
7698         if (load_git_config() == ERR)
7699                 die("Failed to load repo config.");
7701         /* Require a git repository unless when running in pager mode. */
7702         if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
7703                 die("Not a git repository");
7705         if (*opt_encoding && strcmp(opt_codeset, "UTF-8")) {
7706                 opt_iconv_in = iconv_open("UTF-8", opt_encoding);
7707                 if (opt_iconv_in == ICONV_NONE)
7708                         die("Failed to initialize character set conversion");
7709         }
7711         if (*opt_codeset && strcmp(opt_codeset, "UTF-8")) {
7712                 opt_iconv_out = iconv_open(opt_codeset, "UTF-8");
7713                 if (opt_iconv_out == ICONV_NONE)
7714                         die("Failed to initialize character set conversion");
7715         }
7717         if (load_refs() == ERR)
7718                 die("Failed to load refs.");
7720         foreach_view (view, i)
7721                 argv_from_env(view->ops->argv, view->cmd_env);
7723         init_display();
7725         if (request != REQ_NONE)
7726                 open_view(NULL, request, OPEN_PREPARED);
7727         request = request == REQ_NONE ? REQ_VIEW_MAIN : REQ_NONE;
7729         while (view_driver(display[current_view], request)) {
7730                 int key = get_input(0);
7732                 view = display[current_view];
7733                 request = get_keybinding(view->keymap, key);
7735                 /* Some low-level request handling. This keeps access to
7736                  * status_win restricted. */
7737                 switch (request) {
7738                 case REQ_PROMPT:
7739                 {
7740                         char *cmd = read_prompt(":");
7742                         if (cmd && isdigit(*cmd)) {
7743                                 int lineno = view->lineno + 1;
7745                                 if (parse_int(&lineno, cmd, 1, view->lines + 1) == OK) {
7746                                         select_view_line(view, lineno - 1);
7747                                         report("");
7748                                 } else {
7749                                         report("Unable to parse '%s' as a line number", cmd);
7750                                 }
7752                         } else if (cmd) {
7753                                 struct view *next = VIEW(REQ_VIEW_PAGER);
7754                                 const char *argv[SIZEOF_ARG] = { "git" };
7755                                 int argc = 1;
7757                                 /* When running random commands, initially show the
7758                                  * command in the title. However, it maybe later be
7759                                  * overwritten if a commit line is selected. */
7760                                 string_ncopy(next->ref, cmd, strlen(cmd));
7762                                 if (!argv_from_string(argv, &argc, cmd)) {
7763                                         report("Too many arguments");
7764                                 } else if (!prepare_update(next, argv, NULL, FORMAT_DASH)) {
7765                                         report("Failed to format command");
7766                                 } else {
7767                                         open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
7768                                 }
7769                         }
7771                         request = REQ_NONE;
7772                         break;
7773                 }
7774                 case REQ_SEARCH:
7775                 case REQ_SEARCH_BACK:
7776                 {
7777                         const char *prompt = request == REQ_SEARCH ? "/" : "?";
7778                         char *search = read_prompt(prompt);
7780                         if (search)
7781                                 string_ncopy(opt_search, search, strlen(search));
7782                         else if (*opt_search)
7783                                 request = request == REQ_SEARCH ?
7784                                         REQ_FIND_NEXT :
7785                                         REQ_FIND_PREV;
7786                         else
7787                                 request = REQ_NONE;
7788                         break;
7789                 }
7790                 default:
7791                         break;
7792                 }
7793         }
7795         quit(0);
7797         return 0;