Code

Fix parsing of boolean show-date values
[tig.git] / tig.c
1 /* Copyright (c) 2006-2009 Jonas Fonseca <fonseca@diku.dk>
2  *
3  * This program is free software; you can redistribute it and/or
4  * modify it under the terms of the GNU General Public License as
5  * published by the Free Software Foundation; either version 2 of
6  * the License, or (at your option) any later version.
7  *
8  * This program is distributed in the hope that it will be useful,
9  * but WITHOUT ANY WARRANTY; without even the implied warranty of
10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11  * GNU General Public License for more details.
12  */
14 #ifdef HAVE_CONFIG_H
15 #include "config.h"
16 #endif
18 #ifndef TIG_VERSION
19 #define TIG_VERSION "unknown-version"
20 #endif
22 #ifndef DEBUG
23 #define NDEBUG
24 #endif
26 #include <assert.h>
27 #include <errno.h>
28 #include <ctype.h>
29 #include <signal.h>
30 #include <stdarg.h>
31 #include <stdio.h>
32 #include <stdlib.h>
33 #include <string.h>
34 #include <sys/types.h>
35 #include <sys/wait.h>
36 #include <sys/stat.h>
37 #include <sys/select.h>
38 #include <unistd.h>
39 #include <sys/time.h>
40 #include <time.h>
41 #include <fcntl.h>
43 #include <regex.h>
45 #include <locale.h>
46 #include <langinfo.h>
47 #include <iconv.h>
49 /* ncurses(3): Must be defined to have extended wide-character functions. */
50 #define _XOPEN_SOURCE_EXTENDED
52 #ifdef HAVE_NCURSESW_NCURSES_H
53 #include <ncursesw/ncurses.h>
54 #else
55 #ifdef HAVE_NCURSES_NCURSES_H
56 #include <ncurses/ncurses.h>
57 #else
58 #include <ncurses.h>
59 #endif
60 #endif
62 #if __GNUC__ >= 3
63 #define __NORETURN __attribute__((__noreturn__))
64 #else
65 #define __NORETURN
66 #endif
68 static void __NORETURN die(const char *err, ...);
69 static void warn(const char *msg, ...);
70 static void report(const char *msg, ...);
71 static void set_nonblocking_input(bool loading);
72 static size_t utf8_length(const char **string, size_t col, int *width, size_t max_width, int *trimmed, bool reserve);
74 #define ABS(x)          ((x) >= 0  ? (x) : -(x))
75 #define MIN(x, y)       ((x) < (y) ? (x) :  (y))
76 #define MAX(x, y)       ((x) > (y) ? (x) :  (y))
78 #define ARRAY_SIZE(x)   (sizeof(x) / sizeof(x[0]))
79 #define STRING_SIZE(x)  (sizeof(x) - 1)
81 #define SIZEOF_STR      1024    /* Default string size. */
82 #define SIZEOF_REF      256     /* Size of symbolic or SHA1 ID. */
83 #define SIZEOF_REV      41      /* Holds a SHA-1 and an ending NUL. */
84 #define SIZEOF_ARG      32      /* Default argument array size. */
86 /* Revision graph */
88 #define REVGRAPH_INIT   'I'
89 #define REVGRAPH_MERGE  'M'
90 #define REVGRAPH_BRANCH '+'
91 #define REVGRAPH_COMMIT '*'
92 #define REVGRAPH_BOUND  '^'
94 #define SIZEOF_REVGRAPH 19      /* Size of revision ancestry graphics. */
96 /* This color name can be used to refer to the default term colors. */
97 #define COLOR_DEFAULT   (-1)
99 #define ICONV_NONE      ((iconv_t) -1)
100 #ifndef ICONV_CONST
101 #define ICONV_CONST     /* nothing */
102 #endif
104 /* The format and size of the date column in the main view. */
105 #define DATE_FORMAT     "%Y-%m-%d %H:%M"
106 #define DATE_COLS       STRING_SIZE("2006-04-29 14:21 ")
107 #define DATE_SHORT_COLS STRING_SIZE("2006-04-29 ")
109 #define ID_COLS         8
111 #define MIN_VIEW_HEIGHT 4
113 #define NULL_ID         "0000000000000000000000000000000000000000"
115 #define S_ISGITLINK(mode) (((mode) & S_IFMT) == 0160000)
117 /* Some ASCII-shorthands fitted into the ncurses namespace. */
118 #define KEY_TAB         '\t'
119 #define KEY_RETURN      '\r'
120 #define KEY_ESC         27
123 struct ref {
124         char id[SIZEOF_REV];    /* Commit SHA1 ID */
125         unsigned int head:1;    /* Is it the current HEAD? */
126         unsigned int tag:1;     /* Is it a tag? */
127         unsigned int ltag:1;    /* If so, is the tag local? */
128         unsigned int remote:1;  /* Is it a remote ref? */
129         unsigned int tracked:1; /* Is it the remote for the current HEAD? */
130         char name[1];           /* Ref name; tag or head names are shortened. */
131 };
133 struct ref_list {
134         char id[SIZEOF_REV];    /* Commit SHA1 ID */
135         size_t size;            /* Number of refs. */
136         struct ref **refs;      /* References for this ID. */
137 };
139 static struct ref_list *get_ref_list(const char *id);
140 static void foreach_ref(bool (*visitor)(void *data, const struct ref *ref), void *data);
141 static int load_refs(void);
143 enum format_flags {
144         FORMAT_ALL,             /* Perform replacement in all arguments. */
145         FORMAT_DASH,            /* Perform replacement up until "--". */
146         FORMAT_NONE             /* No replacement should be performed. */
147 };
149 static bool format_argv(const char *dst[], const char *src[], enum format_flags flags);
151 enum input_status {
152         INPUT_OK,
153         INPUT_SKIP,
154         INPUT_STOP,
155         INPUT_CANCEL
156 };
158 typedef enum input_status (*input_handler)(void *data, char *buf, int c);
160 static char *prompt_input(const char *prompt, input_handler handler, void *data);
161 static bool prompt_yesno(const char *prompt);
163 struct menu_item {
164         int hotkey;
165         const char *text;
166         void *data;
167 };
169 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected);
171 /*
172  * Allocation helpers ... Entering macro hell to never be seen again.
173  */
175 #define DEFINE_ALLOCATOR(name, type, chunk_size)                                \
176 static type *                                                                   \
177 name(type **mem, size_t size, size_t increase)                                  \
178 {                                                                               \
179         size_t num_chunks = (size + chunk_size - 1) / chunk_size;               \
180         size_t num_chunks_new = (size + increase + chunk_size - 1) / chunk_size;\
181         type *tmp = *mem;                                                       \
182                                                                                 \
183         if (mem == NULL || num_chunks != num_chunks_new) {                      \
184                 tmp = realloc(tmp, num_chunks_new * chunk_size * sizeof(type)); \
185                 if (tmp)                                                        \
186                         *mem = tmp;                                             \
187         }                                                                       \
188                                                                                 \
189         return tmp;                                                             \
192 /*
193  * String helpers
194  */
196 static inline void
197 string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t srclen)
199         if (srclen > dstlen - 1)
200                 srclen = dstlen - 1;
202         strncpy(dst, src, srclen);
203         dst[srclen] = 0;
206 /* Shorthands for safely copying into a fixed buffer. */
208 #define string_copy(dst, src) \
209         string_ncopy_do(dst, sizeof(dst), src, sizeof(src))
211 #define string_ncopy(dst, src, srclen) \
212         string_ncopy_do(dst, sizeof(dst), src, srclen)
214 #define string_copy_rev(dst, src) \
215         string_ncopy_do(dst, SIZEOF_REV, src, SIZEOF_REV - 1)
217 #define string_add(dst, from, src) \
218         string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src))
220 static void
221 string_expand(char *dst, size_t dstlen, const char *src, int tabsize)
223         size_t size, pos;
225         for (size = pos = 0; size < dstlen - 1 && src[pos]; pos++) {
226                 if (src[pos] == '\t') {
227                         size_t expanded = tabsize - (size % tabsize);
229                         if (expanded + size >= dstlen - 1)
230                                 expanded = dstlen - size - 1;
231                         memcpy(dst + size, "        ", expanded);
232                         size += expanded;
233                 } else {
234                         dst[size++] = src[pos];
235                 }
236         }
238         dst[size] = 0;
241 static char *
242 chomp_string(char *name)
244         int namelen;
246         while (isspace(*name))
247                 name++;
249         namelen = strlen(name) - 1;
250         while (namelen > 0 && isspace(name[namelen]))
251                 name[namelen--] = 0;
253         return name;
256 static bool
257 string_nformat(char *buf, size_t bufsize, size_t *bufpos, const char *fmt, ...)
259         va_list args;
260         size_t pos = bufpos ? *bufpos : 0;
262         va_start(args, fmt);
263         pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
264         va_end(args);
266         if (bufpos)
267                 *bufpos = pos;
269         return pos >= bufsize ? FALSE : TRUE;
272 #define string_format(buf, fmt, args...) \
273         string_nformat(buf, sizeof(buf), NULL, fmt, args)
275 #define string_format_from(buf, from, fmt, args...) \
276         string_nformat(buf, sizeof(buf), from, fmt, args)
278 static int
279 string_enum_compare(const char *str1, const char *str2, int len)
281         size_t i;
283 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
285         /* Diff-Header == DIFF_HEADER */
286         for (i = 0; i < len; i++) {
287                 if (toupper(str1[i]) == toupper(str2[i]))
288                         continue;
290                 if (string_enum_sep(str1[i]) &&
291                     string_enum_sep(str2[i]))
292                         continue;
294                 return str1[i] - str2[i];
295         }
297         return 0;
300 struct enum_map {
301         const char *name;
302         int namelen;
303         int value;
304 };
306 #define ENUM_MAP(name, value) { name, STRING_SIZE(name), value }
308 static bool
309 map_enum_do(const struct enum_map *map, size_t map_size, int *value, const char *name)
311         size_t namelen = strlen(name);
312         int i;
314         for (i = 0; i < map_size; i++)
315                 if (namelen == map[i].namelen &&
316                     !string_enum_compare(name, map[i].name, namelen)) {
317                         *value = map[i].value;
318                         return TRUE;
319                 }
321         return FALSE;
324 #define map_enum(attr, map, name) \
325         map_enum_do(map, ARRAY_SIZE(map), attr, name)
327 #define prefixcmp(str1, str2) \
328         strncmp(str1, str2, STRING_SIZE(str2))
330 static inline int
331 suffixcmp(const char *str, int slen, const char *suffix)
333         size_t len = slen >= 0 ? slen : strlen(str);
334         size_t suffixlen = strlen(suffix);
336         return suffixlen < len ? strcmp(str + len - suffixlen, suffix) : -1;
340 /*
341  * What value of "tz" was in effect back then at "time" in the
342  * local timezone?
343  */
344 static int local_tzoffset(time_t time)
346         time_t t, t_local;
347         struct tm tm;
348         int offset, eastwest; 
350         t = time;
351         localtime_r(&t, &tm);
352         t_local = mktime(&tm);
354         if (t_local < t) {
355                 eastwest = -1;
356                 offset = t - t_local;
357         } else {
358                 eastwest = 1;
359                 offset = t_local - t;
360         }
361         offset /= 60; /* in minutes */
362         offset = (offset % 60) + ((offset / 60) * 100);
363         return offset * eastwest;
366 enum date {
367         DATE_NONE = 0,
368         DATE_DEFAULT,
369         DATE_RELATIVE,
370         DATE_SHORT
371 };
373 static char *
374 string_date(const time_t *time, enum date date)
376         static char buf[DATE_COLS + 1];
377         static const struct enum_map reldate[] = {
378                 { "second", 1,                  60 * 2 },
379                 { "minute", 60,                 60 * 60 * 2 },
380                 { "hour",   60 * 60,            60 * 60 * 24 * 2 },
381                 { "day",    60 * 60 * 24,       60 * 60 * 24 * 7 * 2 },
382                 { "week",   60 * 60 * 24 * 7,   60 * 60 * 24 * 7 * 5 },
383                 { "month",  60 * 60 * 24 * 30,  60 * 60 * 24 * 30 * 12 },
384         };
385         struct tm tm;
387         if (date == DATE_RELATIVE) {
388                 struct timeval now;
389                 time_t date = *time + local_tzoffset(*time);
390                 time_t seconds;
391                 int i;
393                 gettimeofday(&now, NULL);
394                 seconds = now.tv_sec < date ? date - now.tv_sec : now.tv_sec - date;
395                 for (i = 0; i < ARRAY_SIZE(reldate); i++) {
396                         if (seconds >= reldate[i].value)
397                                 continue;
399                         seconds /= reldate[i].namelen;
400                         if (!string_format(buf, "%ld %s%s %s",
401                                            seconds, reldate[i].name,
402                                            seconds > 1 ? "s" : "",
403                                            now.tv_sec >= date ? "ago" : "ahead"))
404                                 break;
405                         return buf;
406                 }
407         }
409         gmtime_r(time, &tm);
410         return strftime(buf, sizeof(buf), DATE_FORMAT, &tm) ? buf : NULL;
414 static bool
415 argv_from_string(const char *argv[SIZEOF_ARG], int *argc, char *cmd)
417         int valuelen;
419         while (*cmd && *argc < SIZEOF_ARG && (valuelen = strcspn(cmd, " \t"))) {
420                 bool advance = cmd[valuelen] != 0;
422                 cmd[valuelen] = 0;
423                 argv[(*argc)++] = chomp_string(cmd);
424                 cmd = chomp_string(cmd + valuelen + advance);
425         }
427         if (*argc < SIZEOF_ARG)
428                 argv[*argc] = NULL;
429         return *argc < SIZEOF_ARG;
432 static void
433 argv_from_env(const char **argv, const char *name)
435         char *env = argv ? getenv(name) : NULL;
436         int argc = 0;
438         if (env && *env)
439                 env = strdup(env);
440         if (env && !argv_from_string(argv, &argc, env))
441                 die("Too many arguments in the `%s` environment variable", name);
445 /*
446  * Executing external commands.
447  */
449 enum io_type {
450         IO_FD,                  /* File descriptor based IO. */
451         IO_BG,                  /* Execute command in the background. */
452         IO_FG,                  /* Execute command with same std{in,out,err}. */
453         IO_RD,                  /* Read only fork+exec IO. */
454         IO_WR,                  /* Write only fork+exec IO. */
455         IO_AP,                  /* Append fork+exec output to file. */
456 };
458 struct io {
459         enum io_type type;      /* The requested type of pipe. */
460         const char *dir;        /* Directory from which to execute. */
461         pid_t pid;              /* Pipe for reading or writing. */
462         int pipe;               /* Pipe end for reading or writing. */
463         int error;              /* Error status. */
464         const char *argv[SIZEOF_ARG];   /* Shell command arguments. */
465         char *buf;              /* Read buffer. */
466         size_t bufalloc;        /* Allocated buffer size. */
467         size_t bufsize;         /* Buffer content size. */
468         char *bufpos;           /* Current buffer position. */
469         unsigned int eof:1;     /* Has end of file been reached. */
470 };
472 static void
473 reset_io(struct io *io)
475         io->pipe = -1;
476         io->pid = 0;
477         io->buf = io->bufpos = NULL;
478         io->bufalloc = io->bufsize = 0;
479         io->error = 0;
480         io->eof = 0;
483 static void
484 init_io(struct io *io, const char *dir, enum io_type type)
486         reset_io(io);
487         io->type = type;
488         io->dir = dir;
491 static bool
492 init_io_rd(struct io *io, const char *argv[], const char *dir,
493                 enum format_flags flags)
495         init_io(io, dir, IO_RD);
496         return format_argv(io->argv, argv, flags);
499 static bool
500 io_open(struct io *io, const char *fmt, ...)
502         char name[SIZEOF_STR] = "";
503         bool fits;
504         va_list args;
506         init_io(io, NULL, IO_FD);
508         va_start(args, fmt);
509         fits = vsnprintf(name, sizeof(name), fmt, args) < sizeof(name);
510         va_end(args);
512         if (!fits) {
513                 io->error = ENAMETOOLONG;
514                 return FALSE;
515         }
516         io->pipe = *name ? open(name, O_RDONLY) : STDIN_FILENO;
517         if (io->pipe == -1)
518                 io->error = errno;
519         return io->pipe != -1;
522 static bool
523 kill_io(struct io *io)
525         return io->pid == 0 || kill(io->pid, SIGKILL) != -1;
528 static bool
529 done_io(struct io *io)
531         pid_t pid = io->pid;
533         if (io->pipe != -1)
534                 close(io->pipe);
535         free(io->buf);
536         reset_io(io);
538         while (pid > 0) {
539                 int status;
540                 pid_t waiting = waitpid(pid, &status, 0);
542                 if (waiting < 0) {
543                         if (errno == EINTR)
544                                 continue;
545                         report("waitpid failed (%s)", strerror(errno));
546                         return FALSE;
547                 }
549                 return waiting == pid &&
550                        !WIFSIGNALED(status) &&
551                        WIFEXITED(status) &&
552                        !WEXITSTATUS(status);
553         }
555         return TRUE;
558 static bool
559 start_io(struct io *io)
561         int pipefds[2] = { -1, -1 };
563         if (io->type == IO_FD)
564                 return TRUE;
566         if ((io->type == IO_RD || io->type == IO_WR) &&
567             pipe(pipefds) < 0)
568                 return FALSE;
569         else if (io->type == IO_AP)
570                 pipefds[1] = io->pipe;
572         if ((io->pid = fork())) {
573                 if (pipefds[!(io->type == IO_WR)] != -1)
574                         close(pipefds[!(io->type == IO_WR)]);
575                 if (io->pid != -1) {
576                         io->pipe = pipefds[!!(io->type == IO_WR)];
577                         return TRUE;
578                 }
580         } else {
581                 if (io->type != IO_FG) {
582                         int devnull = open("/dev/null", O_RDWR);
583                         int readfd  = io->type == IO_WR ? pipefds[0] : devnull;
584                         int writefd = (io->type == IO_RD || io->type == IO_AP)
585                                                         ? pipefds[1] : devnull;
587                         dup2(readfd,  STDIN_FILENO);
588                         dup2(writefd, STDOUT_FILENO);
589                         dup2(devnull, STDERR_FILENO);
591                         close(devnull);
592                         if (pipefds[0] != -1)
593                                 close(pipefds[0]);
594                         if (pipefds[1] != -1)
595                                 close(pipefds[1]);
596                 }
598                 if (io->dir && *io->dir && chdir(io->dir) == -1)
599                         die("Failed to change directory: %s", strerror(errno));
601                 execvp(io->argv[0], (char *const*) io->argv);
602                 die("Failed to execute program: %s", strerror(errno));
603         }
605         if (pipefds[!!(io->type == IO_WR)] != -1)
606                 close(pipefds[!!(io->type == IO_WR)]);
607         return FALSE;
610 static bool
611 run_io(struct io *io, const char **argv, const char *dir, enum io_type type)
613         init_io(io, dir, type);
614         if (!format_argv(io->argv, argv, FORMAT_NONE))
615                 return FALSE;
616         return start_io(io);
619 static int
620 run_io_do(struct io *io)
622         return start_io(io) && done_io(io);
625 static int
626 run_io_bg(const char **argv)
628         struct io io = {};
630         init_io(&io, NULL, IO_BG);
631         if (!format_argv(io.argv, argv, FORMAT_NONE))
632                 return FALSE;
633         return run_io_do(&io);
636 static bool
637 run_io_fg(const char **argv, const char *dir)
639         struct io io = {};
641         init_io(&io, dir, IO_FG);
642         if (!format_argv(io.argv, argv, FORMAT_NONE))
643                 return FALSE;
644         return run_io_do(&io);
647 static bool
648 run_io_append(const char **argv, enum format_flags flags, int fd)
650         struct io io = {};
652         init_io(&io, NULL, IO_AP);
653         io.pipe = fd;
654         if (format_argv(io.argv, argv, flags))
655                 return run_io_do(&io);
656         close(fd);
657         return FALSE;
660 static bool
661 run_io_rd(struct io *io, const char **argv, const char *dir, enum format_flags flags)
663         return init_io_rd(io, argv, dir, flags) && start_io(io);
666 static bool
667 io_eof(struct io *io)
669         return io->eof;
672 static int
673 io_error(struct io *io)
675         return io->error;
678 static char *
679 io_strerror(struct io *io)
681         return strerror(io->error);
684 static bool
685 io_can_read(struct io *io)
687         struct timeval tv = { 0, 500 };
688         fd_set fds;
690         FD_ZERO(&fds);
691         FD_SET(io->pipe, &fds);
693         return select(io->pipe + 1, &fds, NULL, NULL, &tv) > 0;
696 static ssize_t
697 io_read(struct io *io, void *buf, size_t bufsize)
699         do {
700                 ssize_t readsize = read(io->pipe, buf, bufsize);
702                 if (readsize < 0 && (errno == EAGAIN || errno == EINTR))
703                         continue;
704                 else if (readsize == -1)
705                         io->error = errno;
706                 else if (readsize == 0)
707                         io->eof = 1;
708                 return readsize;
709         } while (1);
712 DEFINE_ALLOCATOR(realloc_io_buf, char, BUFSIZ)
714 static char *
715 io_get(struct io *io, int c, bool can_read)
717         char *eol;
718         ssize_t readsize;
720         while (TRUE) {
721                 if (io->bufsize > 0) {
722                         eol = memchr(io->bufpos, c, io->bufsize);
723                         if (eol) {
724                                 char *line = io->bufpos;
726                                 *eol = 0;
727                                 io->bufpos = eol + 1;
728                                 io->bufsize -= io->bufpos - line;
729                                 return line;
730                         }
731                 }
733                 if (io_eof(io)) {
734                         if (io->bufsize) {
735                                 io->bufpos[io->bufsize] = 0;
736                                 io->bufsize = 0;
737                                 return io->bufpos;
738                         }
739                         return NULL;
740                 }
742                 if (!can_read)
743                         return NULL;
745                 if (io->bufsize > 0 && io->bufpos > io->buf)
746                         memmove(io->buf, io->bufpos, io->bufsize);
748                 if (io->bufalloc == io->bufsize) {
749                         if (!realloc_io_buf(&io->buf, io->bufalloc, BUFSIZ))
750                                 return NULL;
751                         io->bufalloc += BUFSIZ;
752                 }
754                 io->bufpos = io->buf;
755                 readsize = io_read(io, io->buf + io->bufsize, io->bufalloc - io->bufsize);
756                 if (io_error(io))
757                         return NULL;
758                 io->bufsize += readsize;
759         }
762 static bool
763 io_write(struct io *io, const void *buf, size_t bufsize)
765         size_t written = 0;
767         while (!io_error(io) && written < bufsize) {
768                 ssize_t size;
770                 size = write(io->pipe, buf + written, bufsize - written);
771                 if (size < 0 && (errno == EAGAIN || errno == EINTR))
772                         continue;
773                 else if (size == -1)
774                         io->error = errno;
775                 else
776                         written += size;
777         }
779         return written == bufsize;
782 static bool
783 io_read_buf(struct io *io, char buf[], size_t bufsize)
785         char *result = io_get(io, '\n', TRUE);
787         if (result) {
788                 result = chomp_string(result);
789                 string_ncopy_do(buf, bufsize, result, strlen(result));
790         }
792         return done_io(io) && result;
795 static bool
796 run_io_buf(const char **argv, char buf[], size_t bufsize)
798         struct io io = {};
800         return run_io_rd(&io, argv, NULL, FORMAT_NONE)
801             && io_read_buf(&io, buf, bufsize);
804 static int
805 io_load(struct io *io, const char *separators,
806         int (*read_property)(char *, size_t, char *, size_t))
808         char *name;
809         int state = OK;
811         if (!start_io(io))
812                 return ERR;
814         while (state == OK && (name = io_get(io, '\n', TRUE))) {
815                 char *value;
816                 size_t namelen;
817                 size_t valuelen;
819                 name = chomp_string(name);
820                 namelen = strcspn(name, separators);
822                 if (name[namelen]) {
823                         name[namelen] = 0;
824                         value = chomp_string(name + namelen + 1);
825                         valuelen = strlen(value);
827                 } else {
828                         value = "";
829                         valuelen = 0;
830                 }
832                 state = read_property(name, namelen, value, valuelen);
833         }
835         if (state != ERR && io_error(io))
836                 state = ERR;
837         done_io(io);
839         return state;
842 static int
843 run_io_load(const char **argv, const char *separators,
844             int (*read_property)(char *, size_t, char *, size_t))
846         struct io io = {};
848         return init_io_rd(&io, argv, NULL, FORMAT_NONE)
849                 ? io_load(&io, separators, read_property) : ERR;
853 /*
854  * User requests
855  */
857 #define REQ_INFO \
858         /* XXX: Keep the view request first and in sync with views[]. */ \
859         REQ_GROUP("View switching") \
860         REQ_(VIEW_MAIN,         "Show main view"), \
861         REQ_(VIEW_DIFF,         "Show diff view"), \
862         REQ_(VIEW_LOG,          "Show log view"), \
863         REQ_(VIEW_TREE,         "Show tree view"), \
864         REQ_(VIEW_BLOB,         "Show blob view"), \
865         REQ_(VIEW_BLAME,        "Show blame view"), \
866         REQ_(VIEW_BRANCH,       "Show branch view"), \
867         REQ_(VIEW_HELP,         "Show help page"), \
868         REQ_(VIEW_PAGER,        "Show pager view"), \
869         REQ_(VIEW_STATUS,       "Show status view"), \
870         REQ_(VIEW_STAGE,        "Show stage view"), \
871         \
872         REQ_GROUP("View manipulation") \
873         REQ_(ENTER,             "Enter current line and scroll"), \
874         REQ_(NEXT,              "Move to next"), \
875         REQ_(PREVIOUS,          "Move to previous"), \
876         REQ_(PARENT,            "Move to parent"), \
877         REQ_(VIEW_NEXT,         "Move focus to next view"), \
878         REQ_(REFRESH,           "Reload and refresh"), \
879         REQ_(MAXIMIZE,          "Maximize the current view"), \
880         REQ_(VIEW_CLOSE,        "Close the current view"), \
881         REQ_(QUIT,              "Close all views and quit"), \
882         \
883         REQ_GROUP("View specific requests") \
884         REQ_(STATUS_UPDATE,     "Update file status"), \
885         REQ_(STATUS_REVERT,     "Revert file changes"), \
886         REQ_(STATUS_MERGE,      "Merge file using external tool"), \
887         REQ_(STAGE_NEXT,        "Find next chunk to stage"), \
888         \
889         REQ_GROUP("Cursor navigation") \
890         REQ_(MOVE_UP,           "Move cursor one line up"), \
891         REQ_(MOVE_DOWN,         "Move cursor one line down"), \
892         REQ_(MOVE_PAGE_DOWN,    "Move cursor one page down"), \
893         REQ_(MOVE_PAGE_UP,      "Move cursor one page up"), \
894         REQ_(MOVE_FIRST_LINE,   "Move cursor to first line"), \
895         REQ_(MOVE_LAST_LINE,    "Move cursor to last line"), \
896         \
897         REQ_GROUP("Scrolling") \
898         REQ_(SCROLL_LEFT,       "Scroll two columns left"), \
899         REQ_(SCROLL_RIGHT,      "Scroll two columns right"), \
900         REQ_(SCROLL_LINE_UP,    "Scroll one line up"), \
901         REQ_(SCROLL_LINE_DOWN,  "Scroll one line down"), \
902         REQ_(SCROLL_PAGE_UP,    "Scroll one page up"), \
903         REQ_(SCROLL_PAGE_DOWN,  "Scroll one page down"), \
904         \
905         REQ_GROUP("Searching") \
906         REQ_(SEARCH,            "Search the view"), \
907         REQ_(SEARCH_BACK,       "Search backwards in the view"), \
908         REQ_(FIND_NEXT,         "Find next search match"), \
909         REQ_(FIND_PREV,         "Find previous search match"), \
910         \
911         REQ_GROUP("Option manipulation") \
912         REQ_(OPTIONS,           "Open option menu"), \
913         REQ_(TOGGLE_LINENO,     "Toggle line numbers"), \
914         REQ_(TOGGLE_DATE,       "Toggle date display"), \
915         REQ_(TOGGLE_DATE_SHORT, "Toggle short (date-only) dates"), \
916         REQ_(TOGGLE_AUTHOR,     "Toggle author display"), \
917         REQ_(TOGGLE_REV_GRAPH,  "Toggle revision graph visualization"), \
918         REQ_(TOGGLE_REFS,       "Toggle reference display (tags/branches)"), \
919         REQ_(TOGGLE_SORT_ORDER, "Toggle ascending/descending sort order"), \
920         REQ_(TOGGLE_SORT_FIELD, "Toggle field to sort by"), \
921         \
922         REQ_GROUP("Misc") \
923         REQ_(PROMPT,            "Bring up the prompt"), \
924         REQ_(SCREEN_REDRAW,     "Redraw the screen"), \
925         REQ_(SHOW_VERSION,      "Show version information"), \
926         REQ_(STOP_LOADING,      "Stop all loading views"), \
927         REQ_(EDIT,              "Open in editor"), \
928         REQ_(NONE,              "Do nothing")
931 /* User action requests. */
932 enum request {
933 #define REQ_GROUP(help)
934 #define REQ_(req, help) REQ_##req
936         /* Offset all requests to avoid conflicts with ncurses getch values. */
937         REQ_OFFSET = KEY_MAX + 1,
938         REQ_INFO
940 #undef  REQ_GROUP
941 #undef  REQ_
942 };
944 struct request_info {
945         enum request request;
946         const char *name;
947         int namelen;
948         const char *help;
949 };
951 static const struct request_info req_info[] = {
952 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
953 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
954         REQ_INFO
955 #undef  REQ_GROUP
956 #undef  REQ_
957 };
959 static enum request
960 get_request(const char *name)
962         int namelen = strlen(name);
963         int i;
965         for (i = 0; i < ARRAY_SIZE(req_info); i++)
966                 if (req_info[i].namelen == namelen &&
967                     !string_enum_compare(req_info[i].name, name, namelen))
968                         return req_info[i].request;
970         return REQ_NONE;
974 /*
975  * Options
976  */
978 /* Option and state variables. */
979 static enum date opt_date               = DATE_DEFAULT;
980 static bool opt_author                  = TRUE;
981 static bool opt_line_number             = FALSE;
982 static bool opt_line_graphics           = TRUE;
983 static bool opt_rev_graph               = FALSE;
984 static bool opt_show_refs               = TRUE;
985 static int opt_num_interval             = 5;
986 static double opt_hscroll               = 0.50;
987 static double opt_scale_split_view      = 2.0 / 3.0;
988 static int opt_tab_size                 = 8;
989 static int opt_author_cols              = 19;
990 static char opt_path[SIZEOF_STR]        = "";
991 static char opt_file[SIZEOF_STR]        = "";
992 static char opt_ref[SIZEOF_REF]         = "";
993 static char opt_head[SIZEOF_REF]        = "";
994 static char opt_head_rev[SIZEOF_REV]    = "";
995 static char opt_remote[SIZEOF_REF]      = "";
996 static char opt_encoding[20]            = "UTF-8";
997 static char opt_codeset[20]             = "UTF-8";
998 static iconv_t opt_iconv_in             = ICONV_NONE;
999 static iconv_t opt_iconv_out            = ICONV_NONE;
1000 static char opt_search[SIZEOF_STR]      = "";
1001 static char opt_cdup[SIZEOF_STR]        = "";
1002 static char opt_prefix[SIZEOF_STR]      = "";
1003 static char opt_git_dir[SIZEOF_STR]     = "";
1004 static signed char opt_is_inside_work_tree      = -1; /* set to TRUE or FALSE */
1005 static char opt_editor[SIZEOF_STR]      = "";
1006 static FILE *opt_tty                    = NULL;
1008 #define is_initial_commit()     (!*opt_head_rev)
1009 #define is_head_commit(rev)     (!strcmp((rev), "HEAD") || !strcmp(opt_head_rev, (rev)))
1010 #define mkdate(time)            string_date(time, opt_date)
1013 /*
1014  * Line-oriented content detection.
1015  */
1017 #define LINE_INFO \
1018 LINE(DIFF_HEADER,  "diff --git ",       COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1019 LINE(DIFF_CHUNK,   "@@",                COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1020 LINE(DIFF_ADD,     "+",                 COLOR_GREEN,    COLOR_DEFAULT,  0), \
1021 LINE(DIFF_DEL,     "-",                 COLOR_RED,      COLOR_DEFAULT,  0), \
1022 LINE(DIFF_INDEX,        "index ",         COLOR_BLUE,   COLOR_DEFAULT,  0), \
1023 LINE(DIFF_OLDMODE,      "old file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
1024 LINE(DIFF_NEWMODE,      "new file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
1025 LINE(DIFF_COPY_FROM,    "copy from",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
1026 LINE(DIFF_COPY_TO,      "copy to",        COLOR_YELLOW, COLOR_DEFAULT,  0), \
1027 LINE(DIFF_RENAME_FROM,  "rename from",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
1028 LINE(DIFF_RENAME_TO,    "rename to",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
1029 LINE(DIFF_SIMILARITY,   "similarity ",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
1030 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
1031 LINE(DIFF_TREE,         "diff-tree ",     COLOR_BLUE,   COLOR_DEFAULT,  0), \
1032 LINE(PP_AUTHOR,    "Author: ",          COLOR_CYAN,     COLOR_DEFAULT,  0), \
1033 LINE(PP_COMMIT,    "Commit: ",          COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1034 LINE(PP_MERGE,     "Merge: ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
1035 LINE(PP_DATE,      "Date:   ",          COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1036 LINE(PP_ADATE,     "AuthorDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1037 LINE(PP_CDATE,     "CommitDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1038 LINE(PP_REFS,      "Refs: ",            COLOR_RED,      COLOR_DEFAULT,  0), \
1039 LINE(COMMIT,       "commit ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
1040 LINE(PARENT,       "parent ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
1041 LINE(TREE,         "tree ",             COLOR_BLUE,     COLOR_DEFAULT,  0), \
1042 LINE(AUTHOR,       "author ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
1043 LINE(COMMITTER,    "committer ",        COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1044 LINE(SIGNOFF,      "    Signed-off-by", COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1045 LINE(ACKED,        "    Acked-by",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1046 LINE(DEFAULT,      "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
1047 LINE(CURSOR,       "",                  COLOR_WHITE,    COLOR_GREEN,    A_BOLD), \
1048 LINE(STATUS,       "",                  COLOR_GREEN,    COLOR_DEFAULT,  0), \
1049 LINE(DELIMITER,    "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1050 LINE(DATE,         "",                  COLOR_BLUE,     COLOR_DEFAULT,  0), \
1051 LINE(MODE,         "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1052 LINE(LINE_NUMBER,  "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1053 LINE(TITLE_BLUR,   "",                  COLOR_WHITE,    COLOR_BLUE,     0), \
1054 LINE(TITLE_FOCUS,  "",                  COLOR_WHITE,    COLOR_BLUE,     A_BOLD), \
1055 LINE(MAIN_COMMIT,  "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
1056 LINE(MAIN_TAG,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  A_BOLD), \
1057 LINE(MAIN_LOCAL_TAG,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1058 LINE(MAIN_REMOTE,  "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1059 LINE(MAIN_TRACKED, "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_BOLD), \
1060 LINE(MAIN_REF,     "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1061 LINE(MAIN_HEAD,    "",                  COLOR_CYAN,     COLOR_DEFAULT,  A_BOLD), \
1062 LINE(MAIN_REVGRAPH,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1063 LINE(TREE_HEAD,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_BOLD), \
1064 LINE(TREE_DIR,     "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_NORMAL), \
1065 LINE(TREE_FILE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
1066 LINE(STAT_HEAD,    "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1067 LINE(STAT_SECTION, "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1068 LINE(STAT_NONE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
1069 LINE(STAT_STAGED,  "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1070 LINE(STAT_UNSTAGED,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1071 LINE(STAT_UNTRACKED,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1072 LINE(HELP_KEYMAP,  "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1073 LINE(HELP_GROUP,   "",                  COLOR_BLUE,     COLOR_DEFAULT,  0), \
1074 LINE(BLAME_ID,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0)
1076 enum line_type {
1077 #define LINE(type, line, fg, bg, attr) \
1078         LINE_##type
1079         LINE_INFO,
1080         LINE_NONE
1081 #undef  LINE
1082 };
1084 struct line_info {
1085         const char *name;       /* Option name. */
1086         int namelen;            /* Size of option name. */
1087         const char *line;       /* The start of line to match. */
1088         int linelen;            /* Size of string to match. */
1089         int fg, bg, attr;       /* Color and text attributes for the lines. */
1090 };
1092 static struct line_info line_info[] = {
1093 #define LINE(type, line, fg, bg, attr) \
1094         { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
1095         LINE_INFO
1096 #undef  LINE
1097 };
1099 static enum line_type
1100 get_line_type(const char *line)
1102         int linelen = strlen(line);
1103         enum line_type type;
1105         for (type = 0; type < ARRAY_SIZE(line_info); type++)
1106                 /* Case insensitive search matches Signed-off-by lines better. */
1107                 if (linelen >= line_info[type].linelen &&
1108                     !strncasecmp(line_info[type].line, line, line_info[type].linelen))
1109                         return type;
1111         return LINE_DEFAULT;
1114 static inline int
1115 get_line_attr(enum line_type type)
1117         assert(type < ARRAY_SIZE(line_info));
1118         return COLOR_PAIR(type) | line_info[type].attr;
1121 static struct line_info *
1122 get_line_info(const char *name)
1124         size_t namelen = strlen(name);
1125         enum line_type type;
1127         for (type = 0; type < ARRAY_SIZE(line_info); type++)
1128                 if (namelen == line_info[type].namelen &&
1129                     !string_enum_compare(line_info[type].name, name, namelen))
1130                         return &line_info[type];
1132         return NULL;
1135 static void
1136 init_colors(void)
1138         int default_bg = line_info[LINE_DEFAULT].bg;
1139         int default_fg = line_info[LINE_DEFAULT].fg;
1140         enum line_type type;
1142         start_color();
1144         if (assume_default_colors(default_fg, default_bg) == ERR) {
1145                 default_bg = COLOR_BLACK;
1146                 default_fg = COLOR_WHITE;
1147         }
1149         for (type = 0; type < ARRAY_SIZE(line_info); type++) {
1150                 struct line_info *info = &line_info[type];
1151                 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
1152                 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
1154                 init_pair(type, fg, bg);
1155         }
1158 struct line {
1159         enum line_type type;
1161         /* State flags */
1162         unsigned int selected:1;
1163         unsigned int dirty:1;
1164         unsigned int cleareol:1;
1165         unsigned int other:16;
1167         void *data;             /* User data */
1168 };
1171 /*
1172  * Keys
1173  */
1175 struct keybinding {
1176         int alias;
1177         enum request request;
1178 };
1180 static const struct keybinding default_keybindings[] = {
1181         /* View switching */
1182         { 'm',          REQ_VIEW_MAIN },
1183         { 'd',          REQ_VIEW_DIFF },
1184         { 'l',          REQ_VIEW_LOG },
1185         { 't',          REQ_VIEW_TREE },
1186         { 'f',          REQ_VIEW_BLOB },
1187         { 'B',          REQ_VIEW_BLAME },
1188         { 'H',          REQ_VIEW_BRANCH },
1189         { 'p',          REQ_VIEW_PAGER },
1190         { 'h',          REQ_VIEW_HELP },
1191         { 'S',          REQ_VIEW_STATUS },
1192         { 'c',          REQ_VIEW_STAGE },
1194         /* View manipulation */
1195         { 'q',          REQ_VIEW_CLOSE },
1196         { KEY_TAB,      REQ_VIEW_NEXT },
1197         { KEY_RETURN,   REQ_ENTER },
1198         { KEY_UP,       REQ_PREVIOUS },
1199         { KEY_DOWN,     REQ_NEXT },
1200         { 'R',          REQ_REFRESH },
1201         { KEY_F(5),     REQ_REFRESH },
1202         { 'O',          REQ_MAXIMIZE },
1204         /* Cursor navigation */
1205         { 'k',          REQ_MOVE_UP },
1206         { 'j',          REQ_MOVE_DOWN },
1207         { KEY_HOME,     REQ_MOVE_FIRST_LINE },
1208         { KEY_END,      REQ_MOVE_LAST_LINE },
1209         { KEY_NPAGE,    REQ_MOVE_PAGE_DOWN },
1210         { ' ',          REQ_MOVE_PAGE_DOWN },
1211         { KEY_PPAGE,    REQ_MOVE_PAGE_UP },
1212         { 'b',          REQ_MOVE_PAGE_UP },
1213         { '-',          REQ_MOVE_PAGE_UP },
1215         /* Scrolling */
1216         { KEY_LEFT,     REQ_SCROLL_LEFT },
1217         { KEY_RIGHT,    REQ_SCROLL_RIGHT },
1218         { KEY_IC,       REQ_SCROLL_LINE_UP },
1219         { KEY_DC,       REQ_SCROLL_LINE_DOWN },
1220         { 'w',          REQ_SCROLL_PAGE_UP },
1221         { 's',          REQ_SCROLL_PAGE_DOWN },
1223         /* Searching */
1224         { '/',          REQ_SEARCH },
1225         { '?',          REQ_SEARCH_BACK },
1226         { 'n',          REQ_FIND_NEXT },
1227         { 'N',          REQ_FIND_PREV },
1229         /* Misc */
1230         { 'Q',          REQ_QUIT },
1231         { 'z',          REQ_STOP_LOADING },
1232         { 'v',          REQ_SHOW_VERSION },
1233         { 'r',          REQ_SCREEN_REDRAW },
1234         { 'o',          REQ_OPTIONS },
1235         { '.',          REQ_TOGGLE_LINENO },
1236         { 'D',          REQ_TOGGLE_DATE },
1237         { 'A',          REQ_TOGGLE_AUTHOR },
1238         { 'g',          REQ_TOGGLE_REV_GRAPH },
1239         { 'F',          REQ_TOGGLE_REFS },
1240         { 'I',          REQ_TOGGLE_SORT_ORDER },
1241         { 'i',          REQ_TOGGLE_SORT_FIELD },
1242         { ':',          REQ_PROMPT },
1243         { 'u',          REQ_STATUS_UPDATE },
1244         { '!',          REQ_STATUS_REVERT },
1245         { 'M',          REQ_STATUS_MERGE },
1246         { '@',          REQ_STAGE_NEXT },
1247         { ',',          REQ_PARENT },
1248         { 'e',          REQ_EDIT },
1249 };
1251 #define KEYMAP_INFO \
1252         KEYMAP_(GENERIC), \
1253         KEYMAP_(MAIN), \
1254         KEYMAP_(DIFF), \
1255         KEYMAP_(LOG), \
1256         KEYMAP_(TREE), \
1257         KEYMAP_(BLOB), \
1258         KEYMAP_(BLAME), \
1259         KEYMAP_(BRANCH), \
1260         KEYMAP_(PAGER), \
1261         KEYMAP_(HELP), \
1262         KEYMAP_(STATUS), \
1263         KEYMAP_(STAGE)
1265 enum keymap {
1266 #define KEYMAP_(name) KEYMAP_##name
1267         KEYMAP_INFO
1268 #undef  KEYMAP_
1269 };
1271 static const struct enum_map keymap_table[] = {
1272 #define KEYMAP_(name) ENUM_MAP(#name, KEYMAP_##name)
1273         KEYMAP_INFO
1274 #undef  KEYMAP_
1275 };
1277 #define set_keymap(map, name) map_enum(map, keymap_table, name)
1279 struct keybinding_table {
1280         struct keybinding *data;
1281         size_t size;
1282 };
1284 static struct keybinding_table keybindings[ARRAY_SIZE(keymap_table)];
1286 static void
1287 add_keybinding(enum keymap keymap, enum request request, int key)
1289         struct keybinding_table *table = &keybindings[keymap];
1291         table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data));
1292         if (!table->data)
1293                 die("Failed to allocate keybinding");
1294         table->data[table->size].alias = key;
1295         table->data[table->size++].request = request;
1298 /* Looks for a key binding first in the given map, then in the generic map, and
1299  * lastly in the default keybindings. */
1300 static enum request
1301 get_keybinding(enum keymap keymap, int key)
1303         size_t i;
1305         for (i = 0; i < keybindings[keymap].size; i++)
1306                 if (keybindings[keymap].data[i].alias == key)
1307                         return keybindings[keymap].data[i].request;
1309         for (i = 0; i < keybindings[KEYMAP_GENERIC].size; i++)
1310                 if (keybindings[KEYMAP_GENERIC].data[i].alias == key)
1311                         return keybindings[KEYMAP_GENERIC].data[i].request;
1313         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1314                 if (default_keybindings[i].alias == key)
1315                         return default_keybindings[i].request;
1317         return (enum request) key;
1321 struct key {
1322         const char *name;
1323         int value;
1324 };
1326 static const struct key key_table[] = {
1327         { "Enter",      KEY_RETURN },
1328         { "Space",      ' ' },
1329         { "Backspace",  KEY_BACKSPACE },
1330         { "Tab",        KEY_TAB },
1331         { "Escape",     KEY_ESC },
1332         { "Left",       KEY_LEFT },
1333         { "Right",      KEY_RIGHT },
1334         { "Up",         KEY_UP },
1335         { "Down",       KEY_DOWN },
1336         { "Insert",     KEY_IC },
1337         { "Delete",     KEY_DC },
1338         { "Hash",       '#' },
1339         { "Home",       KEY_HOME },
1340         { "End",        KEY_END },
1341         { "PageUp",     KEY_PPAGE },
1342         { "PageDown",   KEY_NPAGE },
1343         { "F1",         KEY_F(1) },
1344         { "F2",         KEY_F(2) },
1345         { "F3",         KEY_F(3) },
1346         { "F4",         KEY_F(4) },
1347         { "F5",         KEY_F(5) },
1348         { "F6",         KEY_F(6) },
1349         { "F7",         KEY_F(7) },
1350         { "F8",         KEY_F(8) },
1351         { "F9",         KEY_F(9) },
1352         { "F10",        KEY_F(10) },
1353         { "F11",        KEY_F(11) },
1354         { "F12",        KEY_F(12) },
1355 };
1357 static int
1358 get_key_value(const char *name)
1360         int i;
1362         for (i = 0; i < ARRAY_SIZE(key_table); i++)
1363                 if (!strcasecmp(key_table[i].name, name))
1364                         return key_table[i].value;
1366         if (strlen(name) == 1 && isprint(*name))
1367                 return (int) *name;
1369         return ERR;
1372 static const char *
1373 get_key_name(int key_value)
1375         static char key_char[] = "'X'";
1376         const char *seq = NULL;
1377         int key;
1379         for (key = 0; key < ARRAY_SIZE(key_table); key++)
1380                 if (key_table[key].value == key_value)
1381                         seq = key_table[key].name;
1383         if (seq == NULL &&
1384             key_value < 127 &&
1385             isprint(key_value)) {
1386                 key_char[1] = (char) key_value;
1387                 seq = key_char;
1388         }
1390         return seq ? seq : "(no key)";
1393 static bool
1394 append_key(char *buf, size_t *pos, const struct keybinding *keybinding)
1396         const char *sep = *pos > 0 ? ", " : "";
1397         const char *keyname = get_key_name(keybinding->alias);
1399         return string_nformat(buf, BUFSIZ, pos, "%s%s", sep, keyname);
1402 static bool
1403 append_keymap_request_keys(char *buf, size_t *pos, enum request request,
1404                            enum keymap keymap, bool all)
1406         int i;
1408         for (i = 0; i < keybindings[keymap].size; i++) {
1409                 if (keybindings[keymap].data[i].request == request) {
1410                         if (!append_key(buf, pos, &keybindings[keymap].data[i]))
1411                                 return FALSE;
1412                         if (!all)
1413                                 break;
1414                 }
1415         }
1417         return TRUE;
1420 #define get_key(keymap, request) get_keys(keymap, request, FALSE)
1422 static const char *
1423 get_keys(enum keymap keymap, enum request request, bool all)
1425         static char buf[BUFSIZ];
1426         size_t pos = 0;
1427         int i;
1429         buf[pos] = 0;
1431         if (!append_keymap_request_keys(buf, &pos, request, keymap, all))
1432                 return "Too many keybindings!";
1433         if (pos > 0 && !all)
1434                 return buf;
1436         if (keymap != KEYMAP_GENERIC) {
1437                 /* Only the generic keymap includes the default keybindings when
1438                  * listing all keys. */
1439                 if (all)
1440                         return buf;
1442                 if (!append_keymap_request_keys(buf, &pos, request, KEYMAP_GENERIC, all))
1443                         return "Too many keybindings!";
1444                 if (pos)
1445                         return buf;
1446         }
1448         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1449                 if (default_keybindings[i].request == request) {
1450                         if (!append_key(buf, &pos, &default_keybindings[i]))
1451                                 return "Too many keybindings!";
1452                         if (!all)
1453                                 return buf;
1454                 }
1455         }
1457         return buf;
1460 struct run_request {
1461         enum keymap keymap;
1462         int key;
1463         const char *argv[SIZEOF_ARG];
1464 };
1466 static struct run_request *run_request;
1467 static size_t run_requests;
1469 DEFINE_ALLOCATOR(realloc_run_requests, struct run_request, 8)
1471 static enum request
1472 add_run_request(enum keymap keymap, int key, int argc, const char **argv)
1474         struct run_request *req;
1476         if (argc >= ARRAY_SIZE(req->argv) - 1)
1477                 return REQ_NONE;
1479         if (!realloc_run_requests(&run_request, run_requests, 1))
1480                 return REQ_NONE;
1482         req = &run_request[run_requests];
1483         req->keymap = keymap;
1484         req->key = key;
1485         req->argv[0] = NULL;
1487         if (!format_argv(req->argv, argv, FORMAT_NONE))
1488                 return REQ_NONE;
1490         return REQ_NONE + ++run_requests;
1493 static struct run_request *
1494 get_run_request(enum request request)
1496         if (request <= REQ_NONE)
1497                 return NULL;
1498         return &run_request[request - REQ_NONE - 1];
1501 static void
1502 add_builtin_run_requests(void)
1504         const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1505         const char *commit[] = { "git", "commit", NULL };
1506         const char *gc[] = { "git", "gc", NULL };
1507         struct {
1508                 enum keymap keymap;
1509                 int key;
1510                 int argc;
1511                 const char **argv;
1512         } reqs[] = {
1513                 { KEYMAP_MAIN,    'C', ARRAY_SIZE(cherry_pick) - 1, cherry_pick },
1514                 { KEYMAP_STATUS,  'C', ARRAY_SIZE(commit) - 1, commit },
1515                 { KEYMAP_GENERIC, 'G', ARRAY_SIZE(gc) - 1, gc },
1516         };
1517         int i;
1519         for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1520                 enum request req;
1522                 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argc, reqs[i].argv);
1523                 if (req != REQ_NONE)
1524                         add_keybinding(reqs[i].keymap, req, reqs[i].key);
1525         }
1528 /*
1529  * User config file handling.
1530  */
1532 static int   config_lineno;
1533 static bool  config_errors;
1534 static const char *config_msg;
1536 static const struct enum_map color_map[] = {
1537 #define COLOR_MAP(name) ENUM_MAP(#name, COLOR_##name)
1538         COLOR_MAP(DEFAULT),
1539         COLOR_MAP(BLACK),
1540         COLOR_MAP(BLUE),
1541         COLOR_MAP(CYAN),
1542         COLOR_MAP(GREEN),
1543         COLOR_MAP(MAGENTA),
1544         COLOR_MAP(RED),
1545         COLOR_MAP(WHITE),
1546         COLOR_MAP(YELLOW),
1547 };
1549 static const struct enum_map attr_map[] = {
1550 #define ATTR_MAP(name) ENUM_MAP(#name, A_##name)
1551         ATTR_MAP(NORMAL),
1552         ATTR_MAP(BLINK),
1553         ATTR_MAP(BOLD),
1554         ATTR_MAP(DIM),
1555         ATTR_MAP(REVERSE),
1556         ATTR_MAP(STANDOUT),
1557         ATTR_MAP(UNDERLINE),
1558 };
1560 #define set_attribute(attr, name)       map_enum(attr, attr_map, name)
1562 static int parse_step(double *opt, const char *arg)
1564         *opt = atoi(arg);
1565         if (!strchr(arg, '%'))
1566                 return OK;
1568         /* "Shift down" so 100% and 1 does not conflict. */
1569         *opt = (*opt - 1) / 100;
1570         if (*opt >= 1.0) {
1571                 *opt = 0.99;
1572                 config_msg = "Step value larger than 100%";
1573                 return ERR;
1574         }
1575         if (*opt < 0.0) {
1576                 *opt = 1;
1577                 config_msg = "Invalid step value";
1578                 return ERR;
1579         }
1580         return OK;
1583 static int
1584 parse_int(int *opt, const char *arg, int min, int max)
1586         int value = atoi(arg);
1588         if (min <= value && value <= max) {
1589                 *opt = value;
1590                 return OK;
1591         }
1593         config_msg = "Integer value out of bound";
1594         return ERR;
1597 static bool
1598 set_color(int *color, const char *name)
1600         if (map_enum(color, color_map, name))
1601                 return TRUE;
1602         if (!prefixcmp(name, "color"))
1603                 return parse_int(color, name + 5, 0, 255) == OK;
1604         return FALSE;
1607 /* Wants: object fgcolor bgcolor [attribute] */
1608 static int
1609 option_color_command(int argc, const char *argv[])
1611         struct line_info *info;
1613         if (argc < 3) {
1614                 config_msg = "Wrong number of arguments given to color command";
1615                 return ERR;
1616         }
1618         info = get_line_info(argv[0]);
1619         if (!info) {
1620                 static const struct enum_map obsolete[] = {
1621                         ENUM_MAP("main-delim",  LINE_DELIMITER),
1622                         ENUM_MAP("main-date",   LINE_DATE),
1623                         ENUM_MAP("main-author", LINE_AUTHOR),
1624                 };
1625                 int index;
1627                 if (!map_enum(&index, obsolete, argv[0])) {
1628                         config_msg = "Unknown color name";
1629                         return ERR;
1630                 }
1631                 info = &line_info[index];
1632         }
1634         if (!set_color(&info->fg, argv[1]) ||
1635             !set_color(&info->bg, argv[2])) {
1636                 config_msg = "Unknown color";
1637                 return ERR;
1638         }
1640         info->attr = 0;
1641         while (argc-- > 3) {
1642                 int attr;
1644                 if (!set_attribute(&attr, argv[argc])) {
1645                         config_msg = "Unknown attribute";
1646                         return ERR;
1647                 }
1648                 info->attr |= attr;
1649         }
1651         return OK;
1654 static int parse_bool(bool *opt, const char *arg)
1656         *opt = (!strcmp(arg, "1") || !strcmp(arg, "true") || !strcmp(arg, "yes"))
1657                 ? TRUE : FALSE;
1658         return OK;
1661 static int
1662 parse_string(char *opt, const char *arg, size_t optsize)
1664         int arglen = strlen(arg);
1666         switch (arg[0]) {
1667         case '\"':
1668         case '\'':
1669                 if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1670                         config_msg = "Unmatched quotation";
1671                         return ERR;
1672                 }
1673                 arg += 1; arglen -= 2;
1674         default:
1675                 string_ncopy_do(opt, optsize, arg, arglen);
1676                 return OK;
1677         }
1680 /* Wants: name = value */
1681 static int
1682 option_set_command(int argc, const char *argv[])
1684         if (argc != 3) {
1685                 config_msg = "Wrong number of arguments given to set command";
1686                 return ERR;
1687         }
1689         if (strcmp(argv[1], "=")) {
1690                 config_msg = "No value assigned";
1691                 return ERR;
1692         }
1694         if (!strcmp(argv[0], "show-author"))
1695                 return parse_bool(&opt_author, argv[2]);
1697         if (!strcmp(argv[0], "show-date")) {
1698                 bool show_date;
1700                 if (!strcmp(argv[2], "relative")) {
1701                         opt_date = DATE_RELATIVE;
1702                         return OK;
1703                 } else if (!strcmp(argv[2], "short")) {
1704                         opt_date = DATE_SHORT;
1705                         return OK;
1706                 } else if (parse_bool(&show_date, argv[2]) == OK) {
1707                         opt_date = show_date ? DATE_DEFAULT : DATE_NONE;
1708                         return OK;
1709                 }
1710                 return ERR;
1711         }
1713         if (!strcmp(argv[0], "show-rev-graph"))
1714                 return parse_bool(&opt_rev_graph, argv[2]);
1716         if (!strcmp(argv[0], "show-refs"))
1717                 return parse_bool(&opt_show_refs, argv[2]);
1719         if (!strcmp(argv[0], "show-line-numbers"))
1720                 return parse_bool(&opt_line_number, argv[2]);
1722         if (!strcmp(argv[0], "line-graphics"))
1723                 return parse_bool(&opt_line_graphics, argv[2]);
1725         if (!strcmp(argv[0], "line-number-interval"))
1726                 return parse_int(&opt_num_interval, argv[2], 1, 1024);
1728         if (!strcmp(argv[0], "author-width"))
1729                 return parse_int(&opt_author_cols, argv[2], 0, 1024);
1731         if (!strcmp(argv[0], "horizontal-scroll"))
1732                 return parse_step(&opt_hscroll, argv[2]);
1734         if (!strcmp(argv[0], "split-view-height"))
1735                 return parse_step(&opt_scale_split_view, argv[2]);
1737         if (!strcmp(argv[0], "tab-size"))
1738                 return parse_int(&opt_tab_size, argv[2], 1, 1024);
1740         if (!strcmp(argv[0], "commit-encoding"))
1741                 return parse_string(opt_encoding, argv[2], sizeof(opt_encoding));
1743         config_msg = "Unknown variable name";
1744         return ERR;
1747 /* Wants: mode request key */
1748 static int
1749 option_bind_command(int argc, const char *argv[])
1751         enum request request;
1752         int keymap = -1;
1753         int key;
1755         if (argc < 3) {
1756                 config_msg = "Wrong number of arguments given to bind command";
1757                 return ERR;
1758         }
1760         if (set_keymap(&keymap, argv[0]) == ERR) {
1761                 config_msg = "Unknown key map";
1762                 return ERR;
1763         }
1765         key = get_key_value(argv[1]);
1766         if (key == ERR) {
1767                 config_msg = "Unknown key";
1768                 return ERR;
1769         }
1771         request = get_request(argv[2]);
1772         if (request == REQ_NONE) {
1773                 static const struct enum_map obsolete[] = {
1774                         ENUM_MAP("cherry-pick",         REQ_NONE),
1775                         ENUM_MAP("screen-resize",       REQ_NONE),
1776                         ENUM_MAP("tree-parent",         REQ_PARENT),
1777                 };
1778                 int alias;
1780                 if (map_enum(&alias, obsolete, argv[2])) {
1781                         if (alias != REQ_NONE)
1782                                 add_keybinding(keymap, alias, key);
1783                         config_msg = "Obsolete request name";
1784                         return ERR;
1785                 }
1786         }
1787         if (request == REQ_NONE && *argv[2]++ == '!')
1788                 request = add_run_request(keymap, key, argc - 2, argv + 2);
1789         if (request == REQ_NONE) {
1790                 config_msg = "Unknown request name";
1791                 return ERR;
1792         }
1794         add_keybinding(keymap, request, key);
1796         return OK;
1799 static int
1800 set_option(const char *opt, char *value)
1802         const char *argv[SIZEOF_ARG];
1803         int argc = 0;
1805         if (!argv_from_string(argv, &argc, value)) {
1806                 config_msg = "Too many option arguments";
1807                 return ERR;
1808         }
1810         if (!strcmp(opt, "color"))
1811                 return option_color_command(argc, argv);
1813         if (!strcmp(opt, "set"))
1814                 return option_set_command(argc, argv);
1816         if (!strcmp(opt, "bind"))
1817                 return option_bind_command(argc, argv);
1819         config_msg = "Unknown option command";
1820         return ERR;
1823 static int
1824 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
1826         int status = OK;
1828         config_lineno++;
1829         config_msg = "Internal error";
1831         /* Check for comment markers, since read_properties() will
1832          * only ensure opt and value are split at first " \t". */
1833         optlen = strcspn(opt, "#");
1834         if (optlen == 0)
1835                 return OK;
1837         if (opt[optlen] != 0) {
1838                 config_msg = "No option value";
1839                 status = ERR;
1841         }  else {
1842                 /* Look for comment endings in the value. */
1843                 size_t len = strcspn(value, "#");
1845                 if (len < valuelen) {
1846                         valuelen = len;
1847                         value[valuelen] = 0;
1848                 }
1850                 status = set_option(opt, value);
1851         }
1853         if (status == ERR) {
1854                 warn("Error on line %d, near '%.*s': %s",
1855                      config_lineno, (int) optlen, opt, config_msg);
1856                 config_errors = TRUE;
1857         }
1859         /* Always keep going if errors are encountered. */
1860         return OK;
1863 static void
1864 load_option_file(const char *path)
1866         struct io io = {};
1868         /* It's OK that the file doesn't exist. */
1869         if (!io_open(&io, "%s", path))
1870                 return;
1872         config_lineno = 0;
1873         config_errors = FALSE;
1875         if (io_load(&io, " \t", read_option) == ERR ||
1876             config_errors == TRUE)
1877                 warn("Errors while loading %s.", path);
1880 static int
1881 load_options(void)
1883         const char *home = getenv("HOME");
1884         const char *tigrc_user = getenv("TIGRC_USER");
1885         const char *tigrc_system = getenv("TIGRC_SYSTEM");
1886         char buf[SIZEOF_STR];
1888         add_builtin_run_requests();
1890         if (!tigrc_system)
1891                 tigrc_system = SYSCONFDIR "/tigrc";
1892         load_option_file(tigrc_system);
1894         if (!tigrc_user) {
1895                 if (!home || !string_format(buf, "%s/.tigrc", home))
1896                         return ERR;
1897                 tigrc_user = buf;
1898         }
1899         load_option_file(tigrc_user);
1901         return OK;
1905 /*
1906  * The viewer
1907  */
1909 struct view;
1910 struct view_ops;
1912 /* The display array of active views and the index of the current view. */
1913 static struct view *display[2];
1914 static unsigned int current_view;
1916 #define foreach_displayed_view(view, i) \
1917         for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1919 #define displayed_views()       (display[1] != NULL ? 2 : 1)
1921 /* Current head and commit ID */
1922 static char ref_blob[SIZEOF_REF]        = "";
1923 static char ref_commit[SIZEOF_REF]      = "HEAD";
1924 static char ref_head[SIZEOF_REF]        = "HEAD";
1926 struct view {
1927         const char *name;       /* View name */
1928         const char *cmd_env;    /* Command line set via environment */
1929         const char *id;         /* Points to either of ref_{head,commit,blob} */
1931         struct view_ops *ops;   /* View operations */
1933         enum keymap keymap;     /* What keymap does this view have */
1934         bool git_dir;           /* Whether the view requires a git directory. */
1936         char ref[SIZEOF_REF];   /* Hovered commit reference */
1937         char vid[SIZEOF_REF];   /* View ID. Set to id member when updating. */
1939         int height, width;      /* The width and height of the main window */
1940         WINDOW *win;            /* The main window */
1941         WINDOW *title;          /* The title window living below the main window */
1943         /* Navigation */
1944         unsigned long offset;   /* Offset of the window top */
1945         unsigned long yoffset;  /* Offset from the window side. */
1946         unsigned long lineno;   /* Current line number */
1947         unsigned long p_offset; /* Previous offset of the window top */
1948         unsigned long p_yoffset;/* Previous offset from the window side */
1949         unsigned long p_lineno; /* Previous current line number */
1950         bool p_restore;         /* Should the previous position be restored. */
1952         /* Searching */
1953         char grep[SIZEOF_STR];  /* Search string */
1954         regex_t *regex;         /* Pre-compiled regexp */
1956         /* If non-NULL, points to the view that opened this view. If this view
1957          * is closed tig will switch back to the parent view. */
1958         struct view *parent;
1960         /* Buffering */
1961         size_t lines;           /* Total number of lines */
1962         struct line *line;      /* Line index */
1963         unsigned int digits;    /* Number of digits in the lines member. */
1965         /* Drawing */
1966         struct line *curline;   /* Line currently being drawn. */
1967         enum line_type curtype; /* Attribute currently used for drawing. */
1968         unsigned long col;      /* Column when drawing. */
1969         bool has_scrolled;      /* View was scrolled. */
1971         /* Loading */
1972         struct io io;
1973         struct io *pipe;
1974         time_t start_time;
1975         time_t update_secs;
1976 };
1978 struct view_ops {
1979         /* What type of content being displayed. Used in the title bar. */
1980         const char *type;
1981         /* Default command arguments. */
1982         const char **argv;
1983         /* Open and reads in all view content. */
1984         bool (*open)(struct view *view);
1985         /* Read one line; updates view->line. */
1986         bool (*read)(struct view *view, char *data);
1987         /* Draw one line; @lineno must be < view->height. */
1988         bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
1989         /* Depending on view handle a special requests. */
1990         enum request (*request)(struct view *view, enum request request, struct line *line);
1991         /* Search for regexp in a line. */
1992         bool (*grep)(struct view *view, struct line *line);
1993         /* Select line */
1994         void (*select)(struct view *view, struct line *line);
1995         /* Prepare view for loading */
1996         bool (*prepare)(struct view *view);
1997 };
1999 static struct view_ops blame_ops;
2000 static struct view_ops blob_ops;
2001 static struct view_ops diff_ops;
2002 static struct view_ops help_ops;
2003 static struct view_ops log_ops;
2004 static struct view_ops main_ops;
2005 static struct view_ops pager_ops;
2006 static struct view_ops stage_ops;
2007 static struct view_ops status_ops;
2008 static struct view_ops tree_ops;
2009 static struct view_ops branch_ops;
2011 #define VIEW_STR(name, env, ref, ops, map, git) \
2012         { name, #env, ref, ops, map, git }
2014 #define VIEW_(id, name, ops, git, ref) \
2015         VIEW_STR(name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
2018 static struct view views[] = {
2019         VIEW_(MAIN,   "main",   &main_ops,   TRUE,  ref_head),
2020         VIEW_(DIFF,   "diff",   &diff_ops,   TRUE,  ref_commit),
2021         VIEW_(LOG,    "log",    &log_ops,    TRUE,  ref_head),
2022         VIEW_(TREE,   "tree",   &tree_ops,   TRUE,  ref_commit),
2023         VIEW_(BLOB,   "blob",   &blob_ops,   TRUE,  ref_blob),
2024         VIEW_(BLAME,  "blame",  &blame_ops,  TRUE,  ref_commit),
2025         VIEW_(BRANCH, "branch", &branch_ops, TRUE,  ref_head),
2026         VIEW_(HELP,   "help",   &help_ops,   FALSE, ""),
2027         VIEW_(PAGER,  "pager",  &pager_ops,  FALSE, "stdin"),
2028         VIEW_(STATUS, "status", &status_ops, TRUE,  ""),
2029         VIEW_(STAGE,  "stage",  &stage_ops,  TRUE,  ""),
2030 };
2032 #define VIEW(req)       (&views[(req) - REQ_OFFSET - 1])
2033 #define VIEW_REQ(view)  ((view) - views + REQ_OFFSET + 1)
2035 #define foreach_view(view, i) \
2036         for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
2038 #define view_is_displayed(view) \
2039         (view == display[0] || view == display[1])
2042 enum line_graphic {
2043         LINE_GRAPHIC_VLINE
2044 };
2046 static chtype line_graphics[] = {
2047         /* LINE_GRAPHIC_VLINE: */ '|'
2048 };
2050 static inline void
2051 set_view_attr(struct view *view, enum line_type type)
2053         if (!view->curline->selected && view->curtype != type) {
2054                 wattrset(view->win, get_line_attr(type));
2055                 wchgat(view->win, -1, 0, type, NULL);
2056                 view->curtype = type;
2057         }
2060 static int
2061 draw_chars(struct view *view, enum line_type type, const char *string,
2062            int max_len, bool use_tilde)
2064         static char out_buffer[BUFSIZ * 2];
2065         int len = 0;
2066         int col = 0;
2067         int trimmed = FALSE;
2068         size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2070         if (max_len <= 0)
2071                 return 0;
2073         len = utf8_length(&string, skip, &col, max_len, &trimmed, use_tilde);
2075         set_view_attr(view, type);
2076         if (len > 0) {
2077                 if (opt_iconv_out != ICONV_NONE) {
2078                         ICONV_CONST char *inbuf = (ICONV_CONST char *) string;
2079                         size_t inlen = len + 1;
2081                         char *outbuf = out_buffer;
2082                         size_t outlen = sizeof(out_buffer);
2084                         size_t ret;
2086                         ret = iconv(opt_iconv_out, &inbuf, &inlen, &outbuf, &outlen);
2087                         if (ret != (size_t) -1) {
2088                                 string = out_buffer;
2089                                 len = sizeof(out_buffer) - outlen;
2090                         }
2091                 }
2093                 waddnstr(view->win, string, len);
2094         }
2095         if (trimmed && use_tilde) {
2096                 set_view_attr(view, LINE_DELIMITER);
2097                 waddch(view->win, '~');
2098                 col++;
2099         }
2101         return col;
2104 static int
2105 draw_space(struct view *view, enum line_type type, int max, int spaces)
2107         static char space[] = "                    ";
2108         int col = 0;
2110         spaces = MIN(max, spaces);
2112         while (spaces > 0) {
2113                 int len = MIN(spaces, sizeof(space) - 1);
2115                 col += draw_chars(view, type, space, len, FALSE);
2116                 spaces -= len;
2117         }
2119         return col;
2122 static bool
2123 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
2125         view->col += draw_chars(view, type, string, view->width + view->yoffset - view->col, trim);
2126         return view->width + view->yoffset <= view->col;
2129 static bool
2130 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
2132         size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2133         int max = view->width + view->yoffset - view->col;
2134         int i;
2136         if (max < size)
2137                 size = max;
2139         set_view_attr(view, type);
2140         /* Using waddch() instead of waddnstr() ensures that
2141          * they'll be rendered correctly for the cursor line. */
2142         for (i = skip; i < size; i++)
2143                 waddch(view->win, graphic[i]);
2145         view->col += size;
2146         if (size < max && skip <= size)
2147                 waddch(view->win, ' ');
2148         view->col++;
2150         return view->width + view->yoffset <= view->col;
2153 static bool
2154 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
2156         int max = MIN(view->width + view->yoffset - view->col, len);
2157         int col;
2159         if (text)
2160                 col = draw_chars(view, type, text, max - 1, trim);
2161         else
2162                 col = draw_space(view, type, max - 1, max - 1);
2164         view->col += col;
2165         view->col += draw_space(view, LINE_DEFAULT, max - col, max - col);
2166         return view->width + view->yoffset <= view->col;
2169 static bool
2170 draw_date(struct view *view, time_t *time)
2172         const char *date = time ? mkdate(time) : "";
2173         int cols = opt_date == DATE_SHORT ? DATE_SHORT_COLS : DATE_COLS;
2175         return draw_field(view, LINE_DATE, date, cols, FALSE);
2178 static bool
2179 draw_author(struct view *view, const char *author)
2181         bool trim = opt_author_cols == 0 || opt_author_cols > 5 || !author;
2183         if (!trim) {
2184                 static char initials[10];
2185                 size_t pos;
2187 #define is_initial_sep(c) (isspace(c) || ispunct(c) || (c) == '@')
2189                 memset(initials, 0, sizeof(initials));
2190                 for (pos = 0; *author && pos < opt_author_cols - 1; author++, pos++) {
2191                         while (is_initial_sep(*author))
2192                                 author++;
2193                         strncpy(&initials[pos], author, sizeof(initials) - 1 - pos);
2194                         while (*author && !is_initial_sep(author[1]))
2195                                 author++;
2196                 }
2198                 author = initials;
2199         }
2201         return draw_field(view, LINE_AUTHOR, author, opt_author_cols, trim);
2204 static bool
2205 draw_mode(struct view *view, mode_t mode)
2207         const char *str;
2209         if (S_ISDIR(mode))
2210                 str = "drwxr-xr-x";
2211         else if (S_ISLNK(mode))
2212                 str = "lrwxrwxrwx";
2213         else if (S_ISGITLINK(mode))
2214                 str = "m---------";
2215         else if (S_ISREG(mode) && mode & S_IXUSR)
2216                 str = "-rwxr-xr-x";
2217         else if (S_ISREG(mode))
2218                 str = "-rw-r--r--";
2219         else
2220                 str = "----------";
2222         return draw_field(view, LINE_MODE, str, STRING_SIZE("-rw-r--r-- "), FALSE);
2225 static bool
2226 draw_lineno(struct view *view, unsigned int lineno)
2228         char number[10];
2229         int digits3 = view->digits < 3 ? 3 : view->digits;
2230         int max = MIN(view->width + view->yoffset - view->col, digits3);
2231         char *text = NULL;
2233         lineno += view->offset + 1;
2234         if (lineno == 1 || (lineno % opt_num_interval) == 0) {
2235                 static char fmt[] = "%1ld";
2237                 fmt[1] = '0' + (view->digits <= 9 ? digits3 : 1);
2238                 if (string_format(number, fmt, lineno))
2239                         text = number;
2240         }
2241         if (text)
2242                 view->col += draw_chars(view, LINE_LINE_NUMBER, text, max, TRUE);
2243         else
2244                 view->col += draw_space(view, LINE_LINE_NUMBER, max, digits3);
2245         return draw_graphic(view, LINE_DEFAULT, &line_graphics[LINE_GRAPHIC_VLINE], 1);
2248 static bool
2249 draw_view_line(struct view *view, unsigned int lineno)
2251         struct line *line;
2252         bool selected = (view->offset + lineno == view->lineno);
2254         assert(view_is_displayed(view));
2256         if (view->offset + lineno >= view->lines)
2257                 return FALSE;
2259         line = &view->line[view->offset + lineno];
2261         wmove(view->win, lineno, 0);
2262         if (line->cleareol)
2263                 wclrtoeol(view->win);
2264         view->col = 0;
2265         view->curline = line;
2266         view->curtype = LINE_NONE;
2267         line->selected = FALSE;
2268         line->dirty = line->cleareol = 0;
2270         if (selected) {
2271                 set_view_attr(view, LINE_CURSOR);
2272                 line->selected = TRUE;
2273                 view->ops->select(view, line);
2274         }
2276         return view->ops->draw(view, line, lineno);
2279 static void
2280 redraw_view_dirty(struct view *view)
2282         bool dirty = FALSE;
2283         int lineno;
2285         for (lineno = 0; lineno < view->height; lineno++) {
2286                 if (view->offset + lineno >= view->lines)
2287                         break;
2288                 if (!view->line[view->offset + lineno].dirty)
2289                         continue;
2290                 dirty = TRUE;
2291                 if (!draw_view_line(view, lineno))
2292                         break;
2293         }
2295         if (!dirty)
2296                 return;
2297         wnoutrefresh(view->win);
2300 static void
2301 redraw_view_from(struct view *view, int lineno)
2303         assert(0 <= lineno && lineno < view->height);
2305         for (; lineno < view->height; lineno++) {
2306                 if (!draw_view_line(view, lineno))
2307                         break;
2308         }
2310         wnoutrefresh(view->win);
2313 static void
2314 redraw_view(struct view *view)
2316         werase(view->win);
2317         redraw_view_from(view, 0);
2321 static void
2322 update_view_title(struct view *view)
2324         char buf[SIZEOF_STR];
2325         char state[SIZEOF_STR];
2326         size_t bufpos = 0, statelen = 0;
2328         assert(view_is_displayed(view));
2330         if (view != VIEW(REQ_VIEW_STATUS) && view->lines) {
2331                 unsigned int view_lines = view->offset + view->height;
2332                 unsigned int lines = view->lines
2333                                    ? MIN(view_lines, view->lines) * 100 / view->lines
2334                                    : 0;
2336                 string_format_from(state, &statelen, " - %s %d of %d (%d%%)",
2337                                    view->ops->type,
2338                                    view->lineno + 1,
2339                                    view->lines,
2340                                    lines);
2342         }
2344         if (view->pipe) {
2345                 time_t secs = time(NULL) - view->start_time;
2347                 /* Three git seconds are a long time ... */
2348                 if (secs > 2)
2349                         string_format_from(state, &statelen, " loading %lds", secs);
2350         }
2352         string_format_from(buf, &bufpos, "[%s]", view->name);
2353         if (*view->ref && bufpos < view->width) {
2354                 size_t refsize = strlen(view->ref);
2355                 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2357                 if (minsize < view->width)
2358                         refsize = view->width - minsize + 7;
2359                 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2360         }
2362         if (statelen && bufpos < view->width) {
2363                 string_format_from(buf, &bufpos, "%s", state);
2364         }
2366         if (view == display[current_view])
2367                 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2368         else
2369                 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2371         mvwaddnstr(view->title, 0, 0, buf, bufpos);
2372         wclrtoeol(view->title);
2373         wnoutrefresh(view->title);
2376 static int
2377 apply_step(double step, int value)
2379         if (step >= 1)
2380                 return (int) step;
2381         value *= step + 0.01;
2382         return value ? value : 1;
2385 static void
2386 resize_display(void)
2388         int offset, i;
2389         struct view *base = display[0];
2390         struct view *view = display[1] ? display[1] : display[0];
2392         /* Setup window dimensions */
2394         getmaxyx(stdscr, base->height, base->width);
2396         /* Make room for the status window. */
2397         base->height -= 1;
2399         if (view != base) {
2400                 /* Horizontal split. */
2401                 view->width   = base->width;
2402                 view->height  = apply_step(opt_scale_split_view, base->height);
2403                 view->height  = MAX(view->height, MIN_VIEW_HEIGHT);
2404                 view->height  = MIN(view->height, base->height - MIN_VIEW_HEIGHT);
2405                 base->height -= view->height;
2407                 /* Make room for the title bar. */
2408                 view->height -= 1;
2409         }
2411         /* Make room for the title bar. */
2412         base->height -= 1;
2414         offset = 0;
2416         foreach_displayed_view (view, i) {
2417                 if (!view->win) {
2418                         view->win = newwin(view->height, 0, offset, 0);
2419                         if (!view->win)
2420                                 die("Failed to create %s view", view->name);
2422                         scrollok(view->win, FALSE);
2424                         view->title = newwin(1, 0, offset + view->height, 0);
2425                         if (!view->title)
2426                                 die("Failed to create title window");
2428                 } else {
2429                         wresize(view->win, view->height, view->width);
2430                         mvwin(view->win,   offset, 0);
2431                         mvwin(view->title, offset + view->height, 0);
2432                 }
2434                 offset += view->height + 1;
2435         }
2438 static void
2439 redraw_display(bool clear)
2441         struct view *view;
2442         int i;
2444         foreach_displayed_view (view, i) {
2445                 if (clear)
2446                         wclear(view->win);
2447                 redraw_view(view);
2448                 update_view_title(view);
2449         }
2452 static void
2453 toggle_date_option(enum date *date)
2455         static const char *help[] = {
2456                 "no",
2457                 "default",
2458                 "relative",
2459                 "short"
2460         };
2462         *date = (*date + 1) % ARRAY_SIZE(help);
2463         redraw_display(FALSE);
2464         report("Displaying %s dates", help[*date]);
2467 static void
2468 toggle_view_option(bool *option, const char *help)
2470         *option = !*option;
2471         redraw_display(FALSE);
2472         report("%sabling %s", *option ? "En" : "Dis", help);
2475 static void
2476 open_option_menu(void)
2478         const struct menu_item menu[] = {
2479                 { '.', "line numbers", &opt_line_number },
2480                 { 'D', "date display", &opt_date },
2481                 { 'A', "author display", &opt_author },
2482                 { 'g', "revision graph display", &opt_rev_graph },
2483                 { 'F', "reference display", &opt_show_refs },
2484                 { 0 }
2485         };
2486         int selected = 0;
2488         if (prompt_menu("Toggle option", menu, &selected)) {
2489                 if (menu[selected].data == &opt_date)
2490                         toggle_date_option(menu[selected].data);
2491                 else
2492                         toggle_view_option(menu[selected].data, menu[selected].text);
2493         }
2496 static void
2497 maximize_view(struct view *view)
2499         memset(display, 0, sizeof(display));
2500         current_view = 0;
2501         display[current_view] = view;
2502         resize_display();
2503         redraw_display(FALSE);
2504         report("");
2508 /*
2509  * Navigation
2510  */
2512 static bool
2513 goto_view_line(struct view *view, unsigned long offset, unsigned long lineno)
2515         if (lineno >= view->lines)
2516                 lineno = view->lines > 0 ? view->lines - 1 : 0;
2518         if (offset > lineno || offset + view->height <= lineno) {
2519                 unsigned long half = view->height / 2;
2521                 if (lineno > half)
2522                         offset = lineno - half;
2523                 else
2524                         offset = 0;
2525         }
2527         if (offset != view->offset || lineno != view->lineno) {
2528                 view->offset = offset;
2529                 view->lineno = lineno;
2530                 return TRUE;
2531         }
2533         return FALSE;
2536 /* Scrolling backend */
2537 static void
2538 do_scroll_view(struct view *view, int lines)
2540         bool redraw_current_line = FALSE;
2542         /* The rendering expects the new offset. */
2543         view->offset += lines;
2545         assert(0 <= view->offset && view->offset < view->lines);
2546         assert(lines);
2548         /* Move current line into the view. */
2549         if (view->lineno < view->offset) {
2550                 view->lineno = view->offset;
2551                 redraw_current_line = TRUE;
2552         } else if (view->lineno >= view->offset + view->height) {
2553                 view->lineno = view->offset + view->height - 1;
2554                 redraw_current_line = TRUE;
2555         }
2557         assert(view->offset <= view->lineno && view->lineno < view->lines);
2559         /* Redraw the whole screen if scrolling is pointless. */
2560         if (view->height < ABS(lines)) {
2561                 redraw_view(view);
2563         } else {
2564                 int line = lines > 0 ? view->height - lines : 0;
2565                 int end = line + ABS(lines);
2567                 scrollok(view->win, TRUE);
2568                 wscrl(view->win, lines);
2569                 scrollok(view->win, FALSE);
2571                 while (line < end && draw_view_line(view, line))
2572                         line++;
2574                 if (redraw_current_line)
2575                         draw_view_line(view, view->lineno - view->offset);
2576                 wnoutrefresh(view->win);
2577         }
2579         view->has_scrolled = TRUE;
2580         report("");
2583 /* Scroll frontend */
2584 static void
2585 scroll_view(struct view *view, enum request request)
2587         int lines = 1;
2589         assert(view_is_displayed(view));
2591         switch (request) {
2592         case REQ_SCROLL_LEFT:
2593                 if (view->yoffset == 0) {
2594                         report("Cannot scroll beyond the first column");
2595                         return;
2596                 }
2597                 if (view->yoffset <= apply_step(opt_hscroll, view->width))
2598                         view->yoffset = 0;
2599                 else
2600                         view->yoffset -= apply_step(opt_hscroll, view->width);
2601                 redraw_view_from(view, 0);
2602                 report("");
2603                 return;
2604         case REQ_SCROLL_RIGHT:
2605                 view->yoffset += apply_step(opt_hscroll, view->width);
2606                 redraw_view(view);
2607                 report("");
2608                 return;
2609         case REQ_SCROLL_PAGE_DOWN:
2610                 lines = view->height;
2611         case REQ_SCROLL_LINE_DOWN:
2612                 if (view->offset + lines > view->lines)
2613                         lines = view->lines - view->offset;
2615                 if (lines == 0 || view->offset + view->height >= view->lines) {
2616                         report("Cannot scroll beyond the last line");
2617                         return;
2618                 }
2619                 break;
2621         case REQ_SCROLL_PAGE_UP:
2622                 lines = view->height;
2623         case REQ_SCROLL_LINE_UP:
2624                 if (lines > view->offset)
2625                         lines = view->offset;
2627                 if (lines == 0) {
2628                         report("Cannot scroll beyond the first line");
2629                         return;
2630                 }
2632                 lines = -lines;
2633                 break;
2635         default:
2636                 die("request %d not handled in switch", request);
2637         }
2639         do_scroll_view(view, lines);
2642 /* Cursor moving */
2643 static void
2644 move_view(struct view *view, enum request request)
2646         int scroll_steps = 0;
2647         int steps;
2649         switch (request) {
2650         case REQ_MOVE_FIRST_LINE:
2651                 steps = -view->lineno;
2652                 break;
2654         case REQ_MOVE_LAST_LINE:
2655                 steps = view->lines - view->lineno - 1;
2656                 break;
2658         case REQ_MOVE_PAGE_UP:
2659                 steps = view->height > view->lineno
2660                       ? -view->lineno : -view->height;
2661                 break;
2663         case REQ_MOVE_PAGE_DOWN:
2664                 steps = view->lineno + view->height >= view->lines
2665                       ? view->lines - view->lineno - 1 : view->height;
2666                 break;
2668         case REQ_MOVE_UP:
2669                 steps = -1;
2670                 break;
2672         case REQ_MOVE_DOWN:
2673                 steps = 1;
2674                 break;
2676         default:
2677                 die("request %d not handled in switch", request);
2678         }
2680         if (steps <= 0 && view->lineno == 0) {
2681                 report("Cannot move beyond the first line");
2682                 return;
2684         } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2685                 report("Cannot move beyond the last line");
2686                 return;
2687         }
2689         /* Move the current line */
2690         view->lineno += steps;
2691         assert(0 <= view->lineno && view->lineno < view->lines);
2693         /* Check whether the view needs to be scrolled */
2694         if (view->lineno < view->offset ||
2695             view->lineno >= view->offset + view->height) {
2696                 scroll_steps = steps;
2697                 if (steps < 0 && -steps > view->offset) {
2698                         scroll_steps = -view->offset;
2700                 } else if (steps > 0) {
2701                         if (view->lineno == view->lines - 1 &&
2702                             view->lines > view->height) {
2703                                 scroll_steps = view->lines - view->offset - 1;
2704                                 if (scroll_steps >= view->height)
2705                                         scroll_steps -= view->height - 1;
2706                         }
2707                 }
2708         }
2710         if (!view_is_displayed(view)) {
2711                 view->offset += scroll_steps;
2712                 assert(0 <= view->offset && view->offset < view->lines);
2713                 view->ops->select(view, &view->line[view->lineno]);
2714                 return;
2715         }
2717         /* Repaint the old "current" line if we be scrolling */
2718         if (ABS(steps) < view->height)
2719                 draw_view_line(view, view->lineno - steps - view->offset);
2721         if (scroll_steps) {
2722                 do_scroll_view(view, scroll_steps);
2723                 return;
2724         }
2726         /* Draw the current line */
2727         draw_view_line(view, view->lineno - view->offset);
2729         wnoutrefresh(view->win);
2730         report("");
2734 /*
2735  * Searching
2736  */
2738 static void search_view(struct view *view, enum request request);
2740 static bool
2741 grep_text(struct view *view, const char *text[])
2743         regmatch_t pmatch;
2744         size_t i;
2746         for (i = 0; text[i]; i++)
2747                 if (*text[i] &&
2748                     regexec(view->regex, text[i], 1, &pmatch, 0) != REG_NOMATCH)
2749                         return TRUE;
2750         return FALSE;
2753 static void
2754 select_view_line(struct view *view, unsigned long lineno)
2756         unsigned long old_lineno = view->lineno;
2757         unsigned long old_offset = view->offset;
2759         if (goto_view_line(view, view->offset, lineno)) {
2760                 if (view_is_displayed(view)) {
2761                         if (old_offset != view->offset) {
2762                                 redraw_view(view);
2763                         } else {
2764                                 draw_view_line(view, old_lineno - view->offset);
2765                                 draw_view_line(view, view->lineno - view->offset);
2766                                 wnoutrefresh(view->win);
2767                         }
2768                 } else {
2769                         view->ops->select(view, &view->line[view->lineno]);
2770                 }
2771         }
2774 static void
2775 find_next(struct view *view, enum request request)
2777         unsigned long lineno = view->lineno;
2778         int direction;
2780         if (!*view->grep) {
2781                 if (!*opt_search)
2782                         report("No previous search");
2783                 else
2784                         search_view(view, request);
2785                 return;
2786         }
2788         switch (request) {
2789         case REQ_SEARCH:
2790         case REQ_FIND_NEXT:
2791                 direction = 1;
2792                 break;
2794         case REQ_SEARCH_BACK:
2795         case REQ_FIND_PREV:
2796                 direction = -1;
2797                 break;
2799         default:
2800                 return;
2801         }
2803         if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
2804                 lineno += direction;
2806         /* Note, lineno is unsigned long so will wrap around in which case it
2807          * will become bigger than view->lines. */
2808         for (; lineno < view->lines; lineno += direction) {
2809                 if (view->ops->grep(view, &view->line[lineno])) {
2810                         select_view_line(view, lineno);
2811                         report("Line %ld matches '%s'", lineno + 1, view->grep);
2812                         return;
2813                 }
2814         }
2816         report("No match found for '%s'", view->grep);
2819 static void
2820 search_view(struct view *view, enum request request)
2822         int regex_err;
2824         if (view->regex) {
2825                 regfree(view->regex);
2826                 *view->grep = 0;
2827         } else {
2828                 view->regex = calloc(1, sizeof(*view->regex));
2829                 if (!view->regex)
2830                         return;
2831         }
2833         regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
2834         if (regex_err != 0) {
2835                 char buf[SIZEOF_STR] = "unknown error";
2837                 regerror(regex_err, view->regex, buf, sizeof(buf));
2838                 report("Search failed: %s", buf);
2839                 return;
2840         }
2842         string_copy(view->grep, opt_search);
2844         find_next(view, request);
2847 /*
2848  * Incremental updating
2849  */
2851 static void
2852 reset_view(struct view *view)
2854         int i;
2856         for (i = 0; i < view->lines; i++)
2857                 free(view->line[i].data);
2858         free(view->line);
2860         view->p_offset = view->offset;
2861         view->p_yoffset = view->yoffset;
2862         view->p_lineno = view->lineno;
2864         view->line = NULL;
2865         view->offset = 0;
2866         view->yoffset = 0;
2867         view->lines  = 0;
2868         view->lineno = 0;
2869         view->vid[0] = 0;
2870         view->update_secs = 0;
2873 static void
2874 free_argv(const char *argv[])
2876         int argc;
2878         for (argc = 0; argv[argc]; argc++)
2879                 free((void *) argv[argc]);
2882 static bool
2883 format_argv(const char *dst_argv[], const char *src_argv[], enum format_flags flags)
2885         char buf[SIZEOF_STR];
2886         int argc;
2887         bool noreplace = flags == FORMAT_NONE;
2889         free_argv(dst_argv);
2891         for (argc = 0; src_argv[argc]; argc++) {
2892                 const char *arg = src_argv[argc];
2893                 size_t bufpos = 0;
2895                 while (arg) {
2896                         char *next = strstr(arg, "%(");
2897                         int len = next - arg;
2898                         const char *value;
2900                         if (!next || noreplace) {
2901                                 if (flags == FORMAT_DASH && !strcmp(arg, "--"))
2902                                         noreplace = TRUE;
2903                                 len = strlen(arg);
2904                                 value = "";
2906                         } else if (!prefixcmp(next, "%(directory)")) {
2907                                 value = opt_path;
2909                         } else if (!prefixcmp(next, "%(file)")) {
2910                                 value = opt_file;
2912                         } else if (!prefixcmp(next, "%(ref)")) {
2913                                 value = *opt_ref ? opt_ref : "HEAD";
2915                         } else if (!prefixcmp(next, "%(head)")) {
2916                                 value = ref_head;
2918                         } else if (!prefixcmp(next, "%(commit)")) {
2919                                 value = ref_commit;
2921                         } else if (!prefixcmp(next, "%(blob)")) {
2922                                 value = ref_blob;
2924                         } else {
2925                                 report("Unknown replacement: `%s`", next);
2926                                 return FALSE;
2927                         }
2929                         if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
2930                                 return FALSE;
2932                         arg = next && !noreplace ? strchr(next, ')') + 1 : NULL;
2933                 }
2935                 dst_argv[argc] = strdup(buf);
2936                 if (!dst_argv[argc])
2937                         break;
2938         }
2940         dst_argv[argc] = NULL;
2942         return src_argv[argc] == NULL;
2945 static bool
2946 restore_view_position(struct view *view)
2948         if (!view->p_restore || (view->pipe && view->lines <= view->p_lineno))
2949                 return FALSE;
2951         /* Changing the view position cancels the restoring. */
2952         /* FIXME: Changing back to the first line is not detected. */
2953         if (view->offset != 0 || view->lineno != 0) {
2954                 view->p_restore = FALSE;
2955                 return FALSE;
2956         }
2958         if (goto_view_line(view, view->p_offset, view->p_lineno) &&
2959             view_is_displayed(view))
2960                 werase(view->win);
2962         view->yoffset = view->p_yoffset;
2963         view->p_restore = FALSE;
2965         return TRUE;
2968 static void
2969 end_update(struct view *view, bool force)
2971         if (!view->pipe)
2972                 return;
2973         while (!view->ops->read(view, NULL))
2974                 if (!force)
2975                         return;
2976         set_nonblocking_input(FALSE);
2977         if (force)
2978                 kill_io(view->pipe);
2979         done_io(view->pipe);
2980         view->pipe = NULL;
2983 static void
2984 setup_update(struct view *view, const char *vid)
2986         set_nonblocking_input(TRUE);
2987         reset_view(view);
2988         string_copy_rev(view->vid, vid);
2989         view->pipe = &view->io;
2990         view->start_time = time(NULL);
2993 static bool
2994 prepare_update(struct view *view, const char *argv[], const char *dir,
2995                enum format_flags flags)
2997         if (view->pipe)
2998                 end_update(view, TRUE);
2999         return init_io_rd(&view->io, argv, dir, flags);
3002 static bool
3003 prepare_update_file(struct view *view, const char *name)
3005         if (view->pipe)
3006                 end_update(view, TRUE);
3007         return io_open(&view->io, "%s", name);
3010 static bool
3011 begin_update(struct view *view, bool refresh)
3013         if (view->pipe)
3014                 end_update(view, TRUE);
3016         if (!refresh) {
3017                 if (view->ops->prepare) {
3018                         if (!view->ops->prepare(view))
3019                                 return FALSE;
3020                 } else if (!init_io_rd(&view->io, view->ops->argv, NULL, FORMAT_ALL)) {
3021                         return FALSE;
3022                 }
3024                 /* Put the current ref_* value to the view title ref
3025                  * member. This is needed by the blob view. Most other
3026                  * views sets it automatically after loading because the
3027                  * first line is a commit line. */
3028                 string_copy_rev(view->ref, view->id);
3029         }
3031         if (!start_io(&view->io))
3032                 return FALSE;
3034         setup_update(view, view->id);
3036         return TRUE;
3039 static bool
3040 update_view(struct view *view)
3042         char out_buffer[BUFSIZ * 2];
3043         char *line;
3044         /* Clear the view and redraw everything since the tree sorting
3045          * might have rearranged things. */
3046         bool redraw = view->lines == 0;
3047         bool can_read = TRUE;
3049         if (!view->pipe)
3050                 return TRUE;
3052         if (!io_can_read(view->pipe)) {
3053                 if (view->lines == 0 && view_is_displayed(view)) {
3054                         time_t secs = time(NULL) - view->start_time;
3056                         if (secs > 1 && secs > view->update_secs) {
3057                                 if (view->update_secs == 0)
3058                                         redraw_view(view);
3059                                 update_view_title(view);
3060                                 view->update_secs = secs;
3061                         }
3062                 }
3063                 return TRUE;
3064         }
3066         for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
3067                 if (opt_iconv_in != ICONV_NONE) {
3068                         ICONV_CONST char *inbuf = line;
3069                         size_t inlen = strlen(line) + 1;
3071                         char *outbuf = out_buffer;
3072                         size_t outlen = sizeof(out_buffer);
3074                         size_t ret;
3076                         ret = iconv(opt_iconv_in, &inbuf, &inlen, &outbuf, &outlen);
3077                         if (ret != (size_t) -1)
3078                                 line = out_buffer;
3079                 }
3081                 if (!view->ops->read(view, line)) {
3082                         report("Allocation failure");
3083                         end_update(view, TRUE);
3084                         return FALSE;
3085                 }
3086         }
3088         {
3089                 unsigned long lines = view->lines;
3090                 int digits;
3092                 for (digits = 0; lines; digits++)
3093                         lines /= 10;
3095                 /* Keep the displayed view in sync with line number scaling. */
3096                 if (digits != view->digits) {
3097                         view->digits = digits;
3098                         if (opt_line_number || view == VIEW(REQ_VIEW_BLAME))
3099                                 redraw = TRUE;
3100                 }
3101         }
3103         if (io_error(view->pipe)) {
3104                 report("Failed to read: %s", io_strerror(view->pipe));
3105                 end_update(view, TRUE);
3107         } else if (io_eof(view->pipe)) {
3108                 report("");
3109                 end_update(view, FALSE);
3110         }
3112         if (restore_view_position(view))
3113                 redraw = TRUE;
3115         if (!view_is_displayed(view))
3116                 return TRUE;
3118         if (redraw)
3119                 redraw_view_from(view, 0);
3120         else
3121                 redraw_view_dirty(view);
3123         /* Update the title _after_ the redraw so that if the redraw picks up a
3124          * commit reference in view->ref it'll be available here. */
3125         update_view_title(view);
3126         return TRUE;
3129 DEFINE_ALLOCATOR(realloc_lines, struct line, 256)
3131 static struct line *
3132 add_line_data(struct view *view, void *data, enum line_type type)
3134         struct line *line;
3136         if (!realloc_lines(&view->line, view->lines, 1))
3137                 return NULL;
3139         line = &view->line[view->lines++];
3140         memset(line, 0, sizeof(*line));
3141         line->type = type;
3142         line->data = data;
3143         line->dirty = 1;
3145         return line;
3148 static struct line *
3149 add_line_text(struct view *view, const char *text, enum line_type type)
3151         char *data = text ? strdup(text) : NULL;
3153         return data ? add_line_data(view, data, type) : NULL;
3156 static struct line *
3157 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
3159         char buf[SIZEOF_STR];
3160         va_list args;
3162         va_start(args, fmt);
3163         if (vsnprintf(buf, sizeof(buf), fmt, args) >= sizeof(buf))
3164                 buf[0] = 0;
3165         va_end(args);
3167         return buf[0] ? add_line_text(view, buf, type) : NULL;
3170 /*
3171  * View opening
3172  */
3174 enum open_flags {
3175         OPEN_DEFAULT = 0,       /* Use default view switching. */
3176         OPEN_SPLIT = 1,         /* Split current view. */
3177         OPEN_RELOAD = 4,        /* Reload view even if it is the current. */
3178         OPEN_REFRESH = 16,      /* Refresh view using previous command. */
3179         OPEN_PREPARED = 32,     /* Open already prepared command. */
3180 };
3182 static void
3183 open_view(struct view *prev, enum request request, enum open_flags flags)
3185         bool split = !!(flags & OPEN_SPLIT);
3186         bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
3187         bool nomaximize = !!(flags & OPEN_REFRESH);
3188         struct view *view = VIEW(request);
3189         int nviews = displayed_views();
3190         struct view *base_view = display[0];
3192         if (view == prev && nviews == 1 && !reload) {
3193                 report("Already in %s view", view->name);
3194                 return;
3195         }
3197         if (view->git_dir && !opt_git_dir[0]) {
3198                 report("The %s view is disabled in pager view", view->name);
3199                 return;
3200         }
3202         if (split) {
3203                 display[1] = view;
3204                 current_view = 1;
3205         } else if (!nomaximize) {
3206                 /* Maximize the current view. */
3207                 memset(display, 0, sizeof(display));
3208                 current_view = 0;
3209                 display[current_view] = view;
3210         }
3212         /* No parent signals that this is the first loaded view. */
3213         if (prev && view != prev) {
3214                 view->parent = prev;
3215         }
3217         /* Resize the view when switching between split- and full-screen,
3218          * or when switching between two different full-screen views. */
3219         if (nviews != displayed_views() ||
3220             (nviews == 1 && base_view != display[0]))
3221                 resize_display();
3223         if (view->ops->open) {
3224                 if (view->pipe)
3225                         end_update(view, TRUE);
3226                 if (!view->ops->open(view)) {
3227                         report("Failed to load %s view", view->name);
3228                         return;
3229                 }
3230                 restore_view_position(view);
3232         } else if ((reload || strcmp(view->vid, view->id)) &&
3233                    !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
3234                 report("Failed to load %s view", view->name);
3235                 return;
3236         }
3238         if (split && prev->lineno - prev->offset >= prev->height) {
3239                 /* Take the title line into account. */
3240                 int lines = prev->lineno - prev->offset - prev->height + 1;
3242                 /* Scroll the view that was split if the current line is
3243                  * outside the new limited view. */
3244                 do_scroll_view(prev, lines);
3245         }
3247         if (prev && view != prev && split && view_is_displayed(prev)) {
3248                 /* "Blur" the previous view. */
3249                 update_view_title(prev);
3250         }
3252         if (view->pipe && view->lines == 0) {
3253                 /* Clear the old view and let the incremental updating refill
3254                  * the screen. */
3255                 werase(view->win);
3256                 view->p_restore = flags & (OPEN_RELOAD | OPEN_REFRESH);
3257                 report("");
3258         } else if (view_is_displayed(view)) {
3259                 redraw_view(view);
3260                 report("");
3261         }
3264 static void
3265 open_external_viewer(const char *argv[], const char *dir)
3267         def_prog_mode();           /* save current tty modes */
3268         endwin();                  /* restore original tty modes */
3269         run_io_fg(argv, dir);
3270         fprintf(stderr, "Press Enter to continue");
3271         getc(opt_tty);
3272         reset_prog_mode();
3273         redraw_display(TRUE);
3276 static void
3277 open_mergetool(const char *file)
3279         const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
3281         open_external_viewer(mergetool_argv, opt_cdup);
3284 static void
3285 open_editor(bool from_root, const char *file)
3287         const char *editor_argv[] = { "vi", file, NULL };
3288         const char *editor;
3290         editor = getenv("GIT_EDITOR");
3291         if (!editor && *opt_editor)
3292                 editor = opt_editor;
3293         if (!editor)
3294                 editor = getenv("VISUAL");
3295         if (!editor)
3296                 editor = getenv("EDITOR");
3297         if (!editor)
3298                 editor = "vi";
3300         editor_argv[0] = editor;
3301         open_external_viewer(editor_argv, from_root ? opt_cdup : NULL);
3304 static void
3305 open_run_request(enum request request)
3307         struct run_request *req = get_run_request(request);
3308         const char *argv[ARRAY_SIZE(req->argv)] = { NULL };
3310         if (!req) {
3311                 report("Unknown run request");
3312                 return;
3313         }
3315         if (format_argv(argv, req->argv, FORMAT_ALL))
3316                 open_external_viewer(argv, NULL);
3317         free_argv(argv);
3320 /*
3321  * User request switch noodle
3322  */
3324 static int
3325 view_driver(struct view *view, enum request request)
3327         int i;
3329         if (request == REQ_NONE)
3330                 return TRUE;
3332         if (request > REQ_NONE) {
3333                 open_run_request(request);
3334                 /* FIXME: When all views can refresh always do this. */
3335                 if (view == VIEW(REQ_VIEW_STATUS) ||
3336                     view == VIEW(REQ_VIEW_MAIN) ||
3337                     view == VIEW(REQ_VIEW_LOG) ||
3338                     view == VIEW(REQ_VIEW_BRANCH) ||
3339                     view == VIEW(REQ_VIEW_STAGE))
3340                         request = REQ_REFRESH;
3341                 else
3342                         return TRUE;
3343         }
3345         if (view && view->lines) {
3346                 request = view->ops->request(view, request, &view->line[view->lineno]);
3347                 if (request == REQ_NONE)
3348                         return TRUE;
3349         }
3351         switch (request) {
3352         case REQ_MOVE_UP:
3353         case REQ_MOVE_DOWN:
3354         case REQ_MOVE_PAGE_UP:
3355         case REQ_MOVE_PAGE_DOWN:
3356         case REQ_MOVE_FIRST_LINE:
3357         case REQ_MOVE_LAST_LINE:
3358                 move_view(view, request);
3359                 break;
3361         case REQ_SCROLL_LEFT:
3362         case REQ_SCROLL_RIGHT:
3363         case REQ_SCROLL_LINE_DOWN:
3364         case REQ_SCROLL_LINE_UP:
3365         case REQ_SCROLL_PAGE_DOWN:
3366         case REQ_SCROLL_PAGE_UP:
3367                 scroll_view(view, request);
3368                 break;
3370         case REQ_VIEW_BLAME:
3371                 if (!opt_file[0]) {
3372                         report("No file chosen, press %s to open tree view",
3373                                get_key(view->keymap, REQ_VIEW_TREE));
3374                         break;
3375                 }
3376                 open_view(view, request, OPEN_DEFAULT);
3377                 break;
3379         case REQ_VIEW_BLOB:
3380                 if (!ref_blob[0]) {
3381                         report("No file chosen, press %s to open tree view",
3382                                get_key(view->keymap, REQ_VIEW_TREE));
3383                         break;
3384                 }
3385                 open_view(view, request, OPEN_DEFAULT);
3386                 break;
3388         case REQ_VIEW_PAGER:
3389                 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3390                         report("No pager content, press %s to run command from prompt",
3391                                get_key(view->keymap, REQ_PROMPT));
3392                         break;
3393                 }
3394                 open_view(view, request, OPEN_DEFAULT);
3395                 break;
3397         case REQ_VIEW_STAGE:
3398                 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3399                         report("No stage content, press %s to open the status view and choose file",
3400                                get_key(view->keymap, REQ_VIEW_STATUS));
3401                         break;
3402                 }
3403                 open_view(view, request, OPEN_DEFAULT);
3404                 break;
3406         case REQ_VIEW_STATUS:
3407                 if (opt_is_inside_work_tree == FALSE) {
3408                         report("The status view requires a working tree");
3409                         break;
3410                 }
3411                 open_view(view, request, OPEN_DEFAULT);
3412                 break;
3414         case REQ_VIEW_MAIN:
3415         case REQ_VIEW_DIFF:
3416         case REQ_VIEW_LOG:
3417         case REQ_VIEW_TREE:
3418         case REQ_VIEW_HELP:
3419         case REQ_VIEW_BRANCH:
3420                 open_view(view, request, OPEN_DEFAULT);
3421                 break;
3423         case REQ_NEXT:
3424         case REQ_PREVIOUS:
3425                 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3427                 if ((view == VIEW(REQ_VIEW_DIFF) &&
3428                      view->parent == VIEW(REQ_VIEW_MAIN)) ||
3429                    (view == VIEW(REQ_VIEW_DIFF) &&
3430                      view->parent == VIEW(REQ_VIEW_BLAME)) ||
3431                    (view == VIEW(REQ_VIEW_STAGE) &&
3432                      view->parent == VIEW(REQ_VIEW_STATUS)) ||
3433                    (view == VIEW(REQ_VIEW_BLOB) &&
3434                      view->parent == VIEW(REQ_VIEW_TREE)) ||
3435                    (view == VIEW(REQ_VIEW_MAIN) &&
3436                      view->parent == VIEW(REQ_VIEW_BRANCH))) {
3437                         int line;
3439                         view = view->parent;
3440                         line = view->lineno;
3441                         move_view(view, request);
3442                         if (view_is_displayed(view))
3443                                 update_view_title(view);
3444                         if (line != view->lineno)
3445                                 view->ops->request(view, REQ_ENTER,
3446                                                    &view->line[view->lineno]);
3448                 } else {
3449                         move_view(view, request);
3450                 }
3451                 break;
3453         case REQ_VIEW_NEXT:
3454         {
3455                 int nviews = displayed_views();
3456                 int next_view = (current_view + 1) % nviews;
3458                 if (next_view == current_view) {
3459                         report("Only one view is displayed");
3460                         break;
3461                 }
3463                 current_view = next_view;
3464                 /* Blur out the title of the previous view. */
3465                 update_view_title(view);
3466                 report("");
3467                 break;
3468         }
3469         case REQ_REFRESH:
3470                 report("Refreshing is not yet supported for the %s view", view->name);
3471                 break;
3473         case REQ_MAXIMIZE:
3474                 if (displayed_views() == 2)
3475                         maximize_view(view);
3476                 break;
3478         case REQ_OPTIONS:
3479                 open_option_menu();
3480                 break;
3482         case REQ_TOGGLE_LINENO:
3483                 toggle_view_option(&opt_line_number, "line numbers");
3484                 break;
3486         case REQ_TOGGLE_DATE:
3487                 toggle_date_option(&opt_date);
3488                 break;
3490         case REQ_TOGGLE_AUTHOR:
3491                 toggle_view_option(&opt_author, "author display");
3492                 break;
3494         case REQ_TOGGLE_REV_GRAPH:
3495                 toggle_view_option(&opt_rev_graph, "revision graph display");
3496                 break;
3498         case REQ_TOGGLE_REFS:
3499                 toggle_view_option(&opt_show_refs, "reference display");
3500                 break;
3502         case REQ_TOGGLE_SORT_FIELD:
3503         case REQ_TOGGLE_SORT_ORDER:
3504                 report("Sorting is not yet supported for the %s view", view->name);
3505                 break;
3507         case REQ_SEARCH:
3508         case REQ_SEARCH_BACK:
3509                 search_view(view, request);
3510                 break;
3512         case REQ_FIND_NEXT:
3513         case REQ_FIND_PREV:
3514                 find_next(view, request);
3515                 break;
3517         case REQ_STOP_LOADING:
3518                 for (i = 0; i < ARRAY_SIZE(views); i++) {
3519                         view = &views[i];
3520                         if (view->pipe)
3521                                 report("Stopped loading the %s view", view->name),
3522                         end_update(view, TRUE);
3523                 }
3524                 break;
3526         case REQ_SHOW_VERSION:
3527                 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3528                 return TRUE;
3530         case REQ_SCREEN_REDRAW:
3531                 redraw_display(TRUE);
3532                 break;
3534         case REQ_EDIT:
3535                 report("Nothing to edit");
3536                 break;
3538         case REQ_ENTER:
3539                 report("Nothing to enter");
3540                 break;
3542         case REQ_VIEW_CLOSE:
3543                 /* XXX: Mark closed views by letting view->parent point to the
3544                  * view itself. Parents to closed view should never be
3545                  * followed. */
3546                 if (view->parent &&
3547                     view->parent->parent != view->parent) {
3548                         maximize_view(view->parent);
3549                         view->parent = view;
3550                         break;
3551                 }
3552                 /* Fall-through */
3553         case REQ_QUIT:
3554                 return FALSE;
3556         default:
3557                 report("Unknown key, press %s for help",
3558                        get_key(view->keymap, REQ_VIEW_HELP));
3559                 return TRUE;
3560         }
3562         return TRUE;
3566 /*
3567  * View backend utilities
3568  */
3570 enum sort_field {
3571         ORDERBY_NAME,
3572         ORDERBY_DATE,
3573         ORDERBY_AUTHOR,
3574 };
3576 struct sort_state {
3577         const enum sort_field *fields;
3578         size_t size, current;
3579         bool reverse;
3580 };
3582 #define SORT_STATE(fields) { fields, ARRAY_SIZE(fields), 0 }
3583 #define get_sort_field(state) ((state).fields[(state).current])
3584 #define sort_order(state, result) ((state).reverse ? -(result) : (result))
3586 static void
3587 sort_view(struct view *view, enum request request, struct sort_state *state,
3588           int (*compare)(const void *, const void *))
3590         switch (request) {
3591         case REQ_TOGGLE_SORT_FIELD:
3592                 state->current = (state->current + 1) % state->size;
3593                 break;
3595         case REQ_TOGGLE_SORT_ORDER:
3596                 state->reverse = !state->reverse;
3597                 break;
3598         default:
3599                 die("Not a sort request");
3600         }
3602         qsort(view->line, view->lines, sizeof(*view->line), compare);
3603         redraw_view(view);
3606 DEFINE_ALLOCATOR(realloc_authors, const char *, 256)
3608 /* Small author cache to reduce memory consumption. It uses binary
3609  * search to lookup or find place to position new entries. No entries
3610  * are ever freed. */
3611 static const char *
3612 get_author(const char *name)
3614         static const char **authors;
3615         static size_t authors_size;
3616         int from = 0, to = authors_size - 1;
3618         while (from <= to) {
3619                 size_t pos = (to + from) / 2;
3620                 int cmp = strcmp(name, authors[pos]);
3622                 if (!cmp)
3623                         return authors[pos];
3625                 if (cmp < 0)
3626                         to = pos - 1;
3627                 else
3628                         from = pos + 1;
3629         }
3631         if (!realloc_authors(&authors, authors_size, 1))
3632                 return NULL;
3633         name = strdup(name);
3634         if (!name)
3635                 return NULL;
3637         memmove(authors + from + 1, authors + from, (authors_size - from) * sizeof(*authors));
3638         authors[from] = name;
3639         authors_size++;
3641         return name;
3644 static void
3645 parse_timezone(time_t *time, const char *zone)
3647         long tz;
3649         tz  = ('0' - zone[1]) * 60 * 60 * 10;
3650         tz += ('0' - zone[2]) * 60 * 60;
3651         tz += ('0' - zone[3]) * 60;
3652         tz += ('0' - zone[4]);
3654         if (zone[0] == '-')
3655                 tz = -tz;
3657         *time -= tz;
3660 /* Parse author lines where the name may be empty:
3661  *      author  <email@address.tld> 1138474660 +0100
3662  */
3663 static void
3664 parse_author_line(char *ident, const char **author, time_t *time)
3666         char *nameend = strchr(ident, '<');
3667         char *emailend = strchr(ident, '>');
3669         if (nameend && emailend)
3670                 *nameend = *emailend = 0;
3671         ident = chomp_string(ident);
3672         if (!*ident) {
3673                 if (nameend)
3674                         ident = chomp_string(nameend + 1);
3675                 if (!*ident)
3676                         ident = "Unknown";
3677         }
3679         *author = get_author(ident);
3681         /* Parse epoch and timezone */
3682         if (emailend && emailend[1] == ' ') {
3683                 char *secs = emailend + 2;
3684                 char *zone = strchr(secs, ' ');
3686                 *time = (time_t) atol(secs);
3688                 if (zone && strlen(zone) == STRING_SIZE(" +0700"))
3689                         parse_timezone(time, zone + 1);
3690         }
3693 static bool
3694 open_commit_parent_menu(char buf[SIZEOF_STR], int *parents)
3696         char rev[SIZEOF_REV];
3697         const char *revlist_argv[] = {
3698                 "git", "log", "--no-color", "-1", "--pretty=format:%s", rev, NULL
3699         };
3700         struct menu_item *items;
3701         char text[SIZEOF_STR];
3702         bool ok = TRUE;
3703         int i;
3705         items = calloc(*parents + 1, sizeof(*items));
3706         if (!items)
3707                 return FALSE;
3709         for (i = 0; i < *parents; i++) {
3710                 string_copy_rev(rev, &buf[SIZEOF_REV * i]);
3711                 if (!run_io_buf(revlist_argv, text, sizeof(text)) ||
3712                     !(items[i].text = strdup(text))) {
3713                         ok = FALSE;
3714                         break;
3715                 }
3716         }
3718         if (ok) {
3719                 *parents = 0;
3720                 ok = prompt_menu("Select parent", items, parents);
3721         }
3722         for (i = 0; items[i].text; i++)
3723                 free((char *) items[i].text);
3724         free(items);
3725         return ok;
3728 static bool
3729 select_commit_parent(const char *id, char rev[SIZEOF_REV], const char *path)
3731         char buf[SIZEOF_STR * 4];
3732         const char *revlist_argv[] = {
3733                 "git", "log", "--no-color", "-1",
3734                         "--pretty=format:%P", id, "--", path, NULL
3735         };
3736         int parents;
3738         if (!run_io_buf(revlist_argv, buf, sizeof(buf)) ||
3739             (parents = strlen(buf) / 40) < 0) {
3740                 report("Failed to get parent information");
3741                 return FALSE;
3743         } else if (parents == 0) {
3744                 if (path)
3745                         report("Path '%s' does not exist in the parent", path);
3746                 else
3747                         report("The selected commit has no parents");
3748                 return FALSE;
3749         }
3751         if (parents > 1 && !open_commit_parent_menu(buf, &parents))
3752                 return FALSE;
3754         string_copy_rev(rev, &buf[41 * parents]);
3755         return TRUE;
3758 /*
3759  * Pager backend
3760  */
3762 static bool
3763 pager_draw(struct view *view, struct line *line, unsigned int lineno)
3765         char text[SIZEOF_STR];
3767         if (opt_line_number && draw_lineno(view, lineno))
3768                 return TRUE;
3770         string_expand(text, sizeof(text), line->data, opt_tab_size);
3771         draw_text(view, line->type, text, TRUE);
3772         return TRUE;
3775 static bool
3776 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
3778         const char *describe_argv[] = { "git", "describe", commit_id, NULL };
3779         char ref[SIZEOF_STR];
3781         if (!run_io_buf(describe_argv, ref, sizeof(ref)) || !*ref)
3782                 return TRUE;
3784         /* This is the only fatal call, since it can "corrupt" the buffer. */
3785         if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
3786                 return FALSE;
3788         return TRUE;
3791 static void
3792 add_pager_refs(struct view *view, struct line *line)
3794         char buf[SIZEOF_STR];
3795         char *commit_id = (char *)line->data + STRING_SIZE("commit ");
3796         struct ref_list *list;
3797         size_t bufpos = 0, i;
3798         const char *sep = "Refs: ";
3799         bool is_tag = FALSE;
3801         assert(line->type == LINE_COMMIT);
3803         list = get_ref_list(commit_id);
3804         if (!list) {
3805                 if (view == VIEW(REQ_VIEW_DIFF))
3806                         goto try_add_describe_ref;
3807                 return;
3808         }
3810         for (i = 0; i < list->size; i++) {
3811                 struct ref *ref = list->refs[i];
3812                 const char *fmt = ref->tag    ? "%s[%s]" :
3813                                   ref->remote ? "%s<%s>" : "%s%s";
3815                 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
3816                         return;
3817                 sep = ", ";
3818                 if (ref->tag)
3819                         is_tag = TRUE;
3820         }
3822         if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
3823 try_add_describe_ref:
3824                 /* Add <tag>-g<commit_id> "fake" reference. */
3825                 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
3826                         return;
3827         }
3829         if (bufpos == 0)
3830                 return;
3832         add_line_text(view, buf, LINE_PP_REFS);
3835 static bool
3836 pager_read(struct view *view, char *data)
3838         struct line *line;
3840         if (!data)
3841                 return TRUE;
3843         line = add_line_text(view, data, get_line_type(data));
3844         if (!line)
3845                 return FALSE;
3847         if (line->type == LINE_COMMIT &&
3848             (view == VIEW(REQ_VIEW_DIFF) ||
3849              view == VIEW(REQ_VIEW_LOG)))
3850                 add_pager_refs(view, line);
3852         return TRUE;
3855 static enum request
3856 pager_request(struct view *view, enum request request, struct line *line)
3858         int split = 0;
3860         if (request != REQ_ENTER)
3861                 return request;
3863         if (line->type == LINE_COMMIT &&
3864            (view == VIEW(REQ_VIEW_LOG) ||
3865             view == VIEW(REQ_VIEW_PAGER))) {
3866                 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
3867                 split = 1;
3868         }
3870         /* Always scroll the view even if it was split. That way
3871          * you can use Enter to scroll through the log view and
3872          * split open each commit diff. */
3873         scroll_view(view, REQ_SCROLL_LINE_DOWN);
3875         /* FIXME: A minor workaround. Scrolling the view will call report("")
3876          * but if we are scrolling a non-current view this won't properly
3877          * update the view title. */
3878         if (split)
3879                 update_view_title(view);
3881         return REQ_NONE;
3884 static bool
3885 pager_grep(struct view *view, struct line *line)
3887         const char *text[] = { line->data, NULL };
3889         return grep_text(view, text);
3892 static void
3893 pager_select(struct view *view, struct line *line)
3895         if (line->type == LINE_COMMIT) {
3896                 char *text = (char *)line->data + STRING_SIZE("commit ");
3898                 if (view != VIEW(REQ_VIEW_PAGER))
3899                         string_copy_rev(view->ref, text);
3900                 string_copy_rev(ref_commit, text);
3901         }
3904 static struct view_ops pager_ops = {
3905         "line",
3906         NULL,
3907         NULL,
3908         pager_read,
3909         pager_draw,
3910         pager_request,
3911         pager_grep,
3912         pager_select,
3913 };
3915 static const char *log_argv[SIZEOF_ARG] = {
3916         "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
3917 };
3919 static enum request
3920 log_request(struct view *view, enum request request, struct line *line)
3922         switch (request) {
3923         case REQ_REFRESH:
3924                 load_refs();
3925                 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
3926                 return REQ_NONE;
3927         default:
3928                 return pager_request(view, request, line);
3929         }
3932 static struct view_ops log_ops = {
3933         "line",
3934         log_argv,
3935         NULL,
3936         pager_read,
3937         pager_draw,
3938         log_request,
3939         pager_grep,
3940         pager_select,
3941 };
3943 static const char *diff_argv[SIZEOF_ARG] = {
3944         "git", "show", "--pretty=fuller", "--no-color", "--root",
3945                 "--patch-with-stat", "--find-copies-harder", "-C", "%(commit)", NULL
3946 };
3948 static struct view_ops diff_ops = {
3949         "line",
3950         diff_argv,
3951         NULL,
3952         pager_read,
3953         pager_draw,
3954         pager_request,
3955         pager_grep,
3956         pager_select,
3957 };
3959 /*
3960  * Help backend
3961  */
3963 static bool help_keymap_hidden[ARRAY_SIZE(keymap_table)];
3965 static char *
3966 help_name(char buf[SIZEOF_STR], const char *name, size_t namelen)
3968         int bufpos;
3970         for (bufpos = 0; bufpos <= namelen; bufpos++) {
3971                 buf[bufpos] = tolower(name[bufpos]);
3972                 if (buf[bufpos] == '_')
3973                         buf[bufpos] = '-';
3974         }
3976         buf[bufpos] = 0;
3977         return buf;
3980 #define help_keymap_name(buf, keymap) \
3981         help_name(buf, keymap_table[keymap].name, keymap_table[keymap].namelen)
3983 static bool
3984 help_open_keymap_title(struct view *view, enum keymap keymap)
3986         char buf[SIZEOF_STR];
3987         struct line *line;
3989         line = add_line_format(view, LINE_HELP_KEYMAP, "[%c] %s bindings",
3990                                help_keymap_hidden[keymap] ? '+' : '-',
3991                                help_keymap_name(buf, keymap));
3992         if (line)
3993                 line->other = keymap;
3995         return help_keymap_hidden[keymap];
3998 static void
3999 help_open_keymap(struct view *view, enum keymap keymap)
4001         const char *group = NULL;
4002         char buf[SIZEOF_STR];
4003         size_t bufpos;
4004         bool add_title = TRUE;
4005         int i;
4007         for (i = 0; i < ARRAY_SIZE(req_info); i++) {
4008                 const char *key = NULL;
4010                 if (req_info[i].request == REQ_NONE)
4011                         continue;
4013                 if (!req_info[i].request) {
4014                         group = req_info[i].help;
4015                         continue;
4016                 }
4018                 key = get_keys(keymap, req_info[i].request, TRUE);
4019                 if (!key || !*key)
4020                         continue;
4022                 if (add_title && help_open_keymap_title(view, keymap))
4023                         return;
4024                 add_title = false;
4026                 if (group) {
4027                         add_line_text(view, group, LINE_HELP_GROUP);
4028                         group = NULL;
4029                 }
4031                 add_line_format(view, LINE_DEFAULT, "    %-25s %-20s %s", key,
4032                                 help_name(buf, req_info[i].name, req_info[i].namelen),
4033                                 req_info[i].help);
4034         }
4036         group = "External commands:";
4038         for (i = 0; i < run_requests; i++) {
4039                 struct run_request *req = get_run_request(REQ_NONE + i + 1);
4040                 const char *key;
4041                 int argc;
4043                 if (!req || req->keymap != keymap)
4044                         continue;
4046                 key = get_key_name(req->key);
4047                 if (!*key)
4048                         key = "(no key defined)";
4050                 if (add_title && help_open_keymap_title(view, keymap))
4051                         return;
4052                 if (group) {
4053                         add_line_text(view, group, LINE_HELP_GROUP);
4054                         group = NULL;
4055                 }
4057                 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
4058                         if (!string_format_from(buf, &bufpos, "%s%s",
4059                                                 argc ? " " : "", req->argv[argc]))
4060                                 return;
4062                 add_line_format(view, LINE_DEFAULT, "    %-25s `%s`", key, buf);
4063         }
4066 static bool
4067 help_open(struct view *view)
4069         enum keymap keymap;
4071         reset_view(view);
4072         add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
4073         add_line_text(view, "", LINE_DEFAULT);
4075         for (keymap = 0; keymap < ARRAY_SIZE(keymap_table); keymap++)
4076                 help_open_keymap(view, keymap);
4078         return TRUE;
4081 static enum request
4082 help_request(struct view *view, enum request request, struct line *line)
4084         switch (request) {
4085         case REQ_ENTER:
4086                 if (line->type == LINE_HELP_KEYMAP) {
4087                         help_keymap_hidden[line->other] =
4088                                 !help_keymap_hidden[line->other];
4089                         view->p_restore = TRUE;
4090                         open_view(view, REQ_VIEW_HELP, OPEN_REFRESH);
4091                 }
4093                 return REQ_NONE;
4094         default:
4095                 return pager_request(view, request, line);
4096         }
4099 static struct view_ops help_ops = {
4100         "line",
4101         NULL,
4102         help_open,
4103         NULL,
4104         pager_draw,
4105         help_request,
4106         pager_grep,
4107         pager_select,
4108 };
4111 /*
4112  * Tree backend
4113  */
4115 struct tree_stack_entry {
4116         struct tree_stack_entry *prev;  /* Entry below this in the stack */
4117         unsigned long lineno;           /* Line number to restore */
4118         char *name;                     /* Position of name in opt_path */
4119 };
4121 /* The top of the path stack. */
4122 static struct tree_stack_entry *tree_stack = NULL;
4123 unsigned long tree_lineno = 0;
4125 static void
4126 pop_tree_stack_entry(void)
4128         struct tree_stack_entry *entry = tree_stack;
4130         tree_lineno = entry->lineno;
4131         entry->name[0] = 0;
4132         tree_stack = entry->prev;
4133         free(entry);
4136 static void
4137 push_tree_stack_entry(const char *name, unsigned long lineno)
4139         struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
4140         size_t pathlen = strlen(opt_path);
4142         if (!entry)
4143                 return;
4145         entry->prev = tree_stack;
4146         entry->name = opt_path + pathlen;
4147         tree_stack = entry;
4149         if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
4150                 pop_tree_stack_entry();
4151                 return;
4152         }
4154         /* Move the current line to the first tree entry. */
4155         tree_lineno = 1;
4156         entry->lineno = lineno;
4159 /* Parse output from git-ls-tree(1):
4160  *
4161  * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
4162  */
4164 #define SIZEOF_TREE_ATTR \
4165         STRING_SIZE("100644 blob f931e1d229c3e185caad4449bf5b66ed72462657\t")
4167 #define SIZEOF_TREE_MODE \
4168         STRING_SIZE("100644 ")
4170 #define TREE_ID_OFFSET \
4171         STRING_SIZE("100644 blob ")
4173 struct tree_entry {
4174         char id[SIZEOF_REV];
4175         mode_t mode;
4176         time_t time;                    /* Date from the author ident. */
4177         const char *author;             /* Author of the commit. */
4178         char name[1];
4179 };
4181 static const char *
4182 tree_path(const struct line *line)
4184         return ((struct tree_entry *) line->data)->name;
4187 static int
4188 tree_compare_entry(const struct line *line1, const struct line *line2)
4190         if (line1->type != line2->type)
4191                 return line1->type == LINE_TREE_DIR ? -1 : 1;
4192         return strcmp(tree_path(line1), tree_path(line2));
4195 static const enum sort_field tree_sort_fields[] = {
4196         ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
4197 };
4198 static struct sort_state tree_sort_state = SORT_STATE(tree_sort_fields);
4200 static int
4201 tree_compare(const void *l1, const void *l2)
4203         const struct line *line1 = (const struct line *) l1;
4204         const struct line *line2 = (const struct line *) l2;
4205         const struct tree_entry *entry1 = ((const struct line *) l1)->data;
4206         const struct tree_entry *entry2 = ((const struct line *) l2)->data;
4208         if (line1->type == LINE_TREE_HEAD)
4209                 return -1;
4210         if (line2->type == LINE_TREE_HEAD)
4211                 return 1;
4213         switch (get_sort_field(tree_sort_state)) {
4214         case ORDERBY_DATE:
4215                 return sort_order(tree_sort_state, entry1->time - entry2->time);
4217         case ORDERBY_AUTHOR:
4218                 return sort_order(tree_sort_state, strcmp(entry1->author, entry2->author));
4220         case ORDERBY_NAME:
4221         default:
4222                 return sort_order(tree_sort_state, tree_compare_entry(line1, line2));
4223         }
4227 static struct line *
4228 tree_entry(struct view *view, enum line_type type, const char *path,
4229            const char *mode, const char *id)
4231         struct tree_entry *entry = calloc(1, sizeof(*entry) + strlen(path));
4232         struct line *line = entry ? add_line_data(view, entry, type) : NULL;
4234         if (!entry || !line) {
4235                 free(entry);
4236                 return NULL;
4237         }
4239         strncpy(entry->name, path, strlen(path));
4240         if (mode)
4241                 entry->mode = strtoul(mode, NULL, 8);
4242         if (id)
4243                 string_copy_rev(entry->id, id);
4245         return line;
4248 static bool
4249 tree_read_date(struct view *view, char *text, bool *read_date)
4251         static const char *author_name;
4252         static time_t author_time;
4254         if (!text && *read_date) {
4255                 *read_date = FALSE;
4256                 return TRUE;
4258         } else if (!text) {
4259                 char *path = *opt_path ? opt_path : ".";
4260                 /* Find next entry to process */
4261                 const char *log_file[] = {
4262                         "git", "log", "--no-color", "--pretty=raw",
4263                                 "--cc", "--raw", view->id, "--", path, NULL
4264                 };
4265                 struct io io = {};
4267                 if (!view->lines) {
4268                         tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL);
4269                         report("Tree is empty");
4270                         return TRUE;
4271                 }
4273                 if (!run_io_rd(&io, log_file, opt_cdup, FORMAT_NONE)) {
4274                         report("Failed to load tree data");
4275                         return TRUE;
4276                 }
4278                 done_io(view->pipe);
4279                 view->io = io;
4280                 *read_date = TRUE;
4281                 return FALSE;
4283         } else if (*text == 'a' && get_line_type(text) == LINE_AUTHOR) {
4284                 parse_author_line(text + STRING_SIZE("author "),
4285                                   &author_name, &author_time);
4287         } else if (*text == ':') {
4288                 char *pos;
4289                 size_t annotated = 1;
4290                 size_t i;
4292                 pos = strchr(text, '\t');
4293                 if (!pos)
4294                         return TRUE;
4295                 text = pos + 1;
4296                 if (*opt_path && !strncmp(text, opt_path, strlen(opt_path)))
4297                         text += strlen(opt_path);
4298                 pos = strchr(text, '/');
4299                 if (pos)
4300                         *pos = 0;
4302                 for (i = 1; i < view->lines; i++) {
4303                         struct line *line = &view->line[i];
4304                         struct tree_entry *entry = line->data;
4306                         annotated += !!entry->author;
4307                         if (entry->author || strcmp(entry->name, text))
4308                                 continue;
4310                         entry->author = author_name;
4311                         entry->time = author_time;
4312                         line->dirty = 1;
4313                         break;
4314                 }
4316                 if (annotated == view->lines)
4317                         kill_io(view->pipe);
4318         }
4319         return TRUE;
4322 static bool
4323 tree_read(struct view *view, char *text)
4325         static bool read_date = FALSE;
4326         struct tree_entry *data;
4327         struct line *entry, *line;
4328         enum line_type type;
4329         size_t textlen = text ? strlen(text) : 0;
4330         char *path = text + SIZEOF_TREE_ATTR;
4332         if (read_date || !text)
4333                 return tree_read_date(view, text, &read_date);
4335         if (textlen <= SIZEOF_TREE_ATTR)
4336                 return FALSE;
4337         if (view->lines == 0 &&
4338             !tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL))
4339                 return FALSE;
4341         /* Strip the path part ... */
4342         if (*opt_path) {
4343                 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
4344                 size_t striplen = strlen(opt_path);
4346                 if (pathlen > striplen)
4347                         memmove(path, path + striplen,
4348                                 pathlen - striplen + 1);
4350                 /* Insert "link" to parent directory. */
4351                 if (view->lines == 1 &&
4352                     !tree_entry(view, LINE_TREE_DIR, "..", "040000", view->ref))
4353                         return FALSE;
4354         }
4356         type = text[SIZEOF_TREE_MODE] == 't' ? LINE_TREE_DIR : LINE_TREE_FILE;
4357         entry = tree_entry(view, type, path, text, text + TREE_ID_OFFSET);
4358         if (!entry)
4359                 return FALSE;
4360         data = entry->data;
4362         /* Skip "Directory ..." and ".." line. */
4363         for (line = &view->line[1 + !!*opt_path]; line < entry; line++) {
4364                 if (tree_compare_entry(line, entry) <= 0)
4365                         continue;
4367                 memmove(line + 1, line, (entry - line) * sizeof(*entry));
4369                 line->data = data;
4370                 line->type = type;
4371                 for (; line <= entry; line++)
4372                         line->dirty = line->cleareol = 1;
4373                 return TRUE;
4374         }
4376         if (tree_lineno > view->lineno) {
4377                 view->lineno = tree_lineno;
4378                 tree_lineno = 0;
4379         }
4381         return TRUE;
4384 static bool
4385 tree_draw(struct view *view, struct line *line, unsigned int lineno)
4387         struct tree_entry *entry = line->data;
4389         if (line->type == LINE_TREE_HEAD) {
4390                 if (draw_text(view, line->type, "Directory path /", TRUE))
4391                         return TRUE;
4392         } else {
4393                 if (draw_mode(view, entry->mode))
4394                         return TRUE;
4396                 if (opt_author && draw_author(view, entry->author))
4397                         return TRUE;
4399                 if (opt_date && draw_date(view, entry->author ? &entry->time : NULL))
4400                         return TRUE;
4401         }
4402         if (draw_text(view, line->type, entry->name, TRUE))
4403                 return TRUE;
4404         return TRUE;
4407 static void
4408 open_blob_editor()
4410         char file[SIZEOF_STR] = "/tmp/tigblob.XXXXXX";
4411         int fd = mkstemp(file);
4413         if (fd == -1)
4414                 report("Failed to create temporary file");
4415         else if (!run_io_append(blob_ops.argv, FORMAT_ALL, fd))
4416                 report("Failed to save blob data to file");
4417         else
4418                 open_editor(FALSE, file);
4419         if (fd != -1)
4420                 unlink(file);
4423 static enum request
4424 tree_request(struct view *view, enum request request, struct line *line)
4426         enum open_flags flags;
4428         switch (request) {
4429         case REQ_VIEW_BLAME:
4430                 if (line->type != LINE_TREE_FILE) {
4431                         report("Blame only supported for files");
4432                         return REQ_NONE;
4433                 }
4435                 string_copy(opt_ref, view->vid);
4436                 return request;
4438         case REQ_EDIT:
4439                 if (line->type != LINE_TREE_FILE) {
4440                         report("Edit only supported for files");
4441                 } else if (!is_head_commit(view->vid)) {
4442                         open_blob_editor();
4443                 } else {
4444                         open_editor(TRUE, opt_file);
4445                 }
4446                 return REQ_NONE;
4448         case REQ_TOGGLE_SORT_FIELD:
4449         case REQ_TOGGLE_SORT_ORDER:
4450                 sort_view(view, request, &tree_sort_state, tree_compare);
4451                 return REQ_NONE;
4453         case REQ_PARENT:
4454                 if (!*opt_path) {
4455                         /* quit view if at top of tree */
4456                         return REQ_VIEW_CLOSE;
4457                 }
4458                 /* fake 'cd  ..' */
4459                 line = &view->line[1];
4460                 break;
4462         case REQ_ENTER:
4463                 break;
4465         default:
4466                 return request;
4467         }
4469         /* Cleanup the stack if the tree view is at a different tree. */
4470         while (!*opt_path && tree_stack)
4471                 pop_tree_stack_entry();
4473         switch (line->type) {
4474         case LINE_TREE_DIR:
4475                 /* Depending on whether it is a subdirectory or parent link
4476                  * mangle the path buffer. */
4477                 if (line == &view->line[1] && *opt_path) {
4478                         pop_tree_stack_entry();
4480                 } else {
4481                         const char *basename = tree_path(line);
4483                         push_tree_stack_entry(basename, view->lineno);
4484                 }
4486                 /* Trees and subtrees share the same ID, so they are not not
4487                  * unique like blobs. */
4488                 flags = OPEN_RELOAD;
4489                 request = REQ_VIEW_TREE;
4490                 break;
4492         case LINE_TREE_FILE:
4493                 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4494                 request = REQ_VIEW_BLOB;
4495                 break;
4497         default:
4498                 return REQ_NONE;
4499         }
4501         open_view(view, request, flags);
4502         if (request == REQ_VIEW_TREE)
4503                 view->lineno = tree_lineno;
4505         return REQ_NONE;
4508 static bool
4509 tree_grep(struct view *view, struct line *line)
4511         struct tree_entry *entry = line->data;
4512         const char *text[] = {
4513                 entry->name,
4514                 opt_author ? entry->author : "",
4515                 opt_date ? mkdate(&entry->time) : "",
4516                 NULL
4517         };
4519         return grep_text(view, text);
4522 static void
4523 tree_select(struct view *view, struct line *line)
4525         struct tree_entry *entry = line->data;
4527         if (line->type == LINE_TREE_FILE) {
4528                 string_copy_rev(ref_blob, entry->id);
4529                 string_format(opt_file, "%s%s", opt_path, tree_path(line));
4531         } else if (line->type != LINE_TREE_DIR) {
4532                 return;
4533         }
4535         string_copy_rev(view->ref, entry->id);
4538 static bool
4539 tree_prepare(struct view *view)
4541         if (view->lines == 0 && opt_prefix[0]) {
4542                 char *pos = opt_prefix;
4544                 while (pos && *pos) {
4545                         char *end = strchr(pos, '/');
4547                         if (end)
4548                                 *end = 0;
4549                         push_tree_stack_entry(pos, 0);
4550                         pos = end;
4551                         if (end) {
4552                                 *end = '/';
4553                                 pos++;
4554                         }
4555                 }
4557         } else if (strcmp(view->vid, view->id)) {
4558                 opt_path[0] = 0;
4559         }
4561         return init_io_rd(&view->io, view->ops->argv, opt_cdup, FORMAT_ALL);
4564 static const char *tree_argv[SIZEOF_ARG] = {
4565         "git", "ls-tree", "%(commit)", "%(directory)", NULL
4566 };
4568 static struct view_ops tree_ops = {
4569         "file",
4570         tree_argv,
4571         NULL,
4572         tree_read,
4573         tree_draw,
4574         tree_request,
4575         tree_grep,
4576         tree_select,
4577         tree_prepare,
4578 };
4580 static bool
4581 blob_read(struct view *view, char *line)
4583         if (!line)
4584                 return TRUE;
4585         return add_line_text(view, line, LINE_DEFAULT) != NULL;
4588 static enum request
4589 blob_request(struct view *view, enum request request, struct line *line)
4591         switch (request) {
4592         case REQ_EDIT:
4593                 open_blob_editor();
4594                 return REQ_NONE;
4595         default:
4596                 return pager_request(view, request, line);
4597         }
4600 static const char *blob_argv[SIZEOF_ARG] = {
4601         "git", "cat-file", "blob", "%(blob)", NULL
4602 };
4604 static struct view_ops blob_ops = {
4605         "line",
4606         blob_argv,
4607         NULL,
4608         blob_read,
4609         pager_draw,
4610         blob_request,
4611         pager_grep,
4612         pager_select,
4613 };
4615 /*
4616  * Blame backend
4617  *
4618  * Loading the blame view is a two phase job:
4619  *
4620  *  1. File content is read either using opt_file from the
4621  *     filesystem or using git-cat-file.
4622  *  2. Then blame information is incrementally added by
4623  *     reading output from git-blame.
4624  */
4626 static const char *blame_head_argv[] = {
4627         "git", "blame", "--incremental", "--", "%(file)", NULL
4628 };
4630 static const char *blame_ref_argv[] = {
4631         "git", "blame", "--incremental", "%(ref)", "--", "%(file)", NULL
4632 };
4634 static const char *blame_cat_file_argv[] = {
4635         "git", "cat-file", "blob", "%(ref):%(file)", NULL
4636 };
4638 struct blame_commit {
4639         char id[SIZEOF_REV];            /* SHA1 ID. */
4640         char title[128];                /* First line of the commit message. */
4641         const char *author;             /* Author of the commit. */
4642         time_t time;                    /* Date from the author ident. */
4643         char filename[128];             /* Name of file. */
4644         bool has_previous;              /* Was a "previous" line detected. */
4645 };
4647 struct blame {
4648         struct blame_commit *commit;
4649         unsigned long lineno;
4650         char text[1];
4651 };
4653 static bool
4654 blame_open(struct view *view)
4656         char path[SIZEOF_STR];
4658         if (!view->parent && *opt_prefix) {
4659                 string_copy(path, opt_file);
4660                 if (!string_format(opt_file, "%s%s", opt_prefix, path))
4661                         return FALSE;
4662         }
4664         if (*opt_ref || !io_open(&view->io, "%s%s", opt_cdup, opt_file)) {
4665                 if (!run_io_rd(&view->io, blame_cat_file_argv, opt_cdup, FORMAT_ALL))
4666                         return FALSE;
4667         }
4669         setup_update(view, opt_file);
4670         string_format(view->ref, "%s ...", opt_file);
4672         return TRUE;
4675 static struct blame_commit *
4676 get_blame_commit(struct view *view, const char *id)
4678         size_t i;
4680         for (i = 0; i < view->lines; i++) {
4681                 struct blame *blame = view->line[i].data;
4683                 if (!blame->commit)
4684                         continue;
4686                 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
4687                         return blame->commit;
4688         }
4690         {
4691                 struct blame_commit *commit = calloc(1, sizeof(*commit));
4693                 if (commit)
4694                         string_ncopy(commit->id, id, SIZEOF_REV);
4695                 return commit;
4696         }
4699 static bool
4700 parse_number(const char **posref, size_t *number, size_t min, size_t max)
4702         const char *pos = *posref;
4704         *posref = NULL;
4705         pos = strchr(pos + 1, ' ');
4706         if (!pos || !isdigit(pos[1]))
4707                 return FALSE;
4708         *number = atoi(pos + 1);
4709         if (*number < min || *number > max)
4710                 return FALSE;
4712         *posref = pos;
4713         return TRUE;
4716 static struct blame_commit *
4717 parse_blame_commit(struct view *view, const char *text, int *blamed)
4719         struct blame_commit *commit;
4720         struct blame *blame;
4721         const char *pos = text + SIZEOF_REV - 2;
4722         size_t orig_lineno = 0;
4723         size_t lineno;
4724         size_t group;
4726         if (strlen(text) <= SIZEOF_REV || pos[1] != ' ')
4727                 return NULL;
4729         if (!parse_number(&pos, &orig_lineno, 1, 9999999) ||
4730             !parse_number(&pos, &lineno, 1, view->lines) ||
4731             !parse_number(&pos, &group, 1, view->lines - lineno + 1))
4732                 return NULL;
4734         commit = get_blame_commit(view, text);
4735         if (!commit)
4736                 return NULL;
4738         *blamed += group;
4739         while (group--) {
4740                 struct line *line = &view->line[lineno + group - 1];
4742                 blame = line->data;
4743                 blame->commit = commit;
4744                 blame->lineno = orig_lineno + group - 1;
4745                 line->dirty = 1;
4746         }
4748         return commit;
4751 static bool
4752 blame_read_file(struct view *view, const char *line, bool *read_file)
4754         if (!line) {
4755                 const char **argv = *opt_ref ? blame_ref_argv : blame_head_argv;
4756                 struct io io = {};
4758                 if (view->lines == 0 && !view->parent)
4759                         die("No blame exist for %s", view->vid);
4761                 if (view->lines == 0 || !run_io_rd(&io, argv, opt_cdup, FORMAT_ALL)) {
4762                         report("Failed to load blame data");
4763                         return TRUE;
4764                 }
4766                 done_io(view->pipe);
4767                 view->io = io;
4768                 *read_file = FALSE;
4769                 return FALSE;
4771         } else {
4772                 size_t linelen = strlen(line);
4773                 struct blame *blame = malloc(sizeof(*blame) + linelen);
4775                 if (!blame)
4776                         return FALSE;
4778                 blame->commit = NULL;
4779                 strncpy(blame->text, line, linelen);
4780                 blame->text[linelen] = 0;
4781                 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
4782         }
4785 static bool
4786 match_blame_header(const char *name, char **line)
4788         size_t namelen = strlen(name);
4789         bool matched = !strncmp(name, *line, namelen);
4791         if (matched)
4792                 *line += namelen;
4794         return matched;
4797 static bool
4798 blame_read(struct view *view, char *line)
4800         static struct blame_commit *commit = NULL;
4801         static int blamed = 0;
4802         static bool read_file = TRUE;
4804         if (read_file)
4805                 return blame_read_file(view, line, &read_file);
4807         if (!line) {
4808                 /* Reset all! */
4809                 commit = NULL;
4810                 blamed = 0;
4811                 read_file = TRUE;
4812                 string_format(view->ref, "%s", view->vid);
4813                 if (view_is_displayed(view)) {
4814                         update_view_title(view);
4815                         redraw_view_from(view, 0);
4816                 }
4817                 return TRUE;
4818         }
4820         if (!commit) {
4821                 commit = parse_blame_commit(view, line, &blamed);
4822                 string_format(view->ref, "%s %2d%%", view->vid,
4823                               view->lines ? blamed * 100 / view->lines : 0);
4825         } else if (match_blame_header("author ", &line)) {
4826                 commit->author = get_author(line);
4828         } else if (match_blame_header("author-time ", &line)) {
4829                 commit->time = (time_t) atol(line);
4831         } else if (match_blame_header("author-tz ", &line)) {
4832                 parse_timezone(&commit->time, line);
4834         } else if (match_blame_header("summary ", &line)) {
4835                 string_ncopy(commit->title, line, strlen(line));
4837         } else if (match_blame_header("previous ", &line)) {
4838                 commit->has_previous = TRUE;
4840         } else if (match_blame_header("filename ", &line)) {
4841                 string_ncopy(commit->filename, line, strlen(line));
4842                 commit = NULL;
4843         }
4845         return TRUE;
4848 static bool
4849 blame_draw(struct view *view, struct line *line, unsigned int lineno)
4851         struct blame *blame = line->data;
4852         time_t *time = NULL;
4853         const char *id = NULL, *author = NULL;
4854         char text[SIZEOF_STR];
4856         if (blame->commit && *blame->commit->filename) {
4857                 id = blame->commit->id;
4858                 author = blame->commit->author;
4859                 time = &blame->commit->time;
4860         }
4862         if (opt_date && draw_date(view, time))
4863                 return TRUE;
4865         if (opt_author && draw_author(view, author))
4866                 return TRUE;
4868         if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
4869                 return TRUE;
4871         if (draw_lineno(view, lineno))
4872                 return TRUE;
4874         string_expand(text, sizeof(text), blame->text, opt_tab_size);
4875         draw_text(view, LINE_DEFAULT, text, TRUE);
4876         return TRUE;
4879 static bool
4880 check_blame_commit(struct blame *blame, bool check_null_id)
4882         if (!blame->commit)
4883                 report("Commit data not loaded yet");
4884         else if (check_null_id && !strcmp(blame->commit->id, NULL_ID))
4885                 report("No commit exist for the selected line");
4886         else
4887                 return TRUE;
4888         return FALSE;
4891 static void
4892 setup_blame_parent_line(struct view *view, struct blame *blame)
4894         const char *diff_tree_argv[] = {
4895                 "git", "diff-tree", "-U0", blame->commit->id,
4896                         "--", blame->commit->filename, NULL
4897         };
4898         struct io io = {};
4899         int parent_lineno = -1;
4900         int blamed_lineno = -1;
4901         char *line;
4903         if (!run_io(&io, diff_tree_argv, NULL, IO_RD))
4904                 return;
4906         while ((line = io_get(&io, '\n', TRUE))) {
4907                 if (*line == '@') {
4908                         char *pos = strchr(line, '+');
4910                         parent_lineno = atoi(line + 4);
4911                         if (pos)
4912                                 blamed_lineno = atoi(pos + 1);
4914                 } else if (*line == '+' && parent_lineno != -1) {
4915                         if (blame->lineno == blamed_lineno - 1 &&
4916                             !strcmp(blame->text, line + 1)) {
4917                                 view->lineno = parent_lineno ? parent_lineno - 1 : 0;
4918                                 break;
4919                         }
4920                         blamed_lineno++;
4921                 }
4922         }
4924         done_io(&io);
4927 static enum request
4928 blame_request(struct view *view, enum request request, struct line *line)
4930         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4931         struct blame *blame = line->data;
4933         switch (request) {
4934         case REQ_VIEW_BLAME:
4935                 if (check_blame_commit(blame, TRUE)) {
4936                         string_copy(opt_ref, blame->commit->id);
4937                         string_copy(opt_file, blame->commit->filename);
4938                         if (blame->lineno)
4939                                 view->lineno = blame->lineno;
4940                         open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4941                 }
4942                 break;
4944         case REQ_PARENT:
4945                 if (check_blame_commit(blame, TRUE) &&
4946                     select_commit_parent(blame->commit->id, opt_ref,
4947                                          blame->commit->filename)) {
4948                         string_copy(opt_file, blame->commit->filename);
4949                         setup_blame_parent_line(view, blame);
4950                         open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4951                 }
4952                 break;
4954         case REQ_ENTER:
4955                 if (!check_blame_commit(blame, FALSE))
4956                         break;
4958                 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
4959                     !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
4960                         break;
4962                 if (!strcmp(blame->commit->id, NULL_ID)) {
4963                         struct view *diff = VIEW(REQ_VIEW_DIFF);
4964                         const char *diff_index_argv[] = {
4965                                 "git", "diff-index", "--root", "--patch-with-stat",
4966                                         "-C", "-M", "HEAD", "--", view->vid, NULL
4967                         };
4969                         if (!blame->commit->has_previous) {
4970                                 diff_index_argv[1] = "diff";
4971                                 diff_index_argv[2] = "--no-color";
4972                                 diff_index_argv[6] = "--";
4973                                 diff_index_argv[7] = "/dev/null";
4974                         }
4976                         if (!prepare_update(diff, diff_index_argv, NULL, FORMAT_DASH)) {
4977                                 report("Failed to allocate diff command");
4978                                 break;
4979                         }
4980                         flags |= OPEN_PREPARED;
4981                 }
4983                 open_view(view, REQ_VIEW_DIFF, flags);
4984                 if (VIEW(REQ_VIEW_DIFF)->pipe && !strcmp(blame->commit->id, NULL_ID))
4985                         string_copy_rev(VIEW(REQ_VIEW_DIFF)->ref, NULL_ID);
4986                 break;
4988         default:
4989                 return request;
4990         }
4992         return REQ_NONE;
4995 static bool
4996 blame_grep(struct view *view, struct line *line)
4998         struct blame *blame = line->data;
4999         struct blame_commit *commit = blame->commit;
5000         const char *text[] = {
5001                 blame->text,
5002                 commit ? commit->title : "",
5003                 commit ? commit->id : "",
5004                 commit && opt_author ? commit->author : "",
5005                 commit && opt_date ? mkdate(&commit->time) : "",
5006                 NULL
5007         };
5009         return grep_text(view, text);
5012 static void
5013 blame_select(struct view *view, struct line *line)
5015         struct blame *blame = line->data;
5016         struct blame_commit *commit = blame->commit;
5018         if (!commit)
5019                 return;
5021         if (!strcmp(commit->id, NULL_ID))
5022                 string_ncopy(ref_commit, "HEAD", 4);
5023         else
5024                 string_copy_rev(ref_commit, commit->id);
5027 static struct view_ops blame_ops = {
5028         "line",
5029         NULL,
5030         blame_open,
5031         blame_read,
5032         blame_draw,
5033         blame_request,
5034         blame_grep,
5035         blame_select,
5036 };
5038 /*
5039  * Branch backend
5040  */
5042 struct branch {
5043         const char *author;             /* Author of the last commit. */
5044         time_t time;                    /* Date of the last activity. */
5045         const struct ref *ref;          /* Name and commit ID information. */
5046 };
5048 static const struct ref branch_all;
5050 static const enum sort_field branch_sort_fields[] = {
5051         ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
5052 };
5053 static struct sort_state branch_sort_state = SORT_STATE(branch_sort_fields);
5055 static int
5056 branch_compare(const void *l1, const void *l2)
5058         const struct branch *branch1 = ((const struct line *) l1)->data;
5059         const struct branch *branch2 = ((const struct line *) l2)->data;
5061         switch (get_sort_field(branch_sort_state)) {
5062         case ORDERBY_DATE:
5063                 return sort_order(branch_sort_state, branch1->time - branch2->time);
5065         case ORDERBY_AUTHOR:
5066                 return sort_order(branch_sort_state, strcmp(branch1->author, branch2->author));
5068         case ORDERBY_NAME:
5069         default:
5070                 return sort_order(branch_sort_state, strcmp(branch1->ref->name, branch2->ref->name));
5071         }
5074 static bool
5075 branch_draw(struct view *view, struct line *line, unsigned int lineno)
5077         struct branch *branch = line->data;
5078         enum line_type type = branch->ref->head ? LINE_MAIN_HEAD : LINE_DEFAULT;
5080         if (opt_date && draw_date(view, branch->author ? &branch->time : NULL))
5081                 return TRUE;
5083         if (opt_author && draw_author(view, branch->author))
5084                 return TRUE;
5086         draw_text(view, type, branch->ref == &branch_all ? "All branches" : branch->ref->name, TRUE);
5087         return TRUE;
5090 static enum request
5091 branch_request(struct view *view, enum request request, struct line *line)
5093         struct branch *branch = line->data;
5095         switch (request) {
5096         case REQ_REFRESH:
5097                 load_refs();
5098                 open_view(view, REQ_VIEW_BRANCH, OPEN_REFRESH);
5099                 return REQ_NONE;
5101         case REQ_TOGGLE_SORT_FIELD:
5102         case REQ_TOGGLE_SORT_ORDER:
5103                 sort_view(view, request, &branch_sort_state, branch_compare);
5104                 return REQ_NONE;
5106         case REQ_ENTER:
5107                 if (branch->ref == &branch_all) {
5108                         const char *all_branches_argv[] = {
5109                                 "git", "log", "--no-color", "--pretty=raw", "--parents",
5110                                       "--topo-order", "--all", NULL
5111                         };
5112                         struct view *main_view = VIEW(REQ_VIEW_MAIN);
5114                         if (!prepare_update(main_view, all_branches_argv, NULL, FORMAT_NONE)) {
5115                                 report("Failed to load view of all branches");
5116                                 return REQ_NONE;
5117                         }
5118                         open_view(view, REQ_VIEW_MAIN, OPEN_PREPARED | OPEN_SPLIT);
5119                 } else {
5120                         open_view(view, REQ_VIEW_MAIN, OPEN_SPLIT);
5121                 }
5122                 return REQ_NONE;
5124         default:
5125                 return request;
5126         }
5129 static bool
5130 branch_read(struct view *view, char *line)
5132         static char id[SIZEOF_REV];
5133         struct branch *reference;
5134         size_t i;
5136         if (!line)
5137                 return TRUE;
5139         switch (get_line_type(line)) {
5140         case LINE_COMMIT:
5141                 string_copy_rev(id, line + STRING_SIZE("commit "));
5142                 return TRUE;
5144         case LINE_AUTHOR:
5145                 for (i = 0, reference = NULL; i < view->lines; i++) {
5146                         struct branch *branch = view->line[i].data;
5148                         if (strcmp(branch->ref->id, id))
5149                                 continue;
5151                         view->line[i].dirty = TRUE;
5152                         if (reference) {
5153                                 branch->author = reference->author;
5154                                 branch->time = reference->time;
5155                                 continue;
5156                         }
5158                         parse_author_line(line + STRING_SIZE("author "),
5159                                           &branch->author, &branch->time);
5160                         reference = branch;
5161                 }
5162                 return TRUE;
5164         default:
5165                 return TRUE;
5166         }
5170 static bool
5171 branch_open_visitor(void *data, const struct ref *ref)
5173         struct view *view = data;
5174         struct branch *branch;
5176         if (ref->tag || ref->ltag || ref->remote)
5177                 return TRUE;
5179         branch = calloc(1, sizeof(*branch));
5180         if (!branch)
5181                 return FALSE;
5183         branch->ref = ref;
5184         return !!add_line_data(view, branch, LINE_DEFAULT);
5187 static bool
5188 branch_open(struct view *view)
5190         const char *branch_log[] = {
5191                 "git", "log", "--no-color", "--pretty=raw",
5192                         "--simplify-by-decoration", "--all", NULL
5193         };
5195         if (!run_io_rd(&view->io, branch_log, NULL, FORMAT_NONE)) {
5196                 report("Failed to load branch data");
5197                 return TRUE;
5198         }
5200         setup_update(view, view->id);
5201         branch_open_visitor(view, &branch_all);
5202         foreach_ref(branch_open_visitor, view);
5203         view->p_restore = TRUE;
5205         return TRUE;
5208 static bool
5209 branch_grep(struct view *view, struct line *line)
5211         struct branch *branch = line->data;
5212         const char *text[] = {
5213                 branch->ref->name,
5214                 branch->author,
5215                 NULL
5216         };
5218         return grep_text(view, text);
5221 static void
5222 branch_select(struct view *view, struct line *line)
5224         struct branch *branch = line->data;
5226         string_copy_rev(view->ref, branch->ref->id);
5227         string_copy_rev(ref_commit, branch->ref->id);
5228         string_copy_rev(ref_head, branch->ref->id);
5231 static struct view_ops branch_ops = {
5232         "branch",
5233         NULL,
5234         branch_open,
5235         branch_read,
5236         branch_draw,
5237         branch_request,
5238         branch_grep,
5239         branch_select,
5240 };
5242 /*
5243  * Status backend
5244  */
5246 struct status {
5247         char status;
5248         struct {
5249                 mode_t mode;
5250                 char rev[SIZEOF_REV];
5251                 char name[SIZEOF_STR];
5252         } old;
5253         struct {
5254                 mode_t mode;
5255                 char rev[SIZEOF_REV];
5256                 char name[SIZEOF_STR];
5257         } new;
5258 };
5260 static char status_onbranch[SIZEOF_STR];
5261 static struct status stage_status;
5262 static enum line_type stage_line_type;
5263 static size_t stage_chunks;
5264 static int *stage_chunk;
5266 DEFINE_ALLOCATOR(realloc_ints, int, 32)
5268 /* This should work even for the "On branch" line. */
5269 static inline bool
5270 status_has_none(struct view *view, struct line *line)
5272         return line < view->line + view->lines && !line[1].data;
5275 /* Get fields from the diff line:
5276  * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
5277  */
5278 static inline bool
5279 status_get_diff(struct status *file, const char *buf, size_t bufsize)
5281         const char *old_mode = buf +  1;
5282         const char *new_mode = buf +  8;
5283         const char *old_rev  = buf + 15;
5284         const char *new_rev  = buf + 56;
5285         const char *status   = buf + 97;
5287         if (bufsize < 98 ||
5288             old_mode[-1] != ':' ||
5289             new_mode[-1] != ' ' ||
5290             old_rev[-1]  != ' ' ||
5291             new_rev[-1]  != ' ' ||
5292             status[-1]   != ' ')
5293                 return FALSE;
5295         file->status = *status;
5297         string_copy_rev(file->old.rev, old_rev);
5298         string_copy_rev(file->new.rev, new_rev);
5300         file->old.mode = strtoul(old_mode, NULL, 8);
5301         file->new.mode = strtoul(new_mode, NULL, 8);
5303         file->old.name[0] = file->new.name[0] = 0;
5305         return TRUE;
5308 static bool
5309 status_run(struct view *view, const char *argv[], char status, enum line_type type)
5311         struct status *unmerged = NULL;
5312         char *buf;
5313         struct io io = {};
5315         if (!run_io(&io, argv, opt_cdup, IO_RD))
5316                 return FALSE;
5318         add_line_data(view, NULL, type);
5320         while ((buf = io_get(&io, 0, TRUE))) {
5321                 struct status *file = unmerged;
5323                 if (!file) {
5324                         file = calloc(1, sizeof(*file));
5325                         if (!file || !add_line_data(view, file, type))
5326                                 goto error_out;
5327                 }
5329                 /* Parse diff info part. */
5330                 if (status) {
5331                         file->status = status;
5332                         if (status == 'A')
5333                                 string_copy(file->old.rev, NULL_ID);
5335                 } else if (!file->status || file == unmerged) {
5336                         if (!status_get_diff(file, buf, strlen(buf)))
5337                                 goto error_out;
5339                         buf = io_get(&io, 0, TRUE);
5340                         if (!buf)
5341                                 break;
5343                         /* Collapse all modified entries that follow an
5344                          * associated unmerged entry. */
5345                         if (unmerged == file) {
5346                                 unmerged->status = 'U';
5347                                 unmerged = NULL;
5348                         } else if (file->status == 'U') {
5349                                 unmerged = file;
5350                         }
5351                 }
5353                 /* Grab the old name for rename/copy. */
5354                 if (!*file->old.name &&
5355                     (file->status == 'R' || file->status == 'C')) {
5356                         string_ncopy(file->old.name, buf, strlen(buf));
5358                         buf = io_get(&io, 0, TRUE);
5359                         if (!buf)
5360                                 break;
5361                 }
5363                 /* git-ls-files just delivers a NUL separated list of
5364                  * file names similar to the second half of the
5365                  * git-diff-* output. */
5366                 string_ncopy(file->new.name, buf, strlen(buf));
5367                 if (!*file->old.name)
5368                         string_copy(file->old.name, file->new.name);
5369                 file = NULL;
5370         }
5372         if (io_error(&io)) {
5373 error_out:
5374                 done_io(&io);
5375                 return FALSE;
5376         }
5378         if (!view->line[view->lines - 1].data)
5379                 add_line_data(view, NULL, LINE_STAT_NONE);
5381         done_io(&io);
5382         return TRUE;
5385 /* Don't show unmerged entries in the staged section. */
5386 static const char *status_diff_index_argv[] = {
5387         "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
5388                              "--cached", "-M", "HEAD", NULL
5389 };
5391 static const char *status_diff_files_argv[] = {
5392         "git", "diff-files", "-z", NULL
5393 };
5395 static const char *status_list_other_argv[] = {
5396         "git", "ls-files", "-z", "--others", "--exclude-standard", NULL
5397 };
5399 static const char *status_list_no_head_argv[] = {
5400         "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
5401 };
5403 static const char *update_index_argv[] = {
5404         "git", "update-index", "-q", "--unmerged", "--refresh", NULL
5405 };
5407 /* Restore the previous line number to stay in the context or select a
5408  * line with something that can be updated. */
5409 static void
5410 status_restore(struct view *view)
5412         if (view->p_lineno >= view->lines)
5413                 view->p_lineno = view->lines - 1;
5414         while (view->p_lineno < view->lines && !view->line[view->p_lineno].data)
5415                 view->p_lineno++;
5416         while (view->p_lineno > 0 && !view->line[view->p_lineno].data)
5417                 view->p_lineno--;
5419         /* If the above fails, always skip the "On branch" line. */
5420         if (view->p_lineno < view->lines)
5421                 view->lineno = view->p_lineno;
5422         else
5423                 view->lineno = 1;
5425         if (view->lineno < view->offset)
5426                 view->offset = view->lineno;
5427         else if (view->offset + view->height <= view->lineno)
5428                 view->offset = view->lineno - view->height + 1;
5430         view->p_restore = FALSE;
5433 static void
5434 status_update_onbranch(void)
5436         static const char *paths[][2] = {
5437                 { "rebase-apply/rebasing",      "Rebasing" },
5438                 { "rebase-apply/applying",      "Applying mailbox" },
5439                 { "rebase-apply/",              "Rebasing mailbox" },
5440                 { "rebase-merge/interactive",   "Interactive rebase" },
5441                 { "rebase-merge/",              "Rebase merge" },
5442                 { "MERGE_HEAD",                 "Merging" },
5443                 { "BISECT_LOG",                 "Bisecting" },
5444                 { "HEAD",                       "On branch" },
5445         };
5446         char buf[SIZEOF_STR];
5447         struct stat stat;
5448         int i;
5450         if (is_initial_commit()) {
5451                 string_copy(status_onbranch, "Initial commit");
5452                 return;
5453         }
5455         for (i = 0; i < ARRAY_SIZE(paths); i++) {
5456                 char *head = opt_head;
5458                 if (!string_format(buf, "%s/%s", opt_git_dir, paths[i][0]) ||
5459                     lstat(buf, &stat) < 0)
5460                         continue;
5462                 if (!*opt_head) {
5463                         struct io io = {};
5465                         if (io_open(&io, "%s/rebase-merge/head-name", opt_git_dir) &&
5466                             io_read_buf(&io, buf, sizeof(buf))) {
5467                                 head = buf;
5468                                 if (!prefixcmp(head, "refs/heads/"))
5469                                         head += STRING_SIZE("refs/heads/");
5470                         }
5471                 }
5473                 if (!string_format(status_onbranch, "%s %s", paths[i][1], head))
5474                         string_copy(status_onbranch, opt_head);
5475                 return;
5476         }
5478         string_copy(status_onbranch, "Not currently on any branch");
5481 /* First parse staged info using git-diff-index(1), then parse unstaged
5482  * info using git-diff-files(1), and finally untracked files using
5483  * git-ls-files(1). */
5484 static bool
5485 status_open(struct view *view)
5487         reset_view(view);
5489         add_line_data(view, NULL, LINE_STAT_HEAD);
5490         status_update_onbranch();
5492         run_io_bg(update_index_argv);
5494         if (is_initial_commit()) {
5495                 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
5496                         return FALSE;
5497         } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
5498                 return FALSE;
5499         }
5501         if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
5502             !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
5503                 return FALSE;
5505         /* Restore the exact position or use the specialized restore
5506          * mode? */
5507         if (!view->p_restore)
5508                 status_restore(view);
5509         return TRUE;
5512 static bool
5513 status_draw(struct view *view, struct line *line, unsigned int lineno)
5515         struct status *status = line->data;
5516         enum line_type type;
5517         const char *text;
5519         if (!status) {
5520                 switch (line->type) {
5521                 case LINE_STAT_STAGED:
5522                         type = LINE_STAT_SECTION;
5523                         text = "Changes to be committed:";
5524                         break;
5526                 case LINE_STAT_UNSTAGED:
5527                         type = LINE_STAT_SECTION;
5528                         text = "Changed but not updated:";
5529                         break;
5531                 case LINE_STAT_UNTRACKED:
5532                         type = LINE_STAT_SECTION;
5533                         text = "Untracked files:";
5534                         break;
5536                 case LINE_STAT_NONE:
5537                         type = LINE_DEFAULT;
5538                         text = "  (no files)";
5539                         break;
5541                 case LINE_STAT_HEAD:
5542                         type = LINE_STAT_HEAD;
5543                         text = status_onbranch;
5544                         break;
5546                 default:
5547                         return FALSE;
5548                 }
5549         } else {
5550                 static char buf[] = { '?', ' ', ' ', ' ', 0 };
5552                 buf[0] = status->status;
5553                 if (draw_text(view, line->type, buf, TRUE))
5554                         return TRUE;
5555                 type = LINE_DEFAULT;
5556                 text = status->new.name;
5557         }
5559         draw_text(view, type, text, TRUE);
5560         return TRUE;
5563 static enum request
5564 status_load_error(struct view *view, struct view *stage, const char *path)
5566         if (displayed_views() == 2 || display[current_view] != view)
5567                 maximize_view(view);
5568         report("Failed to load '%s': %s", path, io_strerror(&stage->io));
5569         return REQ_NONE;
5572 static enum request
5573 status_enter(struct view *view, struct line *line)
5575         struct status *status = line->data;
5576         const char *oldpath = status ? status->old.name : NULL;
5577         /* Diffs for unmerged entries are empty when passing the new
5578          * path, so leave it empty. */
5579         const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
5580         const char *info;
5581         enum open_flags split;
5582         struct view *stage = VIEW(REQ_VIEW_STAGE);
5584         if (line->type == LINE_STAT_NONE ||
5585             (!status && line[1].type == LINE_STAT_NONE)) {
5586                 report("No file to diff");
5587                 return REQ_NONE;
5588         }
5590         switch (line->type) {
5591         case LINE_STAT_STAGED:
5592                 if (is_initial_commit()) {
5593                         const char *no_head_diff_argv[] = {
5594                                 "git", "diff", "--no-color", "--patch-with-stat",
5595                                         "--", "/dev/null", newpath, NULL
5596                         };
5598                         if (!prepare_update(stage, no_head_diff_argv, opt_cdup, FORMAT_DASH))
5599                                 return status_load_error(view, stage, newpath);
5600                 } else {
5601                         const char *index_show_argv[] = {
5602                                 "git", "diff-index", "--root", "--patch-with-stat",
5603                                         "-C", "-M", "--cached", "HEAD", "--",
5604                                         oldpath, newpath, NULL
5605                         };
5607                         if (!prepare_update(stage, index_show_argv, opt_cdup, FORMAT_DASH))
5608                                 return status_load_error(view, stage, newpath);
5609                 }
5611                 if (status)
5612                         info = "Staged changes to %s";
5613                 else
5614                         info = "Staged changes";
5615                 break;
5617         case LINE_STAT_UNSTAGED:
5618         {
5619                 const char *files_show_argv[] = {
5620                         "git", "diff-files", "--root", "--patch-with-stat",
5621                                 "-C", "-M", "--", oldpath, newpath, NULL
5622                 };
5624                 if (!prepare_update(stage, files_show_argv, opt_cdup, FORMAT_DASH))
5625                         return status_load_error(view, stage, newpath);
5626                 if (status)
5627                         info = "Unstaged changes to %s";
5628                 else
5629                         info = "Unstaged changes";
5630                 break;
5631         }
5632         case LINE_STAT_UNTRACKED:
5633                 if (!newpath) {
5634                         report("No file to show");
5635                         return REQ_NONE;
5636                 }
5638                 if (!suffixcmp(status->new.name, -1, "/")) {
5639                         report("Cannot display a directory");
5640                         return REQ_NONE;
5641                 }
5643                 if (!prepare_update_file(stage, newpath))
5644                         return status_load_error(view, stage, newpath);
5645                 info = "Untracked file %s";
5646                 break;
5648         case LINE_STAT_HEAD:
5649                 return REQ_NONE;
5651         default:
5652                 die("line type %d not handled in switch", line->type);
5653         }
5655         split = view_is_displayed(view) ? OPEN_SPLIT : 0;
5656         open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split);
5657         if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
5658                 if (status) {
5659                         stage_status = *status;
5660                 } else {
5661                         memset(&stage_status, 0, sizeof(stage_status));
5662                 }
5664                 stage_line_type = line->type;
5665                 stage_chunks = 0;
5666                 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
5667         }
5669         return REQ_NONE;
5672 static bool
5673 status_exists(struct status *status, enum line_type type)
5675         struct view *view = VIEW(REQ_VIEW_STATUS);
5676         unsigned long lineno;
5678         for (lineno = 0; lineno < view->lines; lineno++) {
5679                 struct line *line = &view->line[lineno];
5680                 struct status *pos = line->data;
5682                 if (line->type != type)
5683                         continue;
5684                 if (!pos && (!status || !status->status) && line[1].data) {
5685                         select_view_line(view, lineno);
5686                         return TRUE;
5687                 }
5688                 if (pos && !strcmp(status->new.name, pos->new.name)) {
5689                         select_view_line(view, lineno);
5690                         return TRUE;
5691                 }
5692         }
5694         return FALSE;
5698 static bool
5699 status_update_prepare(struct io *io, enum line_type type)
5701         const char *staged_argv[] = {
5702                 "git", "update-index", "-z", "--index-info", NULL
5703         };
5704         const char *others_argv[] = {
5705                 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
5706         };
5708         switch (type) {
5709         case LINE_STAT_STAGED:
5710                 return run_io(io, staged_argv, opt_cdup, IO_WR);
5712         case LINE_STAT_UNSTAGED:
5713         case LINE_STAT_UNTRACKED:
5714                 return run_io(io, others_argv, opt_cdup, IO_WR);
5716         default:
5717                 die("line type %d not handled in switch", type);
5718                 return FALSE;
5719         }
5722 static bool
5723 status_update_write(struct io *io, struct status *status, enum line_type type)
5725         char buf[SIZEOF_STR];
5726         size_t bufsize = 0;
5728         switch (type) {
5729         case LINE_STAT_STAGED:
5730                 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
5731                                         status->old.mode,
5732                                         status->old.rev,
5733                                         status->old.name, 0))
5734                         return FALSE;
5735                 break;
5737         case LINE_STAT_UNSTAGED:
5738         case LINE_STAT_UNTRACKED:
5739                 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
5740                         return FALSE;
5741                 break;
5743         default:
5744                 die("line type %d not handled in switch", type);
5745         }
5747         return io_write(io, buf, bufsize);
5750 static bool
5751 status_update_file(struct status *status, enum line_type type)
5753         struct io io = {};
5754         bool result;
5756         if (!status_update_prepare(&io, type))
5757                 return FALSE;
5759         result = status_update_write(&io, status, type);
5760         return done_io(&io) && result;
5763 static bool
5764 status_update_files(struct view *view, struct line *line)
5766         char buf[sizeof(view->ref)];
5767         struct io io = {};
5768         bool result = TRUE;
5769         struct line *pos = view->line + view->lines;
5770         int files = 0;
5771         int file, done;
5772         int cursor_y = -1, cursor_x = -1;
5774         if (!status_update_prepare(&io, line->type))
5775                 return FALSE;
5777         for (pos = line; pos < view->line + view->lines && pos->data; pos++)
5778                 files++;
5780         string_copy(buf, view->ref);
5781         getsyx(cursor_y, cursor_x);
5782         for (file = 0, done = 5; result && file < files; line++, file++) {
5783                 int almost_done = file * 100 / files;
5785                 if (almost_done > done) {
5786                         done = almost_done;
5787                         string_format(view->ref, "updating file %u of %u (%d%% done)",
5788                                       file, files, done);
5789                         update_view_title(view);
5790                         setsyx(cursor_y, cursor_x);
5791                         doupdate();
5792                 }
5793                 result = status_update_write(&io, line->data, line->type);
5794         }
5795         string_copy(view->ref, buf);
5797         return done_io(&io) && result;
5800 static bool
5801 status_update(struct view *view)
5803         struct line *line = &view->line[view->lineno];
5805         assert(view->lines);
5807         if (!line->data) {
5808                 /* This should work even for the "On branch" line. */
5809                 if (line < view->line + view->lines && !line[1].data) {
5810                         report("Nothing to update");
5811                         return FALSE;
5812                 }
5814                 if (!status_update_files(view, line + 1)) {
5815                         report("Failed to update file status");
5816                         return FALSE;
5817                 }
5819         } else if (!status_update_file(line->data, line->type)) {
5820                 report("Failed to update file status");
5821                 return FALSE;
5822         }
5824         return TRUE;
5827 static bool
5828 status_revert(struct status *status, enum line_type type, bool has_none)
5830         if (!status || type != LINE_STAT_UNSTAGED) {
5831                 if (type == LINE_STAT_STAGED) {
5832                         report("Cannot revert changes to staged files");
5833                 } else if (type == LINE_STAT_UNTRACKED) {
5834                         report("Cannot revert changes to untracked files");
5835                 } else if (has_none) {
5836                         report("Nothing to revert");
5837                 } else {
5838                         report("Cannot revert changes to multiple files");
5839                 }
5841         } else if (prompt_yesno("Are you sure you want to revert changes?")) {
5842                 char mode[10] = "100644";
5843                 const char *reset_argv[] = {
5844                         "git", "update-index", "--cacheinfo", mode,
5845                                 status->old.rev, status->old.name, NULL
5846                 };
5847                 const char *checkout_argv[] = {
5848                         "git", "checkout", "--", status->old.name, NULL
5849                 };
5851                 if (status->status == 'U') {
5852                         string_format(mode, "%5o", status->old.mode);
5854                         if (status->old.mode == 0 && status->new.mode == 0) {
5855                                 reset_argv[2] = "--force-remove";
5856                                 reset_argv[3] = status->old.name;
5857                                 reset_argv[4] = NULL;
5858                         }
5860                         if (!run_io_fg(reset_argv, opt_cdup))
5861                                 return FALSE;
5862                         if (status->old.mode == 0 && status->new.mode == 0)
5863                                 return TRUE;
5864                 }
5866                 return run_io_fg(checkout_argv, opt_cdup);
5867         }
5869         return FALSE;
5872 static enum request
5873 status_request(struct view *view, enum request request, struct line *line)
5875         struct status *status = line->data;
5877         switch (request) {
5878         case REQ_STATUS_UPDATE:
5879                 if (!status_update(view))
5880                         return REQ_NONE;
5881                 break;
5883         case REQ_STATUS_REVERT:
5884                 if (!status_revert(status, line->type, status_has_none(view, line)))
5885                         return REQ_NONE;
5886                 break;
5888         case REQ_STATUS_MERGE:
5889                 if (!status || status->status != 'U') {
5890                         report("Merging only possible for files with unmerged status ('U').");
5891                         return REQ_NONE;
5892                 }
5893                 open_mergetool(status->new.name);
5894                 break;
5896         case REQ_EDIT:
5897                 if (!status)
5898                         return request;
5899                 if (status->status == 'D') {
5900                         report("File has been deleted.");
5901                         return REQ_NONE;
5902                 }
5904                 open_editor(status->status != '?', status->new.name);
5905                 break;
5907         case REQ_VIEW_BLAME:
5908                 if (status)
5909                         opt_ref[0] = 0;
5910                 return request;
5912         case REQ_ENTER:
5913                 /* After returning the status view has been split to
5914                  * show the stage view. No further reloading is
5915                  * necessary. */
5916                 return status_enter(view, line);
5918         case REQ_REFRESH:
5919                 /* Simply reload the view. */
5920                 break;
5922         default:
5923                 return request;
5924         }
5926         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
5928         return REQ_NONE;
5931 static void
5932 status_select(struct view *view, struct line *line)
5934         struct status *status = line->data;
5935         char file[SIZEOF_STR] = "all files";
5936         const char *text;
5937         const char *key;
5939         if (status && !string_format(file, "'%s'", status->new.name))
5940                 return;
5942         if (!status && line[1].type == LINE_STAT_NONE)
5943                 line++;
5945         switch (line->type) {
5946         case LINE_STAT_STAGED:
5947                 text = "Press %s to unstage %s for commit";
5948                 break;
5950         case LINE_STAT_UNSTAGED:
5951                 text = "Press %s to stage %s for commit";
5952                 break;
5954         case LINE_STAT_UNTRACKED:
5955                 text = "Press %s to stage %s for addition";
5956                 break;
5958         case LINE_STAT_HEAD:
5959         case LINE_STAT_NONE:
5960                 text = "Nothing to update";
5961                 break;
5963         default:
5964                 die("line type %d not handled in switch", line->type);
5965         }
5967         if (status && status->status == 'U') {
5968                 text = "Press %s to resolve conflict in %s";
5969                 key = get_key(KEYMAP_STATUS, REQ_STATUS_MERGE);
5971         } else {
5972                 key = get_key(KEYMAP_STATUS, REQ_STATUS_UPDATE);
5973         }
5975         string_format(view->ref, text, key, file);
5976         if (status)
5977                 string_copy(opt_file, status->new.name);
5980 static bool
5981 status_grep(struct view *view, struct line *line)
5983         struct status *status = line->data;
5985         if (status) {
5986                 const char buf[2] = { status->status, 0 };
5987                 const char *text[] = { status->new.name, buf, NULL };
5989                 return grep_text(view, text);
5990         }
5992         return FALSE;
5995 static struct view_ops status_ops = {
5996         "file",
5997         NULL,
5998         status_open,
5999         NULL,
6000         status_draw,
6001         status_request,
6002         status_grep,
6003         status_select,
6004 };
6007 static bool
6008 stage_diff_write(struct io *io, struct line *line, struct line *end)
6010         while (line < end) {
6011                 if (!io_write(io, line->data, strlen(line->data)) ||
6012                     !io_write(io, "\n", 1))
6013                         return FALSE;
6014                 line++;
6015                 if (line->type == LINE_DIFF_CHUNK ||
6016                     line->type == LINE_DIFF_HEADER)
6017                         break;
6018         }
6020         return TRUE;
6023 static struct line *
6024 stage_diff_find(struct view *view, struct line *line, enum line_type type)
6026         for (; view->line < line; line--)
6027                 if (line->type == type)
6028                         return line;
6030         return NULL;
6033 static bool
6034 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
6036         const char *apply_argv[SIZEOF_ARG] = {
6037                 "git", "apply", "--whitespace=nowarn", NULL
6038         };
6039         struct line *diff_hdr;
6040         struct io io = {};
6041         int argc = 3;
6043         diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
6044         if (!diff_hdr)
6045                 return FALSE;
6047         if (!revert)
6048                 apply_argv[argc++] = "--cached";
6049         if (revert || stage_line_type == LINE_STAT_STAGED)
6050                 apply_argv[argc++] = "-R";
6051         apply_argv[argc++] = "-";
6052         apply_argv[argc++] = NULL;
6053         if (!run_io(&io, apply_argv, opt_cdup, IO_WR))
6054                 return FALSE;
6056         if (!stage_diff_write(&io, diff_hdr, chunk) ||
6057             !stage_diff_write(&io, chunk, view->line + view->lines))
6058                 chunk = NULL;
6060         done_io(&io);
6061         run_io_bg(update_index_argv);
6063         return chunk ? TRUE : FALSE;
6066 static bool
6067 stage_update(struct view *view, struct line *line)
6069         struct line *chunk = NULL;
6071         if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
6072                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6074         if (chunk) {
6075                 if (!stage_apply_chunk(view, chunk, FALSE)) {
6076                         report("Failed to apply chunk");
6077                         return FALSE;
6078                 }
6080         } else if (!stage_status.status) {
6081                 view = VIEW(REQ_VIEW_STATUS);
6083                 for (line = view->line; line < view->line + view->lines; line++)
6084                         if (line->type == stage_line_type)
6085                                 break;
6087                 if (!status_update_files(view, line + 1)) {
6088                         report("Failed to update files");
6089                         return FALSE;
6090                 }
6092         } else if (!status_update_file(&stage_status, stage_line_type)) {
6093                 report("Failed to update file");
6094                 return FALSE;
6095         }
6097         return TRUE;
6100 static bool
6101 stage_revert(struct view *view, struct line *line)
6103         struct line *chunk = NULL;
6105         if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
6106                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6108         if (chunk) {
6109                 if (!prompt_yesno("Are you sure you want to revert changes?"))
6110                         return FALSE;
6112                 if (!stage_apply_chunk(view, chunk, TRUE)) {
6113                         report("Failed to revert chunk");
6114                         return FALSE;
6115                 }
6116                 return TRUE;
6118         } else {
6119                 return status_revert(stage_status.status ? &stage_status : NULL,
6120                                      stage_line_type, FALSE);
6121         }
6125 static void
6126 stage_next(struct view *view, struct line *line)
6128         int i;
6130         if (!stage_chunks) {
6131                 for (line = view->line; line < view->line + view->lines; line++) {
6132                         if (line->type != LINE_DIFF_CHUNK)
6133                                 continue;
6135                         if (!realloc_ints(&stage_chunk, stage_chunks, 1)) {
6136                                 report("Allocation failure");
6137                                 return;
6138                         }
6140                         stage_chunk[stage_chunks++] = line - view->line;
6141                 }
6142         }
6144         for (i = 0; i < stage_chunks; i++) {
6145                 if (stage_chunk[i] > view->lineno) {
6146                         do_scroll_view(view, stage_chunk[i] - view->lineno);
6147                         report("Chunk %d of %d", i + 1, stage_chunks);
6148                         return;
6149                 }
6150         }
6152         report("No next chunk found");
6155 static enum request
6156 stage_request(struct view *view, enum request request, struct line *line)
6158         switch (request) {
6159         case REQ_STATUS_UPDATE:
6160                 if (!stage_update(view, line))
6161                         return REQ_NONE;
6162                 break;
6164         case REQ_STATUS_REVERT:
6165                 if (!stage_revert(view, line))
6166                         return REQ_NONE;
6167                 break;
6169         case REQ_STAGE_NEXT:
6170                 if (stage_line_type == LINE_STAT_UNTRACKED) {
6171                         report("File is untracked; press %s to add",
6172                                get_key(KEYMAP_STAGE, REQ_STATUS_UPDATE));
6173                         return REQ_NONE;
6174                 }
6175                 stage_next(view, line);
6176                 return REQ_NONE;
6178         case REQ_EDIT:
6179                 if (!stage_status.new.name[0])
6180                         return request;
6181                 if (stage_status.status == 'D') {
6182                         report("File has been deleted.");
6183                         return REQ_NONE;
6184                 }
6186                 open_editor(stage_status.status != '?', stage_status.new.name);
6187                 break;
6189         case REQ_REFRESH:
6190                 /* Reload everything ... */
6191                 break;
6193         case REQ_VIEW_BLAME:
6194                 if (stage_status.new.name[0]) {
6195                         string_copy(opt_file, stage_status.new.name);
6196                         opt_ref[0] = 0;
6197                 }
6198                 return request;
6200         case REQ_ENTER:
6201                 return pager_request(view, request, line);
6203         default:
6204                 return request;
6205         }
6207         VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
6208         open_view(view, REQ_VIEW_STATUS, OPEN_REFRESH);
6210         /* Check whether the staged entry still exists, and close the
6211          * stage view if it doesn't. */
6212         if (!status_exists(&stage_status, stage_line_type)) {
6213                 status_restore(VIEW(REQ_VIEW_STATUS));
6214                 return REQ_VIEW_CLOSE;
6215         }
6217         if (stage_line_type == LINE_STAT_UNTRACKED) {
6218                 if (!suffixcmp(stage_status.new.name, -1, "/")) {
6219                         report("Cannot display a directory");
6220                         return REQ_NONE;
6221                 }
6223                 if (!prepare_update_file(view, stage_status.new.name)) {
6224                         report("Failed to open file: %s", strerror(errno));
6225                         return REQ_NONE;
6226                 }
6227         }
6228         open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
6230         return REQ_NONE;
6233 static struct view_ops stage_ops = {
6234         "line",
6235         NULL,
6236         NULL,
6237         pager_read,
6238         pager_draw,
6239         stage_request,
6240         pager_grep,
6241         pager_select,
6242 };
6245 /*
6246  * Revision graph
6247  */
6249 struct commit {
6250         char id[SIZEOF_REV];            /* SHA1 ID. */
6251         char title[128];                /* First line of the commit message. */
6252         const char *author;             /* Author of the commit. */
6253         time_t time;                    /* Date from the author ident. */
6254         struct ref_list *refs;          /* Repository references. */
6255         chtype graph[SIZEOF_REVGRAPH];  /* Ancestry chain graphics. */
6256         size_t graph_size;              /* The width of the graph array. */
6257         bool has_parents;               /* Rewritten --parents seen. */
6258 };
6260 /* Size of rev graph with no  "padding" columns */
6261 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
6263 struct rev_graph {
6264         struct rev_graph *prev, *next, *parents;
6265         char rev[SIZEOF_REVITEMS][SIZEOF_REV];
6266         size_t size;
6267         struct commit *commit;
6268         size_t pos;
6269         unsigned int boundary:1;
6270 };
6272 /* Parents of the commit being visualized. */
6273 static struct rev_graph graph_parents[4];
6275 /* The current stack of revisions on the graph. */
6276 static struct rev_graph graph_stacks[4] = {
6277         { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
6278         { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
6279         { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
6280         { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
6281 };
6283 static inline bool
6284 graph_parent_is_merge(struct rev_graph *graph)
6286         return graph->parents->size > 1;
6289 static inline void
6290 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
6292         struct commit *commit = graph->commit;
6294         if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
6295                 commit->graph[commit->graph_size++] = symbol;
6298 static void
6299 clear_rev_graph(struct rev_graph *graph)
6301         graph->boundary = 0;
6302         graph->size = graph->pos = 0;
6303         graph->commit = NULL;
6304         memset(graph->parents, 0, sizeof(*graph->parents));
6307 static void
6308 done_rev_graph(struct rev_graph *graph)
6310         if (graph_parent_is_merge(graph) &&
6311             graph->pos < graph->size - 1 &&
6312             graph->next->size == graph->size + graph->parents->size - 1) {
6313                 size_t i = graph->pos + graph->parents->size - 1;
6315                 graph->commit->graph_size = i * 2;
6316                 while (i < graph->next->size - 1) {
6317                         append_to_rev_graph(graph, ' ');
6318                         append_to_rev_graph(graph, '\\');
6319                         i++;
6320                 }
6321         }
6323         clear_rev_graph(graph);
6326 static void
6327 push_rev_graph(struct rev_graph *graph, const char *parent)
6329         int i;
6331         /* "Collapse" duplicate parents lines.
6332          *
6333          * FIXME: This needs to also update update the drawn graph but
6334          * for now it just serves as a method for pruning graph lines. */
6335         for (i = 0; i < graph->size; i++)
6336                 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
6337                         return;
6339         if (graph->size < SIZEOF_REVITEMS) {
6340                 string_copy_rev(graph->rev[graph->size++], parent);
6341         }
6344 static chtype
6345 get_rev_graph_symbol(struct rev_graph *graph)
6347         chtype symbol;
6349         if (graph->boundary)
6350                 symbol = REVGRAPH_BOUND;
6351         else if (graph->parents->size == 0)
6352                 symbol = REVGRAPH_INIT;
6353         else if (graph_parent_is_merge(graph))
6354                 symbol = REVGRAPH_MERGE;
6355         else if (graph->pos >= graph->size)
6356                 symbol = REVGRAPH_BRANCH;
6357         else
6358                 symbol = REVGRAPH_COMMIT;
6360         return symbol;
6363 static void
6364 draw_rev_graph(struct rev_graph *graph)
6366         struct rev_filler {
6367                 chtype separator, line;
6368         };
6369         enum { DEFAULT, RSHARP, RDIAG, LDIAG };
6370         static struct rev_filler fillers[] = {
6371                 { ' ',  '|' },
6372                 { '`',  '.' },
6373                 { '\'', ' ' },
6374                 { '/',  ' ' },
6375         };
6376         chtype symbol = get_rev_graph_symbol(graph);
6377         struct rev_filler *filler;
6378         size_t i;
6380         if (opt_line_graphics)
6381                 fillers[DEFAULT].line = line_graphics[LINE_GRAPHIC_VLINE];
6383         filler = &fillers[DEFAULT];
6385         for (i = 0; i < graph->pos; i++) {
6386                 append_to_rev_graph(graph, filler->line);
6387                 if (graph_parent_is_merge(graph->prev) &&
6388                     graph->prev->pos == i)
6389                         filler = &fillers[RSHARP];
6391                 append_to_rev_graph(graph, filler->separator);
6392         }
6394         /* Place the symbol for this revision. */
6395         append_to_rev_graph(graph, symbol);
6397         if (graph->prev->size > graph->size)
6398                 filler = &fillers[RDIAG];
6399         else
6400                 filler = &fillers[DEFAULT];
6402         i++;
6404         for (; i < graph->size; i++) {
6405                 append_to_rev_graph(graph, filler->separator);
6406                 append_to_rev_graph(graph, filler->line);
6407                 if (graph_parent_is_merge(graph->prev) &&
6408                     i < graph->prev->pos + graph->parents->size)
6409                         filler = &fillers[RSHARP];
6410                 if (graph->prev->size > graph->size)
6411                         filler = &fillers[LDIAG];
6412         }
6414         if (graph->prev->size > graph->size) {
6415                 append_to_rev_graph(graph, filler->separator);
6416                 if (filler->line != ' ')
6417                         append_to_rev_graph(graph, filler->line);
6418         }
6421 /* Prepare the next rev graph */
6422 static void
6423 prepare_rev_graph(struct rev_graph *graph)
6425         size_t i;
6427         /* First, traverse all lines of revisions up to the active one. */
6428         for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
6429                 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
6430                         break;
6432                 push_rev_graph(graph->next, graph->rev[graph->pos]);
6433         }
6435         /* Interleave the new revision parent(s). */
6436         for (i = 0; !graph->boundary && i < graph->parents->size; i++)
6437                 push_rev_graph(graph->next, graph->parents->rev[i]);
6439         /* Lastly, put any remaining revisions. */
6440         for (i = graph->pos + 1; i < graph->size; i++)
6441                 push_rev_graph(graph->next, graph->rev[i]);
6444 static void
6445 update_rev_graph(struct view *view, struct rev_graph *graph)
6447         /* If this is the finalizing update ... */
6448         if (graph->commit)
6449                 prepare_rev_graph(graph);
6451         /* Graph visualization needs a one rev look-ahead,
6452          * so the first update doesn't visualize anything. */
6453         if (!graph->prev->commit)
6454                 return;
6456         if (view->lines > 2)
6457                 view->line[view->lines - 3].dirty = 1;
6458         if (view->lines > 1)
6459                 view->line[view->lines - 2].dirty = 1;
6460         draw_rev_graph(graph->prev);
6461         done_rev_graph(graph->prev->prev);
6465 /*
6466  * Main view backend
6467  */
6469 static const char *main_argv[SIZEOF_ARG] = {
6470         "git", "log", "--no-color", "--pretty=raw", "--parents",
6471                       "--topo-order", "%(head)", NULL
6472 };
6474 static bool
6475 main_draw(struct view *view, struct line *line, unsigned int lineno)
6477         struct commit *commit = line->data;
6479         if (!commit->author)
6480                 return FALSE;
6482         if (opt_date && draw_date(view, &commit->time))
6483                 return TRUE;
6485         if (opt_author && draw_author(view, commit->author))
6486                 return TRUE;
6488         if (opt_rev_graph && commit->graph_size &&
6489             draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
6490                 return TRUE;
6492         if (opt_show_refs && commit->refs) {
6493                 size_t i;
6495                 for (i = 0; i < commit->refs->size; i++) {
6496                         struct ref *ref = commit->refs->refs[i];
6497                         enum line_type type;
6499                         if (ref->head)
6500                                 type = LINE_MAIN_HEAD;
6501                         else if (ref->ltag)
6502                                 type = LINE_MAIN_LOCAL_TAG;
6503                         else if (ref->tag)
6504                                 type = LINE_MAIN_TAG;
6505                         else if (ref->tracked)
6506                                 type = LINE_MAIN_TRACKED;
6507                         else if (ref->remote)
6508                                 type = LINE_MAIN_REMOTE;
6509                         else
6510                                 type = LINE_MAIN_REF;
6512                         if (draw_text(view, type, "[", TRUE) ||
6513                             draw_text(view, type, ref->name, TRUE) ||
6514                             draw_text(view, type, "]", TRUE))
6515                                 return TRUE;
6517                         if (draw_text(view, LINE_DEFAULT, " ", TRUE))
6518                                 return TRUE;
6519                 }
6520         }
6522         draw_text(view, LINE_DEFAULT, commit->title, TRUE);
6523         return TRUE;
6526 /* Reads git log --pretty=raw output and parses it into the commit struct. */
6527 static bool
6528 main_read(struct view *view, char *line)
6530         static struct rev_graph *graph = graph_stacks;
6531         enum line_type type;
6532         struct commit *commit;
6534         if (!line) {
6535                 int i;
6537                 if (!view->lines && !view->parent)
6538                         die("No revisions match the given arguments.");
6539                 if (view->lines > 0) {
6540                         commit = view->line[view->lines - 1].data;
6541                         view->line[view->lines - 1].dirty = 1;
6542                         if (!commit->author) {
6543                                 view->lines--;
6544                                 free(commit);
6545                                 graph->commit = NULL;
6546                         }
6547                 }
6548                 update_rev_graph(view, graph);
6550                 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
6551                         clear_rev_graph(&graph_stacks[i]);
6552                 return TRUE;
6553         }
6555         type = get_line_type(line);
6556         if (type == LINE_COMMIT) {
6557                 commit = calloc(1, sizeof(struct commit));
6558                 if (!commit)
6559                         return FALSE;
6561                 line += STRING_SIZE("commit ");
6562                 if (*line == '-') {
6563                         graph->boundary = 1;
6564                         line++;
6565                 }
6567                 string_copy_rev(commit->id, line);
6568                 commit->refs = get_ref_list(commit->id);
6569                 graph->commit = commit;
6570                 add_line_data(view, commit, LINE_MAIN_COMMIT);
6572                 while ((line = strchr(line, ' '))) {
6573                         line++;
6574                         push_rev_graph(graph->parents, line);
6575                         commit->has_parents = TRUE;
6576                 }
6577                 return TRUE;
6578         }
6580         if (!view->lines)
6581                 return TRUE;
6582         commit = view->line[view->lines - 1].data;
6584         switch (type) {
6585         case LINE_PARENT:
6586                 if (commit->has_parents)
6587                         break;
6588                 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
6589                 break;
6591         case LINE_AUTHOR:
6592                 parse_author_line(line + STRING_SIZE("author "),
6593                                   &commit->author, &commit->time);
6594                 update_rev_graph(view, graph);
6595                 graph = graph->next;
6596                 break;
6598         default:
6599                 /* Fill in the commit title if it has not already been set. */
6600                 if (commit->title[0])
6601                         break;
6603                 /* Require titles to start with a non-space character at the
6604                  * offset used by git log. */
6605                 if (strncmp(line, "    ", 4))
6606                         break;
6607                 line += 4;
6608                 /* Well, if the title starts with a whitespace character,
6609                  * try to be forgiving.  Otherwise we end up with no title. */
6610                 while (isspace(*line))
6611                         line++;
6612                 if (*line == '\0')
6613                         break;
6614                 /* FIXME: More graceful handling of titles; append "..." to
6615                  * shortened titles, etc. */
6617                 string_expand(commit->title, sizeof(commit->title), line, 1);
6618                 view->line[view->lines - 1].dirty = 1;
6619         }
6621         return TRUE;
6624 static enum request
6625 main_request(struct view *view, enum request request, struct line *line)
6627         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
6629         switch (request) {
6630         case REQ_ENTER:
6631                 open_view(view, REQ_VIEW_DIFF, flags);
6632                 break;
6633         case REQ_REFRESH:
6634                 load_refs();
6635                 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
6636                 break;
6637         default:
6638                 return request;
6639         }
6641         return REQ_NONE;
6644 static bool
6645 grep_refs(struct ref_list *list, regex_t *regex)
6647         regmatch_t pmatch;
6648         size_t i;
6650         if (!opt_show_refs || !list)
6651                 return FALSE;
6653         for (i = 0; i < list->size; i++) {
6654                 if (regexec(regex, list->refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
6655                         return TRUE;
6656         }
6658         return FALSE;
6661 static bool
6662 main_grep(struct view *view, struct line *line)
6664         struct commit *commit = line->data;
6665         const char *text[] = {
6666                 commit->title,
6667                 opt_author ? commit->author : "",
6668                 opt_date ? mkdate(&commit->time) : "",
6669                 NULL
6670         };
6672         return grep_text(view, text) || grep_refs(commit->refs, view->regex);
6675 static void
6676 main_select(struct view *view, struct line *line)
6678         struct commit *commit = line->data;
6680         string_copy_rev(view->ref, commit->id);
6681         string_copy_rev(ref_commit, view->ref);
6684 static struct view_ops main_ops = {
6685         "commit",
6686         main_argv,
6687         NULL,
6688         main_read,
6689         main_draw,
6690         main_request,
6691         main_grep,
6692         main_select,
6693 };
6696 /*
6697  * Unicode / UTF-8 handling
6698  *
6699  * NOTE: Much of the following code for dealing with Unicode is derived from
6700  * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
6701  * src/intl/charset.c from the UTF-8 branch commit elinks-0.11.0-g31f2c28.
6702  */
6704 static inline int
6705 unicode_width(unsigned long c)
6707         if (c >= 0x1100 &&
6708            (c <= 0x115f                         /* Hangul Jamo */
6709             || c == 0x2329
6710             || c == 0x232a
6711             || (c >= 0x2e80  && c <= 0xa4cf && c != 0x303f)
6712                                                 /* CJK ... Yi */
6713             || (c >= 0xac00  && c <= 0xd7a3)    /* Hangul Syllables */
6714             || (c >= 0xf900  && c <= 0xfaff)    /* CJK Compatibility Ideographs */
6715             || (c >= 0xfe30  && c <= 0xfe6f)    /* CJK Compatibility Forms */
6716             || (c >= 0xff00  && c <= 0xff60)    /* Fullwidth Forms */
6717             || (c >= 0xffe0  && c <= 0xffe6)
6718             || (c >= 0x20000 && c <= 0x2fffd)
6719             || (c >= 0x30000 && c <= 0x3fffd)))
6720                 return 2;
6722         if (c == '\t')
6723                 return opt_tab_size;
6725         return 1;
6728 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
6729  * Illegal bytes are set one. */
6730 static const unsigned char utf8_bytes[256] = {
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         1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
6736         1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
6737         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,
6738         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,
6739 };
6741 /* Decode UTF-8 multi-byte representation into a Unicode character. */
6742 static inline unsigned long
6743 utf8_to_unicode(const char *string, size_t length)
6745         unsigned long unicode;
6747         switch (length) {
6748         case 1:
6749                 unicode  =   string[0];
6750                 break;
6751         case 2:
6752                 unicode  =  (string[0] & 0x1f) << 6;
6753                 unicode +=  (string[1] & 0x3f);
6754                 break;
6755         case 3:
6756                 unicode  =  (string[0] & 0x0f) << 12;
6757                 unicode += ((string[1] & 0x3f) << 6);
6758                 unicode +=  (string[2] & 0x3f);
6759                 break;
6760         case 4:
6761                 unicode  =  (string[0] & 0x0f) << 18;
6762                 unicode += ((string[1] & 0x3f) << 12);
6763                 unicode += ((string[2] & 0x3f) << 6);
6764                 unicode +=  (string[3] & 0x3f);
6765                 break;
6766         case 5:
6767                 unicode  =  (string[0] & 0x0f) << 24;
6768                 unicode += ((string[1] & 0x3f) << 18);
6769                 unicode += ((string[2] & 0x3f) << 12);
6770                 unicode += ((string[3] & 0x3f) << 6);
6771                 unicode +=  (string[4] & 0x3f);
6772                 break;
6773         case 6:
6774                 unicode  =  (string[0] & 0x01) << 30;
6775                 unicode += ((string[1] & 0x3f) << 24);
6776                 unicode += ((string[2] & 0x3f) << 18);
6777                 unicode += ((string[3] & 0x3f) << 12);
6778                 unicode += ((string[4] & 0x3f) << 6);
6779                 unicode +=  (string[5] & 0x3f);
6780                 break;
6781         default:
6782                 die("Invalid Unicode length");
6783         }
6785         /* Invalid characters could return the special 0xfffd value but NUL
6786          * should be just as good. */
6787         return unicode > 0xffff ? 0 : unicode;
6790 /* Calculates how much of string can be shown within the given maximum width
6791  * and sets trimmed parameter to non-zero value if all of string could not be
6792  * shown. If the reserve flag is TRUE, it will reserve at least one
6793  * trailing character, which can be useful when drawing a delimiter.
6794  *
6795  * Returns the number of bytes to output from string to satisfy max_width. */
6796 static size_t
6797 utf8_length(const char **start, size_t skip, int *width, size_t max_width, int *trimmed, bool reserve)
6799         const char *string = *start;
6800         const char *end = strchr(string, '\0');
6801         unsigned char last_bytes = 0;
6802         size_t last_ucwidth = 0;
6804         *width = 0;
6805         *trimmed = 0;
6807         while (string < end) {
6808                 int c = *(unsigned char *) string;
6809                 unsigned char bytes = utf8_bytes[c];
6810                 size_t ucwidth;
6811                 unsigned long unicode;
6813                 if (string + bytes > end)
6814                         break;
6816                 /* Change representation to figure out whether
6817                  * it is a single- or double-width character. */
6819                 unicode = utf8_to_unicode(string, bytes);
6820                 /* FIXME: Graceful handling of invalid Unicode character. */
6821                 if (!unicode)
6822                         break;
6824                 ucwidth = unicode_width(unicode);
6825                 if (skip > 0) {
6826                         skip -= ucwidth <= skip ? ucwidth : skip;
6827                         *start += bytes;
6828                 }
6829                 *width  += ucwidth;
6830                 if (*width > max_width) {
6831                         *trimmed = 1;
6832                         *width -= ucwidth;
6833                         if (reserve && *width == max_width) {
6834                                 string -= last_bytes;
6835                                 *width -= last_ucwidth;
6836                         }
6837                         break;
6838                 }
6840                 string  += bytes;
6841                 last_bytes = ucwidth ? bytes : 0;
6842                 last_ucwidth = ucwidth;
6843         }
6845         return string - *start;
6849 /*
6850  * Status management
6851  */
6853 /* Whether or not the curses interface has been initialized. */
6854 static bool cursed = FALSE;
6856 /* Terminal hacks and workarounds. */
6857 static bool use_scroll_redrawwin;
6858 static bool use_scroll_status_wclear;
6860 /* The status window is used for polling keystrokes. */
6861 static WINDOW *status_win;
6863 /* Reading from the prompt? */
6864 static bool input_mode = FALSE;
6866 static bool status_empty = FALSE;
6868 /* Update status and title window. */
6869 static void
6870 report(const char *msg, ...)
6872         struct view *view = display[current_view];
6874         if (input_mode)
6875                 return;
6877         if (!view) {
6878                 char buf[SIZEOF_STR];
6879                 va_list args;
6881                 va_start(args, msg);
6882                 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
6883                         buf[sizeof(buf) - 1] = 0;
6884                         buf[sizeof(buf) - 2] = '.';
6885                         buf[sizeof(buf) - 3] = '.';
6886                         buf[sizeof(buf) - 4] = '.';
6887                 }
6888                 va_end(args);
6889                 die("%s", buf);
6890         }
6892         if (!status_empty || *msg) {
6893                 va_list args;
6895                 va_start(args, msg);
6897                 wmove(status_win, 0, 0);
6898                 if (view->has_scrolled && use_scroll_status_wclear)
6899                         wclear(status_win);
6900                 if (*msg) {
6901                         vwprintw(status_win, msg, args);
6902                         status_empty = FALSE;
6903                 } else {
6904                         status_empty = TRUE;
6905                 }
6906                 wclrtoeol(status_win);
6907                 wnoutrefresh(status_win);
6909                 va_end(args);
6910         }
6912         update_view_title(view);
6915 /* Controls when nodelay should be in effect when polling user input. */
6916 static void
6917 set_nonblocking_input(bool loading)
6919         static unsigned int loading_views;
6921         if ((loading == FALSE && loading_views-- == 1) ||
6922             (loading == TRUE  && loading_views++ == 0))
6923                 nodelay(status_win, loading);
6926 static void
6927 init_display(void)
6929         const char *term;
6930         int x, y;
6932         /* Initialize the curses library */
6933         if (isatty(STDIN_FILENO)) {
6934                 cursed = !!initscr();
6935                 opt_tty = stdin;
6936         } else {
6937                 /* Leave stdin and stdout alone when acting as a pager. */
6938                 opt_tty = fopen("/dev/tty", "r+");
6939                 if (!opt_tty)
6940                         die("Failed to open /dev/tty");
6941                 cursed = !!newterm(NULL, opt_tty, opt_tty);
6942         }
6944         if (!cursed)
6945                 die("Failed to initialize curses");
6947         nonl();         /* Disable conversion and detect newlines from input. */
6948         cbreak();       /* Take input chars one at a time, no wait for \n */
6949         noecho();       /* Don't echo input */
6950         leaveok(stdscr, FALSE);
6952         if (has_colors())
6953                 init_colors();
6955         getmaxyx(stdscr, y, x);
6956         status_win = newwin(1, 0, y - 1, 0);
6957         if (!status_win)
6958                 die("Failed to create status window");
6960         /* Enable keyboard mapping */
6961         keypad(status_win, TRUE);
6962         wbkgdset(status_win, get_line_attr(LINE_STATUS));
6964         TABSIZE = opt_tab_size;
6965         if (opt_line_graphics) {
6966                 line_graphics[LINE_GRAPHIC_VLINE] = ACS_VLINE;
6967         }
6969         term = getenv("XTERM_VERSION") ? NULL : getenv("COLORTERM");
6970         if (term && !strcmp(term, "gnome-terminal")) {
6971                 /* In the gnome-terminal-emulator, the message from
6972                  * scrolling up one line when impossible followed by
6973                  * scrolling down one line causes corruption of the
6974                  * status line. This is fixed by calling wclear. */
6975                 use_scroll_status_wclear = TRUE;
6976                 use_scroll_redrawwin = FALSE;
6978         } else if (term && !strcmp(term, "xrvt-xpm")) {
6979                 /* No problems with full optimizations in xrvt-(unicode)
6980                  * and aterm. */
6981                 use_scroll_status_wclear = use_scroll_redrawwin = FALSE;
6983         } else {
6984                 /* When scrolling in (u)xterm the last line in the
6985                  * scrolling direction will update slowly. */
6986                 use_scroll_redrawwin = TRUE;
6987                 use_scroll_status_wclear = FALSE;
6988         }
6991 static int
6992 get_input(int prompt_position)
6994         struct view *view;
6995         int i, key, cursor_y, cursor_x;
6997         if (prompt_position)
6998                 input_mode = TRUE;
7000         while (TRUE) {
7001                 foreach_view (view, i) {
7002                         update_view(view);
7003                         if (view_is_displayed(view) && view->has_scrolled &&
7004                             use_scroll_redrawwin)
7005                                 redrawwin(view->win);
7006                         view->has_scrolled = FALSE;
7007                 }
7009                 /* Update the cursor position. */
7010                 if (prompt_position) {
7011                         getbegyx(status_win, cursor_y, cursor_x);
7012                         cursor_x = prompt_position;
7013                 } else {
7014                         view = display[current_view];
7015                         getbegyx(view->win, cursor_y, cursor_x);
7016                         cursor_x = view->width - 1;
7017                         cursor_y += view->lineno - view->offset;
7018                 }
7019                 setsyx(cursor_y, cursor_x);
7021                 /* Refresh, accept single keystroke of input */
7022                 doupdate();
7023                 key = wgetch(status_win);
7025                 /* wgetch() with nodelay() enabled returns ERR when
7026                  * there's no input. */
7027                 if (key == ERR) {
7029                 } else if (key == KEY_RESIZE) {
7030                         int height, width;
7032                         getmaxyx(stdscr, height, width);
7034                         wresize(status_win, 1, width);
7035                         mvwin(status_win, height - 1, 0);
7036                         wnoutrefresh(status_win);
7037                         resize_display();
7038                         redraw_display(TRUE);
7040                 } else {
7041                         input_mode = FALSE;
7042                         return key;
7043                 }
7044         }
7047 static char *
7048 prompt_input(const char *prompt, input_handler handler, void *data)
7050         enum input_status status = INPUT_OK;
7051         static char buf[SIZEOF_STR];
7052         size_t pos = 0;
7054         buf[pos] = 0;
7056         while (status == INPUT_OK || status == INPUT_SKIP) {
7057                 int key;
7059                 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
7060                 wclrtoeol(status_win);
7062                 key = get_input(pos + 1);
7063                 switch (key) {
7064                 case KEY_RETURN:
7065                 case KEY_ENTER:
7066                 case '\n':
7067                         status = pos ? INPUT_STOP : INPUT_CANCEL;
7068                         break;
7070                 case KEY_BACKSPACE:
7071                         if (pos > 0)
7072                                 buf[--pos] = 0;
7073                         else
7074                                 status = INPUT_CANCEL;
7075                         break;
7077                 case KEY_ESC:
7078                         status = INPUT_CANCEL;
7079                         break;
7081                 default:
7082                         if (pos >= sizeof(buf)) {
7083                                 report("Input string too long");
7084                                 return NULL;
7085                         }
7087                         status = handler(data, buf, key);
7088                         if (status == INPUT_OK)
7089                                 buf[pos++] = (char) key;
7090                 }
7091         }
7093         /* Clear the status window */
7094         status_empty = FALSE;
7095         report("");
7097         if (status == INPUT_CANCEL)
7098                 return NULL;
7100         buf[pos++] = 0;
7102         return buf;
7105 static enum input_status
7106 prompt_yesno_handler(void *data, char *buf, int c)
7108         if (c == 'y' || c == 'Y')
7109                 return INPUT_STOP;
7110         if (c == 'n' || c == 'N')
7111                 return INPUT_CANCEL;
7112         return INPUT_SKIP;
7115 static bool
7116 prompt_yesno(const char *prompt)
7118         char prompt2[SIZEOF_STR];
7120         if (!string_format(prompt2, "%s [Yy/Nn]", prompt))
7121                 return FALSE;
7123         return !!prompt_input(prompt2, prompt_yesno_handler, NULL);
7126 static enum input_status
7127 read_prompt_handler(void *data, char *buf, int c)
7129         return isprint(c) ? INPUT_OK : INPUT_SKIP;
7132 static char *
7133 read_prompt(const char *prompt)
7135         return prompt_input(prompt, read_prompt_handler, NULL);
7138 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected)
7140         enum input_status status = INPUT_OK;
7141         int size = 0;
7143         while (items[size].text)
7144                 size++;
7146         while (status == INPUT_OK) {
7147                 const struct menu_item *item = &items[*selected];
7148                 int key;
7149                 int i;
7151                 mvwprintw(status_win, 0, 0, "%s (%d of %d) ",
7152                           prompt, *selected + 1, size);
7153                 if (item->hotkey)
7154                         wprintw(status_win, "[%c] ", (char) item->hotkey);
7155                 wprintw(status_win, "%s", item->text);
7156                 wclrtoeol(status_win);
7158                 key = get_input(COLS - 1);
7159                 switch (key) {
7160                 case KEY_RETURN:
7161                 case KEY_ENTER:
7162                 case '\n':
7163                         status = INPUT_STOP;
7164                         break;
7166                 case KEY_LEFT:
7167                 case KEY_UP:
7168                         *selected = *selected - 1;
7169                         if (*selected < 0)
7170                                 *selected = size - 1;
7171                         break;
7173                 case KEY_RIGHT:
7174                 case KEY_DOWN:
7175                         *selected = (*selected + 1) % size;
7176                         break;
7178                 case KEY_ESC:
7179                         status = INPUT_CANCEL;
7180                         break;
7182                 default:
7183                         for (i = 0; items[i].text; i++)
7184                                 if (items[i].hotkey == key) {
7185                                         *selected = i;
7186                                         status = INPUT_STOP;
7187                                         break;
7188                                 }
7189                 }
7190         }
7192         /* Clear the status window */
7193         status_empty = FALSE;
7194         report("");
7196         return status != INPUT_CANCEL;
7199 /*
7200  * Repository properties
7201  */
7203 static struct ref **refs = NULL;
7204 static size_t refs_size = 0;
7206 static struct ref_list **ref_lists = NULL;
7207 static size_t ref_lists_size = 0;
7209 DEFINE_ALLOCATOR(realloc_refs, struct ref *, 256)
7210 DEFINE_ALLOCATOR(realloc_refs_list, struct ref *, 8)
7211 DEFINE_ALLOCATOR(realloc_ref_lists, struct ref_list *, 8)
7213 static int
7214 compare_refs(const void *ref1_, const void *ref2_)
7216         const struct ref *ref1 = *(const struct ref **)ref1_;
7217         const struct ref *ref2 = *(const struct ref **)ref2_;
7219         if (ref1->tag != ref2->tag)
7220                 return ref2->tag - ref1->tag;
7221         if (ref1->ltag != ref2->ltag)
7222                 return ref2->ltag - ref2->ltag;
7223         if (ref1->head != ref2->head)
7224                 return ref2->head - ref1->head;
7225         if (ref1->tracked != ref2->tracked)
7226                 return ref2->tracked - ref1->tracked;
7227         if (ref1->remote != ref2->remote)
7228                 return ref2->remote - ref1->remote;
7229         return strcmp(ref1->name, ref2->name);
7232 static void
7233 foreach_ref(bool (*visitor)(void *data, const struct ref *ref), void *data)
7235         size_t i;
7237         for (i = 0; i < refs_size; i++)
7238                 if (!visitor(data, refs[i]))
7239                         break;
7242 static struct ref_list *
7243 get_ref_list(const char *id)
7245         struct ref_list *list;
7246         size_t i;
7248         for (i = 0; i < ref_lists_size; i++)
7249                 if (!strcmp(id, ref_lists[i]->id))
7250                         return ref_lists[i];
7252         if (!realloc_ref_lists(&ref_lists, ref_lists_size, 1))
7253                 return NULL;
7254         list = calloc(1, sizeof(*list));
7255         if (!list)
7256                 return NULL;
7258         for (i = 0; i < refs_size; i++) {
7259                 if (!strcmp(id, refs[i]->id) &&
7260                     realloc_refs_list(&list->refs, list->size, 1))
7261                         list->refs[list->size++] = refs[i];
7262         }
7264         if (!list->refs) {
7265                 free(list);
7266                 return NULL;
7267         }
7269         qsort(list->refs, list->size, sizeof(*list->refs), compare_refs);
7270         ref_lists[ref_lists_size++] = list;
7271         return list;
7274 static int
7275 read_ref(char *id, size_t idlen, char *name, size_t namelen)
7277         struct ref *ref = NULL;
7278         bool tag = FALSE;
7279         bool ltag = FALSE;
7280         bool remote = FALSE;
7281         bool tracked = FALSE;
7282         bool head = FALSE;
7283         int from = 0, to = refs_size - 1;
7285         if (!prefixcmp(name, "refs/tags/")) {
7286                 if (!suffixcmp(name, namelen, "^{}")) {
7287                         namelen -= 3;
7288                         name[namelen] = 0;
7289                 } else {
7290                         ltag = TRUE;
7291                 }
7293                 tag = TRUE;
7294                 namelen -= STRING_SIZE("refs/tags/");
7295                 name    += STRING_SIZE("refs/tags/");
7297         } else if (!prefixcmp(name, "refs/remotes/")) {
7298                 remote = TRUE;
7299                 namelen -= STRING_SIZE("refs/remotes/");
7300                 name    += STRING_SIZE("refs/remotes/");
7301                 tracked  = !strcmp(opt_remote, name);
7303         } else if (!prefixcmp(name, "refs/heads/")) {
7304                 namelen -= STRING_SIZE("refs/heads/");
7305                 name    += STRING_SIZE("refs/heads/");
7306                 head     = !strncmp(opt_head, name, namelen);
7308         } else if (!strcmp(name, "HEAD")) {
7309                 string_ncopy(opt_head_rev, id, idlen);
7310                 return OK;
7311         }
7313         /* If we are reloading or it's an annotated tag, replace the
7314          * previous SHA1 with the resolved commit id; relies on the fact
7315          * git-ls-remote lists the commit id of an annotated tag right
7316          * before the commit id it points to. */
7317         while (from <= to) {
7318                 size_t pos = (to + from) / 2;
7319                 int cmp = strcmp(name, refs[pos]->name);
7321                 if (!cmp) {
7322                         ref = refs[pos];
7323                         break;
7324                 }
7326                 if (cmp < 0)
7327                         to = pos - 1;
7328                 else
7329                         from = pos + 1;
7330         }
7332         if (!ref) {
7333                 if (!realloc_refs(&refs, refs_size, 1))
7334                         return ERR;
7335                 ref = calloc(1, sizeof(*ref) + namelen);
7336                 if (!ref)
7337                         return ERR;
7338                 memmove(refs + from + 1, refs + from,
7339                         (refs_size - from) * sizeof(*refs));
7340                 refs[from] = ref;
7341                 strncpy(ref->name, name, namelen);
7342                 refs_size++;
7343         }
7345         ref->head = head;
7346         ref->tag = tag;
7347         ref->ltag = ltag;
7348         ref->remote = remote;
7349         ref->tracked = tracked;
7350         string_copy_rev(ref->id, id);
7352         return OK;
7355 static int
7356 load_refs(void)
7358         const char *head_argv[] = {
7359                 "git", "symbolic-ref", "HEAD", NULL
7360         };
7361         static const char *ls_remote_argv[SIZEOF_ARG] = {
7362                 "git", "ls-remote", opt_git_dir, NULL
7363         };
7364         static bool init = FALSE;
7365         size_t i;
7367         if (!init) {
7368                 argv_from_env(ls_remote_argv, "TIG_LS_REMOTE");
7369                 init = TRUE;
7370         }
7372         if (!*opt_git_dir)
7373                 return OK;
7375         if (run_io_buf(head_argv, opt_head, sizeof(opt_head)) &&
7376             !prefixcmp(opt_head, "refs/heads/")) {
7377                 char *offset = opt_head + STRING_SIZE("refs/heads/");
7379                 memmove(opt_head, offset, strlen(offset) + 1);
7380         }
7382         for (i = 0; i < refs_size; i++)
7383                 refs[i]->id[0] = 0;
7385         if (run_io_load(ls_remote_argv, "\t", read_ref) == ERR)
7386                 return ERR;
7388         /* Update the ref lists to reflect changes. */
7389         for (i = 0; i < ref_lists_size; i++) {
7390                 struct ref_list *list = ref_lists[i];
7391                 size_t old, new;
7393                 for (old = new = 0; old < list->size; old++)
7394                         if (!strcmp(list->id, list->refs[old]->id))
7395                                 list->refs[new++] = list->refs[old];
7396                 list->size = new;
7397         }
7399         return OK;
7402 static void
7403 set_remote_branch(const char *name, const char *value, size_t valuelen)
7405         if (!strcmp(name, ".remote")) {
7406                 string_ncopy(opt_remote, value, valuelen);
7408         } else if (*opt_remote && !strcmp(name, ".merge")) {
7409                 size_t from = strlen(opt_remote);
7411                 if (!prefixcmp(value, "refs/heads/"))
7412                         value += STRING_SIZE("refs/heads/");
7414                 if (!string_format_from(opt_remote, &from, "/%s", value))
7415                         opt_remote[0] = 0;
7416         }
7419 static void
7420 set_repo_config_option(char *name, char *value, int (*cmd)(int, const char **))
7422         const char *argv[SIZEOF_ARG] = { name, "=" };
7423         int argc = 1 + (cmd == option_set_command);
7424         int error = ERR;
7426         if (!argv_from_string(argv, &argc, value))
7427                 config_msg = "Too many option arguments";
7428         else
7429                 error = cmd(argc, argv);
7431         if (error == ERR)
7432                 warn("Option 'tig.%s': %s", name, config_msg);
7435 static bool
7436 set_environment_variable(const char *name, const char *value)
7438         size_t len = strlen(name) + 1 + strlen(value) + 1;
7439         char *env = malloc(len);
7441         if (env &&
7442             string_nformat(env, len, NULL, "%s=%s", name, value) &&
7443             putenv(env) == 0)
7444                 return TRUE;
7445         free(env);
7446         return FALSE;
7449 static void
7450 set_work_tree(const char *value)
7452         char cwd[SIZEOF_STR];
7454         if (!getcwd(cwd, sizeof(cwd)))
7455                 die("Failed to get cwd path: %s", strerror(errno));
7456         if (chdir(opt_git_dir) < 0)
7457                 die("Failed to chdir(%s): %s", strerror(errno));
7458         if (!getcwd(opt_git_dir, sizeof(opt_git_dir)))
7459                 die("Failed to get git path: %s", strerror(errno));
7460         if (chdir(cwd) < 0)
7461                 die("Failed to chdir(%s): %s", cwd, strerror(errno));
7462         if (chdir(value) < 0)
7463                 die("Failed to chdir(%s): %s", value, strerror(errno));
7464         if (!getcwd(cwd, sizeof(cwd)))
7465                 die("Failed to get cwd path: %s", strerror(errno));
7466         if (!set_environment_variable("GIT_WORK_TREE", cwd))
7467                 die("Failed to set GIT_WORK_TREE to '%s'", cwd);
7468         if (!set_environment_variable("GIT_DIR", opt_git_dir))
7469                 die("Failed to set GIT_DIR to '%s'", opt_git_dir);
7470         opt_is_inside_work_tree = TRUE;
7473 static int
7474 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
7476         if (!strcmp(name, "i18n.commitencoding"))
7477                 string_ncopy(opt_encoding, value, valuelen);
7479         else if (!strcmp(name, "core.editor"))
7480                 string_ncopy(opt_editor, value, valuelen);
7482         else if (!strcmp(name, "core.worktree"))
7483                 set_work_tree(value);
7485         else if (!prefixcmp(name, "tig.color."))
7486                 set_repo_config_option(name + 10, value, option_color_command);
7488         else if (!prefixcmp(name, "tig.bind."))
7489                 set_repo_config_option(name + 9, value, option_bind_command);
7491         else if (!prefixcmp(name, "tig."))
7492                 set_repo_config_option(name + 4, value, option_set_command);
7494         else if (*opt_head && !prefixcmp(name, "branch.") &&
7495                  !strncmp(name + 7, opt_head, strlen(opt_head)))
7496                 set_remote_branch(name + 7 + strlen(opt_head), value, valuelen);
7498         return OK;
7501 static int
7502 load_git_config(void)
7504         const char *config_list_argv[] = { "git", "config", "--list", NULL };
7506         return run_io_load(config_list_argv, "=", read_repo_config_option);
7509 static int
7510 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
7512         if (!opt_git_dir[0]) {
7513                 string_ncopy(opt_git_dir, name, namelen);
7515         } else if (opt_is_inside_work_tree == -1) {
7516                 /* This can be 3 different values depending on the
7517                  * version of git being used. If git-rev-parse does not
7518                  * understand --is-inside-work-tree it will simply echo
7519                  * the option else either "true" or "false" is printed.
7520                  * Default to true for the unknown case. */
7521                 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
7523         } else if (*name == '.') {
7524                 string_ncopy(opt_cdup, name, namelen);
7526         } else {
7527                 string_ncopy(opt_prefix, name, namelen);
7528         }
7530         return OK;
7533 static int
7534 load_repo_info(void)
7536         const char *rev_parse_argv[] = {
7537                 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
7538                         "--show-cdup", "--show-prefix", NULL
7539         };
7541         return run_io_load(rev_parse_argv, "=", read_repo_info);
7545 /*
7546  * Main
7547  */
7549 static const char usage[] =
7550 "tig " TIG_VERSION " (" __DATE__ ")\n"
7551 "\n"
7552 "Usage: tig        [options] [revs] [--] [paths]\n"
7553 "   or: tig show   [options] [revs] [--] [paths]\n"
7554 "   or: tig blame  [rev] path\n"
7555 "   or: tig status\n"
7556 "   or: tig <      [git command output]\n"
7557 "\n"
7558 "Options:\n"
7559 "  -v, --version   Show version and exit\n"
7560 "  -h, --help      Show help message and exit";
7562 static void __NORETURN
7563 quit(int sig)
7565         /* XXX: Restore tty modes and let the OS cleanup the rest! */
7566         if (cursed)
7567                 endwin();
7568         exit(0);
7571 static void __NORETURN
7572 die(const char *err, ...)
7574         va_list args;
7576         endwin();
7578         va_start(args, err);
7579         fputs("tig: ", stderr);
7580         vfprintf(stderr, err, args);
7581         fputs("\n", stderr);
7582         va_end(args);
7584         exit(1);
7587 static void
7588 warn(const char *msg, ...)
7590         va_list args;
7592         va_start(args, msg);
7593         fputs("tig warning: ", stderr);
7594         vfprintf(stderr, msg, args);
7595         fputs("\n", stderr);
7596         va_end(args);
7599 static enum request
7600 parse_options(int argc, const char *argv[])
7602         enum request request = REQ_VIEW_MAIN;
7603         const char *subcommand;
7604         bool seen_dashdash = FALSE;
7605         /* XXX: This is vulnerable to the user overriding options
7606          * required for the main view parser. */
7607         const char *custom_argv[SIZEOF_ARG] = {
7608                 "git", "log", "--no-color", "--pretty=raw", "--parents",
7609                         "--topo-order", NULL
7610         };
7611         int i, j = 6;
7613         if (!isatty(STDIN_FILENO)) {
7614                 io_open(&VIEW(REQ_VIEW_PAGER)->io, "");
7615                 return REQ_VIEW_PAGER;
7616         }
7618         if (argc <= 1)
7619                 return REQ_NONE;
7621         subcommand = argv[1];
7622         if (!strcmp(subcommand, "status")) {
7623                 if (argc > 2)
7624                         warn("ignoring arguments after `%s'", subcommand);
7625                 return REQ_VIEW_STATUS;
7627         } else if (!strcmp(subcommand, "blame")) {
7628                 if (argc <= 2 || argc > 4)
7629                         die("invalid number of options to blame\n\n%s", usage);
7631                 i = 2;
7632                 if (argc == 4) {
7633                         string_ncopy(opt_ref, argv[i], strlen(argv[i]));
7634                         i++;
7635                 }
7637                 string_ncopy(opt_file, argv[i], strlen(argv[i]));
7638                 return REQ_VIEW_BLAME;
7640         } else if (!strcmp(subcommand, "show")) {
7641                 request = REQ_VIEW_DIFF;
7643         } else {
7644                 subcommand = NULL;
7645         }
7647         if (subcommand) {
7648                 custom_argv[1] = subcommand;
7649                 j = 2;
7650         }
7652         for (i = 1 + !!subcommand; i < argc; i++) {
7653                 const char *opt = argv[i];
7655                 if (seen_dashdash || !strcmp(opt, "--")) {
7656                         seen_dashdash = TRUE;
7658                 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
7659                         printf("tig version %s\n", TIG_VERSION);
7660                         quit(0);
7662                 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
7663                         printf("%s\n", usage);
7664                         quit(0);
7665                 }
7667                 custom_argv[j++] = opt;
7668                 if (j >= ARRAY_SIZE(custom_argv))
7669                         die("command too long");
7670         }
7672         if (!prepare_update(VIEW(request), custom_argv, NULL, FORMAT_NONE))
7673                 die("Failed to format arguments");
7675         return request;
7678 int
7679 main(int argc, const char *argv[])
7681         enum request request = parse_options(argc, argv);
7682         struct view *view;
7683         size_t i;
7685         signal(SIGINT, quit);
7686         signal(SIGPIPE, SIG_IGN);
7688         if (setlocale(LC_ALL, "")) {
7689                 char *codeset = nl_langinfo(CODESET);
7691                 string_ncopy(opt_codeset, codeset, strlen(codeset));
7692         }
7694         if (load_repo_info() == ERR)
7695                 die("Failed to load repo info.");
7697         if (load_options() == ERR)
7698                 die("Failed to load user config.");
7700         if (load_git_config() == ERR)
7701                 die("Failed to load repo config.");
7703         /* Require a git repository unless when running in pager mode. */
7704         if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
7705                 die("Not a git repository");
7707         if (*opt_encoding && strcmp(opt_codeset, "UTF-8")) {
7708                 opt_iconv_in = iconv_open("UTF-8", opt_encoding);
7709                 if (opt_iconv_in == ICONV_NONE)
7710                         die("Failed to initialize character set conversion");
7711         }
7713         if (*opt_codeset && strcmp(opt_codeset, "UTF-8")) {
7714                 opt_iconv_out = iconv_open(opt_codeset, "UTF-8");
7715                 if (opt_iconv_out == ICONV_NONE)
7716                         die("Failed to initialize character set conversion");
7717         }
7719         if (load_refs() == ERR)
7720                 die("Failed to load refs.");
7722         foreach_view (view, i)
7723                 argv_from_env(view->ops->argv, view->cmd_env);
7725         init_display();
7727         if (request != REQ_NONE)
7728                 open_view(NULL, request, OPEN_PREPARED);
7729         request = request == REQ_NONE ? REQ_VIEW_MAIN : REQ_NONE;
7731         while (view_driver(display[current_view], request)) {
7732                 int key = get_input(0);
7734                 view = display[current_view];
7735                 request = get_keybinding(view->keymap, key);
7737                 /* Some low-level request handling. This keeps access to
7738                  * status_win restricted. */
7739                 switch (request) {
7740                 case REQ_PROMPT:
7741                 {
7742                         char *cmd = read_prompt(":");
7744                         if (cmd && isdigit(*cmd)) {
7745                                 int lineno = view->lineno + 1;
7747                                 if (parse_int(&lineno, cmd, 1, view->lines + 1) == OK) {
7748                                         select_view_line(view, lineno - 1);
7749                                         report("");
7750                                 } else {
7751                                         report("Unable to parse '%s' as a line number", cmd);
7752                                 }
7754                         } else if (cmd) {
7755                                 struct view *next = VIEW(REQ_VIEW_PAGER);
7756                                 const char *argv[SIZEOF_ARG] = { "git" };
7757                                 int argc = 1;
7759                                 /* When running random commands, initially show the
7760                                  * command in the title. However, it maybe later be
7761                                  * overwritten if a commit line is selected. */
7762                                 string_ncopy(next->ref, cmd, strlen(cmd));
7764                                 if (!argv_from_string(argv, &argc, cmd)) {
7765                                         report("Too many arguments");
7766                                 } else if (!prepare_update(next, argv, NULL, FORMAT_DASH)) {
7767                                         report("Failed to format command");
7768                                 } else {
7769                                         open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
7770                                 }
7771                         }
7773                         request = REQ_NONE;
7774                         break;
7775                 }
7776                 case REQ_SEARCH:
7777                 case REQ_SEARCH_BACK:
7778                 {
7779                         const char *prompt = request == REQ_SEARCH ? "/" : "?";
7780                         char *search = read_prompt(prompt);
7782                         if (search)
7783                                 string_ncopy(opt_search, search, strlen(search));
7784                         else if (*opt_search)
7785                                 request = request == REQ_SEARCH ?
7786                                         REQ_FIND_NEXT :
7787                                         REQ_FIND_PREV;
7788                         else
7789                                 request = REQ_NONE;
7790                         break;
7791                 }
7792                 default:
7793                         break;
7794                 }
7795         }
7797         quit(0);
7799         return 0;