Code

foreach_ref: make ref argument const
[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 bool opt_utf8                    = TRUE;
998 static char opt_codeset[20]             = "UTF-8";
999 static iconv_t opt_iconv                = ICONV_NONE;
1000 static char opt_search[SIZEOF_STR]      = "";
1001 static char opt_cdup[SIZEOF_STR]        = "";
1002 static char opt_prefix[SIZEOF_STR]      = "";
1003 static char opt_git_dir[SIZEOF_STR]     = "";
1004 static signed char opt_is_inside_work_tree      = -1; /* set to TRUE or FALSE */
1005 static char opt_editor[SIZEOF_STR]      = "";
1006 static FILE *opt_tty                    = NULL;
1008 #define is_initial_commit()     (!*opt_head_rev)
1009 #define is_head_commit(rev)     (!strcmp((rev), "HEAD") || !strcmp(opt_head_rev, (rev)))
1010 #define mkdate(time)            string_date(time, opt_date)
1013 /*
1014  * Line-oriented content detection.
1015  */
1017 #define LINE_INFO \
1018 LINE(DIFF_HEADER,  "diff --git ",       COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1019 LINE(DIFF_CHUNK,   "@@",                COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1020 LINE(DIFF_ADD,     "+",                 COLOR_GREEN,    COLOR_DEFAULT,  0), \
1021 LINE(DIFF_DEL,     "-",                 COLOR_RED,      COLOR_DEFAULT,  0), \
1022 LINE(DIFF_INDEX,        "index ",         COLOR_BLUE,   COLOR_DEFAULT,  0), \
1023 LINE(DIFF_OLDMODE,      "old file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
1024 LINE(DIFF_NEWMODE,      "new file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
1025 LINE(DIFF_COPY_FROM,    "copy from",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
1026 LINE(DIFF_COPY_TO,      "copy to",        COLOR_YELLOW, COLOR_DEFAULT,  0), \
1027 LINE(DIFF_RENAME_FROM,  "rename from",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
1028 LINE(DIFF_RENAME_TO,    "rename to",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
1029 LINE(DIFF_SIMILARITY,   "similarity ",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
1030 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
1031 LINE(DIFF_TREE,         "diff-tree ",     COLOR_BLUE,   COLOR_DEFAULT,  0), \
1032 LINE(PP_AUTHOR,    "Author: ",          COLOR_CYAN,     COLOR_DEFAULT,  0), \
1033 LINE(PP_COMMIT,    "Commit: ",          COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1034 LINE(PP_MERGE,     "Merge: ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
1035 LINE(PP_DATE,      "Date:   ",          COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1036 LINE(PP_ADATE,     "AuthorDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1037 LINE(PP_CDATE,     "CommitDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1038 LINE(PP_REFS,      "Refs: ",            COLOR_RED,      COLOR_DEFAULT,  0), \
1039 LINE(COMMIT,       "commit ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
1040 LINE(PARENT,       "parent ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
1041 LINE(TREE,         "tree ",             COLOR_BLUE,     COLOR_DEFAULT,  0), \
1042 LINE(AUTHOR,       "author ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
1043 LINE(COMMITTER,    "committer ",        COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1044 LINE(SIGNOFF,      "    Signed-off-by", COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1045 LINE(ACKED,        "    Acked-by",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1046 LINE(DEFAULT,      "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
1047 LINE(CURSOR,       "",                  COLOR_WHITE,    COLOR_GREEN,    A_BOLD), \
1048 LINE(STATUS,       "",                  COLOR_GREEN,    COLOR_DEFAULT,  0), \
1049 LINE(DELIMITER,    "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1050 LINE(DATE,         "",                  COLOR_BLUE,     COLOR_DEFAULT,  0), \
1051 LINE(MODE,         "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1052 LINE(LINE_NUMBER,  "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1053 LINE(TITLE_BLUR,   "",                  COLOR_WHITE,    COLOR_BLUE,     0), \
1054 LINE(TITLE_FOCUS,  "",                  COLOR_WHITE,    COLOR_BLUE,     A_BOLD), \
1055 LINE(MAIN_COMMIT,  "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
1056 LINE(MAIN_TAG,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  A_BOLD), \
1057 LINE(MAIN_LOCAL_TAG,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1058 LINE(MAIN_REMOTE,  "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1059 LINE(MAIN_TRACKED, "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_BOLD), \
1060 LINE(MAIN_REF,     "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1061 LINE(MAIN_HEAD,    "",                  COLOR_CYAN,     COLOR_DEFAULT,  A_BOLD), \
1062 LINE(MAIN_REVGRAPH,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1063 LINE(TREE_HEAD,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_BOLD), \
1064 LINE(TREE_DIR,     "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_NORMAL), \
1065 LINE(TREE_FILE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
1066 LINE(STAT_HEAD,    "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1067 LINE(STAT_SECTION, "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1068 LINE(STAT_NONE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
1069 LINE(STAT_STAGED,  "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1070 LINE(STAT_UNSTAGED,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1071 LINE(STAT_UNTRACKED,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1072 LINE(HELP_KEYMAP,  "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1073 LINE(HELP_GROUP,   "",                  COLOR_BLUE,     COLOR_DEFAULT,  0), \
1074 LINE(BLAME_ID,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0)
1076 enum line_type {
1077 #define LINE(type, line, fg, bg, attr) \
1078         LINE_##type
1079         LINE_INFO,
1080         LINE_NONE
1081 #undef  LINE
1082 };
1084 struct line_info {
1085         const char *name;       /* Option name. */
1086         int namelen;            /* Size of option name. */
1087         const char *line;       /* The start of line to match. */
1088         int linelen;            /* Size of string to match. */
1089         int fg, bg, attr;       /* Color and text attributes for the lines. */
1090 };
1092 static struct line_info line_info[] = {
1093 #define LINE(type, line, fg, bg, attr) \
1094         { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
1095         LINE_INFO
1096 #undef  LINE
1097 };
1099 static enum line_type
1100 get_line_type(const char *line)
1102         int linelen = strlen(line);
1103         enum line_type type;
1105         for (type = 0; type < ARRAY_SIZE(line_info); type++)
1106                 /* Case insensitive search matches Signed-off-by lines better. */
1107                 if (linelen >= line_info[type].linelen &&
1108                     !strncasecmp(line_info[type].line, line, line_info[type].linelen))
1109                         return type;
1111         return LINE_DEFAULT;
1114 static inline int
1115 get_line_attr(enum line_type type)
1117         assert(type < ARRAY_SIZE(line_info));
1118         return COLOR_PAIR(type) | line_info[type].attr;
1121 static struct line_info *
1122 get_line_info(const char *name)
1124         size_t namelen = strlen(name);
1125         enum line_type type;
1127         for (type = 0; type < ARRAY_SIZE(line_info); type++)
1128                 if (namelen == line_info[type].namelen &&
1129                     !string_enum_compare(line_info[type].name, name, namelen))
1130                         return &line_info[type];
1132         return NULL;
1135 static void
1136 init_colors(void)
1138         int default_bg = line_info[LINE_DEFAULT].bg;
1139         int default_fg = line_info[LINE_DEFAULT].fg;
1140         enum line_type type;
1142         start_color();
1144         if (assume_default_colors(default_fg, default_bg) == ERR) {
1145                 default_bg = COLOR_BLACK;
1146                 default_fg = COLOR_WHITE;
1147         }
1149         for (type = 0; type < ARRAY_SIZE(line_info); type++) {
1150                 struct line_info *info = &line_info[type];
1151                 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
1152                 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
1154                 init_pair(type, fg, bg);
1155         }
1158 struct line {
1159         enum line_type type;
1161         /* State flags */
1162         unsigned int selected:1;
1163         unsigned int dirty:1;
1164         unsigned int cleareol:1;
1165         unsigned int other:16;
1167         void *data;             /* User data */
1168 };
1171 /*
1172  * Keys
1173  */
1175 struct keybinding {
1176         int alias;
1177         enum request request;
1178 };
1180 static const struct keybinding default_keybindings[] = {
1181         /* View switching */
1182         { 'm',          REQ_VIEW_MAIN },
1183         { 'd',          REQ_VIEW_DIFF },
1184         { 'l',          REQ_VIEW_LOG },
1185         { 't',          REQ_VIEW_TREE },
1186         { 'f',          REQ_VIEW_BLOB },
1187         { 'B',          REQ_VIEW_BLAME },
1188         { 'H',          REQ_VIEW_BRANCH },
1189         { 'p',          REQ_VIEW_PAGER },
1190         { 'h',          REQ_VIEW_HELP },
1191         { 'S',          REQ_VIEW_STATUS },
1192         { 'c',          REQ_VIEW_STAGE },
1194         /* View manipulation */
1195         { 'q',          REQ_VIEW_CLOSE },
1196         { KEY_TAB,      REQ_VIEW_NEXT },
1197         { KEY_RETURN,   REQ_ENTER },
1198         { KEY_UP,       REQ_PREVIOUS },
1199         { KEY_DOWN,     REQ_NEXT },
1200         { 'R',          REQ_REFRESH },
1201         { KEY_F(5),     REQ_REFRESH },
1202         { 'O',          REQ_MAXIMIZE },
1204         /* Cursor navigation */
1205         { 'k',          REQ_MOVE_UP },
1206         { 'j',          REQ_MOVE_DOWN },
1207         { KEY_HOME,     REQ_MOVE_FIRST_LINE },
1208         { KEY_END,      REQ_MOVE_LAST_LINE },
1209         { KEY_NPAGE,    REQ_MOVE_PAGE_DOWN },
1210         { ' ',          REQ_MOVE_PAGE_DOWN },
1211         { KEY_PPAGE,    REQ_MOVE_PAGE_UP },
1212         { 'b',          REQ_MOVE_PAGE_UP },
1213         { '-',          REQ_MOVE_PAGE_UP },
1215         /* Scrolling */
1216         { KEY_LEFT,     REQ_SCROLL_LEFT },
1217         { KEY_RIGHT,    REQ_SCROLL_RIGHT },
1218         { KEY_IC,       REQ_SCROLL_LINE_UP },
1219         { KEY_DC,       REQ_SCROLL_LINE_DOWN },
1220         { 'w',          REQ_SCROLL_PAGE_UP },
1221         { 's',          REQ_SCROLL_PAGE_DOWN },
1223         /* Searching */
1224         { '/',          REQ_SEARCH },
1225         { '?',          REQ_SEARCH_BACK },
1226         { 'n',          REQ_FIND_NEXT },
1227         { 'N',          REQ_FIND_PREV },
1229         /* Misc */
1230         { 'Q',          REQ_QUIT },
1231         { 'z',          REQ_STOP_LOADING },
1232         { 'v',          REQ_SHOW_VERSION },
1233         { 'r',          REQ_SCREEN_REDRAW },
1234         { 'o',          REQ_OPTIONS },
1235         { '.',          REQ_TOGGLE_LINENO },
1236         { 'D',          REQ_TOGGLE_DATE },
1237         { 'A',          REQ_TOGGLE_AUTHOR },
1238         { 'g',          REQ_TOGGLE_REV_GRAPH },
1239         { 'F',          REQ_TOGGLE_REFS },
1240         { 'I',          REQ_TOGGLE_SORT_ORDER },
1241         { 'i',          REQ_TOGGLE_SORT_FIELD },
1242         { ':',          REQ_PROMPT },
1243         { 'u',          REQ_STATUS_UPDATE },
1244         { '!',          REQ_STATUS_REVERT },
1245         { 'M',          REQ_STATUS_MERGE },
1246         { '@',          REQ_STAGE_NEXT },
1247         { ',',          REQ_PARENT },
1248         { 'e',          REQ_EDIT },
1249 };
1251 #define KEYMAP_INFO \
1252         KEYMAP_(GENERIC), \
1253         KEYMAP_(MAIN), \
1254         KEYMAP_(DIFF), \
1255         KEYMAP_(LOG), \
1256         KEYMAP_(TREE), \
1257         KEYMAP_(BLOB), \
1258         KEYMAP_(BLAME), \
1259         KEYMAP_(BRANCH), \
1260         KEYMAP_(PAGER), \
1261         KEYMAP_(HELP), \
1262         KEYMAP_(STATUS), \
1263         KEYMAP_(STAGE)
1265 enum keymap {
1266 #define KEYMAP_(name) KEYMAP_##name
1267         KEYMAP_INFO
1268 #undef  KEYMAP_
1269 };
1271 static const struct enum_map keymap_table[] = {
1272 #define KEYMAP_(name) ENUM_MAP(#name, KEYMAP_##name)
1273         KEYMAP_INFO
1274 #undef  KEYMAP_
1275 };
1277 #define set_keymap(map, name) map_enum(map, keymap_table, name)
1279 struct keybinding_table {
1280         struct keybinding *data;
1281         size_t size;
1282 };
1284 static struct keybinding_table keybindings[ARRAY_SIZE(keymap_table)];
1286 static void
1287 add_keybinding(enum keymap keymap, enum request request, int key)
1289         struct keybinding_table *table = &keybindings[keymap];
1291         table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data));
1292         if (!table->data)
1293                 die("Failed to allocate keybinding");
1294         table->data[table->size].alias = key;
1295         table->data[table->size++].request = request;
1298 /* Looks for a key binding first in the given map, then in the generic map, and
1299  * lastly in the default keybindings. */
1300 static enum request
1301 get_keybinding(enum keymap keymap, int key)
1303         size_t i;
1305         for (i = 0; i < keybindings[keymap].size; i++)
1306                 if (keybindings[keymap].data[i].alias == key)
1307                         return keybindings[keymap].data[i].request;
1309         for (i = 0; i < keybindings[KEYMAP_GENERIC].size; i++)
1310                 if (keybindings[KEYMAP_GENERIC].data[i].alias == key)
1311                         return keybindings[KEYMAP_GENERIC].data[i].request;
1313         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1314                 if (default_keybindings[i].alias == key)
1315                         return default_keybindings[i].request;
1317         return (enum request) key;
1321 struct key {
1322         const char *name;
1323         int value;
1324 };
1326 static const struct key key_table[] = {
1327         { "Enter",      KEY_RETURN },
1328         { "Space",      ' ' },
1329         { "Backspace",  KEY_BACKSPACE },
1330         { "Tab",        KEY_TAB },
1331         { "Escape",     KEY_ESC },
1332         { "Left",       KEY_LEFT },
1333         { "Right",      KEY_RIGHT },
1334         { "Up",         KEY_UP },
1335         { "Down",       KEY_DOWN },
1336         { "Insert",     KEY_IC },
1337         { "Delete",     KEY_DC },
1338         { "Hash",       '#' },
1339         { "Home",       KEY_HOME },
1340         { "End",        KEY_END },
1341         { "PageUp",     KEY_PPAGE },
1342         { "PageDown",   KEY_NPAGE },
1343         { "F1",         KEY_F(1) },
1344         { "F2",         KEY_F(2) },
1345         { "F3",         KEY_F(3) },
1346         { "F4",         KEY_F(4) },
1347         { "F5",         KEY_F(5) },
1348         { "F6",         KEY_F(6) },
1349         { "F7",         KEY_F(7) },
1350         { "F8",         KEY_F(8) },
1351         { "F9",         KEY_F(9) },
1352         { "F10",        KEY_F(10) },
1353         { "F11",        KEY_F(11) },
1354         { "F12",        KEY_F(12) },
1355 };
1357 static int
1358 get_key_value(const char *name)
1360         int i;
1362         for (i = 0; i < ARRAY_SIZE(key_table); i++)
1363                 if (!strcasecmp(key_table[i].name, name))
1364                         return key_table[i].value;
1366         if (strlen(name) == 1 && isprint(*name))
1367                 return (int) *name;
1369         return ERR;
1372 static const char *
1373 get_key_name(int key_value)
1375         static char key_char[] = "'X'";
1376         const char *seq = NULL;
1377         int key;
1379         for (key = 0; key < ARRAY_SIZE(key_table); key++)
1380                 if (key_table[key].value == key_value)
1381                         seq = key_table[key].name;
1383         if (seq == NULL &&
1384             key_value < 127 &&
1385             isprint(key_value)) {
1386                 key_char[1] = (char) key_value;
1387                 seq = key_char;
1388         }
1390         return seq ? seq : "(no key)";
1393 static bool
1394 append_key(char *buf, size_t *pos, const struct keybinding *keybinding)
1396         const char *sep = *pos > 0 ? ", " : "";
1397         const char *keyname = get_key_name(keybinding->alias);
1399         return string_nformat(buf, BUFSIZ, pos, "%s%s", sep, keyname);
1402 static bool
1403 append_keymap_request_keys(char *buf, size_t *pos, enum request request,
1404                            enum keymap keymap, bool all)
1406         int i;
1408         for (i = 0; i < keybindings[keymap].size; i++) {
1409                 if (keybindings[keymap].data[i].request == request) {
1410                         if (!append_key(buf, pos, &keybindings[keymap].data[i]))
1411                                 return FALSE;
1412                         if (!all)
1413                                 break;
1414                 }
1415         }
1417         return TRUE;
1420 #define get_key(keymap, request) get_keys(keymap, request, FALSE)
1422 static const char *
1423 get_keys(enum keymap keymap, enum request request, bool all)
1425         static char buf[BUFSIZ];
1426         size_t pos = 0;
1427         int i;
1429         buf[pos] = 0;
1431         if (!append_keymap_request_keys(buf, &pos, request, keymap, all))
1432                 return "Too many keybindings!";
1433         if (pos > 0 && !all)
1434                 return buf;
1436         if (keymap != KEYMAP_GENERIC) {
1437                 /* Only the generic keymap includes the default keybindings when
1438                  * listing all keys. */
1439                 if (all)
1440                         return buf;
1442                 if (!append_keymap_request_keys(buf, &pos, request, KEYMAP_GENERIC, all))
1443                         return "Too many keybindings!";
1444                 if (pos)
1445                         return buf;
1446         }
1448         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1449                 if (default_keybindings[i].request == request) {
1450                         if (!append_key(buf, &pos, &default_keybindings[i]))
1451                                 return "Too many keybindings!";
1452                         if (!all)
1453                                 return buf;
1454                 }
1455         }
1457         return buf;
1460 struct run_request {
1461         enum keymap keymap;
1462         int key;
1463         const char *argv[SIZEOF_ARG];
1464 };
1466 static struct run_request *run_request;
1467 static size_t run_requests;
1469 DEFINE_ALLOCATOR(realloc_run_requests, struct run_request, 8)
1471 static enum request
1472 add_run_request(enum keymap keymap, int key, int argc, const char **argv)
1474         struct run_request *req;
1476         if (argc >= ARRAY_SIZE(req->argv) - 1)
1477                 return REQ_NONE;
1479         if (!realloc_run_requests(&run_request, run_requests, 1))
1480                 return REQ_NONE;
1482         req = &run_request[run_requests];
1483         req->keymap = keymap;
1484         req->key = key;
1485         req->argv[0] = NULL;
1487         if (!format_argv(req->argv, argv, FORMAT_NONE))
1488                 return REQ_NONE;
1490         return REQ_NONE + ++run_requests;
1493 static struct run_request *
1494 get_run_request(enum request request)
1496         if (request <= REQ_NONE)
1497                 return NULL;
1498         return &run_request[request - REQ_NONE - 1];
1501 static void
1502 add_builtin_run_requests(void)
1504         const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1505         const char *commit[] = { "git", "commit", NULL };
1506         const char *gc[] = { "git", "gc", NULL };
1507         struct {
1508                 enum keymap keymap;
1509                 int key;
1510                 int argc;
1511                 const char **argv;
1512         } reqs[] = {
1513                 { KEYMAP_MAIN,    'C', ARRAY_SIZE(cherry_pick) - 1, cherry_pick },
1514                 { KEYMAP_STATUS,  'C', ARRAY_SIZE(commit) - 1, commit },
1515                 { KEYMAP_GENERIC, 'G', ARRAY_SIZE(gc) - 1, gc },
1516         };
1517         int i;
1519         for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1520                 enum request req;
1522                 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argc, reqs[i].argv);
1523                 if (req != REQ_NONE)
1524                         add_keybinding(reqs[i].keymap, req, reqs[i].key);
1525         }
1528 /*
1529  * User config file handling.
1530  */
1532 static int   config_lineno;
1533 static bool  config_errors;
1534 static const char *config_msg;
1536 static const struct enum_map color_map[] = {
1537 #define COLOR_MAP(name) ENUM_MAP(#name, COLOR_##name)
1538         COLOR_MAP(DEFAULT),
1539         COLOR_MAP(BLACK),
1540         COLOR_MAP(BLUE),
1541         COLOR_MAP(CYAN),
1542         COLOR_MAP(GREEN),
1543         COLOR_MAP(MAGENTA),
1544         COLOR_MAP(RED),
1545         COLOR_MAP(WHITE),
1546         COLOR_MAP(YELLOW),
1547 };
1549 static const struct enum_map attr_map[] = {
1550 #define ATTR_MAP(name) ENUM_MAP(#name, A_##name)
1551         ATTR_MAP(NORMAL),
1552         ATTR_MAP(BLINK),
1553         ATTR_MAP(BOLD),
1554         ATTR_MAP(DIM),
1555         ATTR_MAP(REVERSE),
1556         ATTR_MAP(STANDOUT),
1557         ATTR_MAP(UNDERLINE),
1558 };
1560 #define set_attribute(attr, name)       map_enum(attr, attr_map, name)
1562 static int parse_step(double *opt, const char *arg)
1564         *opt = atoi(arg);
1565         if (!strchr(arg, '%'))
1566                 return OK;
1568         /* "Shift down" so 100% and 1 does not conflict. */
1569         *opt = (*opt - 1) / 100;
1570         if (*opt >= 1.0) {
1571                 *opt = 0.99;
1572                 config_msg = "Step value larger than 100%";
1573                 return ERR;
1574         }
1575         if (*opt < 0.0) {
1576                 *opt = 1;
1577                 config_msg = "Invalid step value";
1578                 return ERR;
1579         }
1580         return OK;
1583 static int
1584 parse_int(int *opt, const char *arg, int min, int max)
1586         int value = atoi(arg);
1588         if (min <= value && value <= max) {
1589                 *opt = value;
1590                 return OK;
1591         }
1593         config_msg = "Integer value out of bound";
1594         return ERR;
1597 static bool
1598 set_color(int *color, const char *name)
1600         if (map_enum(color, color_map, name))
1601                 return TRUE;
1602         if (!prefixcmp(name, "color"))
1603                 return parse_int(color, name + 5, 0, 255) == OK;
1604         return FALSE;
1607 /* Wants: object fgcolor bgcolor [attribute] */
1608 static int
1609 option_color_command(int argc, const char *argv[])
1611         struct line_info *info;
1613         if (argc < 3) {
1614                 config_msg = "Wrong number of arguments given to color command";
1615                 return ERR;
1616         }
1618         info = get_line_info(argv[0]);
1619         if (!info) {
1620                 static const struct enum_map obsolete[] = {
1621                         ENUM_MAP("main-delim",  LINE_DELIMITER),
1622                         ENUM_MAP("main-date",   LINE_DATE),
1623                         ENUM_MAP("main-author", LINE_AUTHOR),
1624                 };
1625                 int index;
1627                 if (!map_enum(&index, obsolete, argv[0])) {
1628                         config_msg = "Unknown color name";
1629                         return ERR;
1630                 }
1631                 info = &line_info[index];
1632         }
1634         if (!set_color(&info->fg, argv[1]) ||
1635             !set_color(&info->bg, argv[2])) {
1636                 config_msg = "Unknown color";
1637                 return ERR;
1638         }
1640         info->attr = 0;
1641         while (argc-- > 3) {
1642                 int attr;
1644                 if (!set_attribute(&attr, argv[argc])) {
1645                         config_msg = "Unknown attribute";
1646                         return ERR;
1647                 }
1648                 info->attr |= attr;
1649         }
1651         return OK;
1654 static int parse_bool(bool *opt, const char *arg)
1656         *opt = (!strcmp(arg, "1") || !strcmp(arg, "true") || !strcmp(arg, "yes"))
1657                 ? TRUE : FALSE;
1658         return OK;
1661 static int
1662 parse_string(char *opt, const char *arg, size_t optsize)
1664         int arglen = strlen(arg);
1666         switch (arg[0]) {
1667         case '\"':
1668         case '\'':
1669                 if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1670                         config_msg = "Unmatched quotation";
1671                         return ERR;
1672                 }
1673                 arg += 1; arglen -= 2;
1674         default:
1675                 string_ncopy_do(opt, optsize, arg, arglen);
1676                 return OK;
1677         }
1680 /* Wants: name = value */
1681 static int
1682 option_set_command(int argc, const char *argv[])
1684         if (argc != 3) {
1685                 config_msg = "Wrong number of arguments given to set command";
1686                 return ERR;
1687         }
1689         if (strcmp(argv[1], "=")) {
1690                 config_msg = "No value assigned";
1691                 return ERR;
1692         }
1694         if (!strcmp(argv[0], "show-author"))
1695                 return parse_bool(&opt_author, argv[2]);
1697         if (!strcmp(argv[0], "show-date")) {
1698                 bool show_date;
1700                 if (!strcmp(argv[2], "relative")) {
1701                         opt_date = DATE_RELATIVE;
1702                         return OK;
1703                 } else if (!strcmp(argv[2], "short")) {
1704                         opt_date = DATE_SHORT;
1705                         return OK;
1706                 } else if (parse_bool(&show_date, argv[2])) {
1707                         opt_date = show_date ? DATE_DEFAULT : DATE_NONE;
1708                 }
1709                 return ERR;
1710         }
1712         if (!strcmp(argv[0], "show-rev-graph"))
1713                 return parse_bool(&opt_rev_graph, argv[2]);
1715         if (!strcmp(argv[0], "show-refs"))
1716                 return parse_bool(&opt_show_refs, argv[2]);
1718         if (!strcmp(argv[0], "show-line-numbers"))
1719                 return parse_bool(&opt_line_number, argv[2]);
1721         if (!strcmp(argv[0], "line-graphics"))
1722                 return parse_bool(&opt_line_graphics, argv[2]);
1724         if (!strcmp(argv[0], "line-number-interval"))
1725                 return parse_int(&opt_num_interval, argv[2], 1, 1024);
1727         if (!strcmp(argv[0], "author-width"))
1728                 return parse_int(&opt_author_cols, argv[2], 0, 1024);
1730         if (!strcmp(argv[0], "horizontal-scroll"))
1731                 return parse_step(&opt_hscroll, argv[2]);
1733         if (!strcmp(argv[0], "split-view-height"))
1734                 return parse_step(&opt_scale_split_view, argv[2]);
1736         if (!strcmp(argv[0], "tab-size"))
1737                 return parse_int(&opt_tab_size, argv[2], 1, 1024);
1739         if (!strcmp(argv[0], "commit-encoding"))
1740                 return parse_string(opt_encoding, argv[2], sizeof(opt_encoding));
1742         config_msg = "Unknown variable name";
1743         return ERR;
1746 /* Wants: mode request key */
1747 static int
1748 option_bind_command(int argc, const char *argv[])
1750         enum request request;
1751         int keymap = -1;
1752         int key;
1754         if (argc < 3) {
1755                 config_msg = "Wrong number of arguments given to bind command";
1756                 return ERR;
1757         }
1759         if (set_keymap(&keymap, argv[0]) == ERR) {
1760                 config_msg = "Unknown key map";
1761                 return ERR;
1762         }
1764         key = get_key_value(argv[1]);
1765         if (key == ERR) {
1766                 config_msg = "Unknown key";
1767                 return ERR;
1768         }
1770         request = get_request(argv[2]);
1771         if (request == REQ_NONE) {
1772                 static const struct enum_map obsolete[] = {
1773                         ENUM_MAP("cherry-pick",         REQ_NONE),
1774                         ENUM_MAP("screen-resize",       REQ_NONE),
1775                         ENUM_MAP("tree-parent",         REQ_PARENT),
1776                 };
1777                 int alias;
1779                 if (map_enum(&alias, obsolete, argv[2])) {
1780                         if (alias != REQ_NONE)
1781                                 add_keybinding(keymap, alias, key);
1782                         config_msg = "Obsolete request name";
1783                         return ERR;
1784                 }
1785         }
1786         if (request == REQ_NONE && *argv[2]++ == '!')
1787                 request = add_run_request(keymap, key, argc - 2, argv + 2);
1788         if (request == REQ_NONE) {
1789                 config_msg = "Unknown request name";
1790                 return ERR;
1791         }
1793         add_keybinding(keymap, request, key);
1795         return OK;
1798 static int
1799 set_option(const char *opt, char *value)
1801         const char *argv[SIZEOF_ARG];
1802         int argc = 0;
1804         if (!argv_from_string(argv, &argc, value)) {
1805                 config_msg = "Too many option arguments";
1806                 return ERR;
1807         }
1809         if (!strcmp(opt, "color"))
1810                 return option_color_command(argc, argv);
1812         if (!strcmp(opt, "set"))
1813                 return option_set_command(argc, argv);
1815         if (!strcmp(opt, "bind"))
1816                 return option_bind_command(argc, argv);
1818         config_msg = "Unknown option command";
1819         return ERR;
1822 static int
1823 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
1825         int status = OK;
1827         config_lineno++;
1828         config_msg = "Internal error";
1830         /* Check for comment markers, since read_properties() will
1831          * only ensure opt and value are split at first " \t". */
1832         optlen = strcspn(opt, "#");
1833         if (optlen == 0)
1834                 return OK;
1836         if (opt[optlen] != 0) {
1837                 config_msg = "No option value";
1838                 status = ERR;
1840         }  else {
1841                 /* Look for comment endings in the value. */
1842                 size_t len = strcspn(value, "#");
1844                 if (len < valuelen) {
1845                         valuelen = len;
1846                         value[valuelen] = 0;
1847                 }
1849                 status = set_option(opt, value);
1850         }
1852         if (status == ERR) {
1853                 warn("Error on line %d, near '%.*s': %s",
1854                      config_lineno, (int) optlen, opt, config_msg);
1855                 config_errors = TRUE;
1856         }
1858         /* Always keep going if errors are encountered. */
1859         return OK;
1862 static void
1863 load_option_file(const char *path)
1865         struct io io = {};
1867         /* It's OK that the file doesn't exist. */
1868         if (!io_open(&io, "%s", path))
1869                 return;
1871         config_lineno = 0;
1872         config_errors = FALSE;
1874         if (io_load(&io, " \t", read_option) == ERR ||
1875             config_errors == TRUE)
1876                 warn("Errors while loading %s.", path);
1879 static int
1880 load_options(void)
1882         const char *home = getenv("HOME");
1883         const char *tigrc_user = getenv("TIGRC_USER");
1884         const char *tigrc_system = getenv("TIGRC_SYSTEM");
1885         char buf[SIZEOF_STR];
1887         add_builtin_run_requests();
1889         if (!tigrc_system)
1890                 tigrc_system = SYSCONFDIR "/tigrc";
1891         load_option_file(tigrc_system);
1893         if (!tigrc_user) {
1894                 if (!home || !string_format(buf, "%s/.tigrc", home))
1895                         return ERR;
1896                 tigrc_user = buf;
1897         }
1898         load_option_file(tigrc_user);
1900         return OK;
1904 /*
1905  * The viewer
1906  */
1908 struct view;
1909 struct view_ops;
1911 /* The display array of active views and the index of the current view. */
1912 static struct view *display[2];
1913 static unsigned int current_view;
1915 #define foreach_displayed_view(view, i) \
1916         for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1918 #define displayed_views()       (display[1] != NULL ? 2 : 1)
1920 /* Current head and commit ID */
1921 static char ref_blob[SIZEOF_REF]        = "";
1922 static char ref_commit[SIZEOF_REF]      = "HEAD";
1923 static char ref_head[SIZEOF_REF]        = "HEAD";
1925 struct view {
1926         const char *name;       /* View name */
1927         const char *cmd_env;    /* Command line set via environment */
1928         const char *id;         /* Points to either of ref_{head,commit,blob} */
1930         struct view_ops *ops;   /* View operations */
1932         enum keymap keymap;     /* What keymap does this view have */
1933         bool git_dir;           /* Whether the view requires a git directory. */
1935         char ref[SIZEOF_REF];   /* Hovered commit reference */
1936         char vid[SIZEOF_REF];   /* View ID. Set to id member when updating. */
1938         int height, width;      /* The width and height of the main window */
1939         WINDOW *win;            /* The main window */
1940         WINDOW *title;          /* The title window living below the main window */
1942         /* Navigation */
1943         unsigned long offset;   /* Offset of the window top */
1944         unsigned long yoffset;  /* Offset from the window side. */
1945         unsigned long lineno;   /* Current line number */
1946         unsigned long p_offset; /* Previous offset of the window top */
1947         unsigned long p_yoffset;/* Previous offset from the window side */
1948         unsigned long p_lineno; /* Previous current line number */
1949         bool p_restore;         /* Should the previous position be restored. */
1951         /* Searching */
1952         char grep[SIZEOF_STR];  /* Search string */
1953         regex_t *regex;         /* Pre-compiled regexp */
1955         /* If non-NULL, points to the view that opened this view. If this view
1956          * is closed tig will switch back to the parent view. */
1957         struct view *parent;
1959         /* Buffering */
1960         size_t lines;           /* Total number of lines */
1961         struct line *line;      /* Line index */
1962         unsigned int digits;    /* Number of digits in the lines member. */
1964         /* Drawing */
1965         struct line *curline;   /* Line currently being drawn. */
1966         enum line_type curtype; /* Attribute currently used for drawing. */
1967         unsigned long col;      /* Column when drawing. */
1968         bool has_scrolled;      /* View was scrolled. */
1970         /* Loading */
1971         struct io io;
1972         struct io *pipe;
1973         time_t start_time;
1974         time_t update_secs;
1975 };
1977 struct view_ops {
1978         /* What type of content being displayed. Used in the title bar. */
1979         const char *type;
1980         /* Default command arguments. */
1981         const char **argv;
1982         /* Open and reads in all view content. */
1983         bool (*open)(struct view *view);
1984         /* Read one line; updates view->line. */
1985         bool (*read)(struct view *view, char *data);
1986         /* Draw one line; @lineno must be < view->height. */
1987         bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
1988         /* Depending on view handle a special requests. */
1989         enum request (*request)(struct view *view, enum request request, struct line *line);
1990         /* Search for regexp in a line. */
1991         bool (*grep)(struct view *view, struct line *line);
1992         /* Select line */
1993         void (*select)(struct view *view, struct line *line);
1994         /* Prepare view for loading */
1995         bool (*prepare)(struct view *view);
1996 };
1998 static struct view_ops blame_ops;
1999 static struct view_ops blob_ops;
2000 static struct view_ops diff_ops;
2001 static struct view_ops help_ops;
2002 static struct view_ops log_ops;
2003 static struct view_ops main_ops;
2004 static struct view_ops pager_ops;
2005 static struct view_ops stage_ops;
2006 static struct view_ops status_ops;
2007 static struct view_ops tree_ops;
2008 static struct view_ops branch_ops;
2010 #define VIEW_STR(name, env, ref, ops, map, git) \
2011         { name, #env, ref, ops, map, git }
2013 #define VIEW_(id, name, ops, git, ref) \
2014         VIEW_STR(name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
2017 static struct view views[] = {
2018         VIEW_(MAIN,   "main",   &main_ops,   TRUE,  ref_head),
2019         VIEW_(DIFF,   "diff",   &diff_ops,   TRUE,  ref_commit),
2020         VIEW_(LOG,    "log",    &log_ops,    TRUE,  ref_head),
2021         VIEW_(TREE,   "tree",   &tree_ops,   TRUE,  ref_commit),
2022         VIEW_(BLOB,   "blob",   &blob_ops,   TRUE,  ref_blob),
2023         VIEW_(BLAME,  "blame",  &blame_ops,  TRUE,  ref_commit),
2024         VIEW_(BRANCH, "branch", &branch_ops, TRUE,  ref_head),
2025         VIEW_(HELP,   "help",   &help_ops,   FALSE, ""),
2026         VIEW_(PAGER,  "pager",  &pager_ops,  FALSE, "stdin"),
2027         VIEW_(STATUS, "status", &status_ops, TRUE,  ""),
2028         VIEW_(STAGE,  "stage",  &stage_ops,  TRUE,  ""),
2029 };
2031 #define VIEW(req)       (&views[(req) - REQ_OFFSET - 1])
2032 #define VIEW_REQ(view)  ((view) - views + REQ_OFFSET + 1)
2034 #define foreach_view(view, i) \
2035         for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
2037 #define view_is_displayed(view) \
2038         (view == display[0] || view == display[1])
2041 enum line_graphic {
2042         LINE_GRAPHIC_VLINE
2043 };
2045 static chtype line_graphics[] = {
2046         /* LINE_GRAPHIC_VLINE: */ '|'
2047 };
2049 static inline void
2050 set_view_attr(struct view *view, enum line_type type)
2052         if (!view->curline->selected && view->curtype != type) {
2053                 wattrset(view->win, get_line_attr(type));
2054                 wchgat(view->win, -1, 0, type, NULL);
2055                 view->curtype = type;
2056         }
2059 static int
2060 draw_chars(struct view *view, enum line_type type, const char *string,
2061            int max_len, bool use_tilde)
2063         int len = 0;
2064         int col = 0;
2065         int trimmed = FALSE;
2066         size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2068         if (max_len <= 0)
2069                 return 0;
2071         if (opt_utf8) {
2072                 len = utf8_length(&string, skip, &col, max_len, &trimmed, use_tilde);
2073         } else {
2074                 col = len = strlen(string);
2075                 if (len > max_len) {
2076                         if (use_tilde) {
2077                                 max_len -= 1;
2078                         }
2079                         col = len = max_len;
2080                         trimmed = TRUE;
2081                 }
2082         }
2084         set_view_attr(view, type);
2085         if (len > 0)
2086                 waddnstr(view->win, string, len);
2087         if (trimmed && use_tilde) {
2088                 set_view_attr(view, LINE_DELIMITER);
2089                 waddch(view->win, '~');
2090                 col++;
2091         }
2093         return col;
2096 static int
2097 draw_space(struct view *view, enum line_type type, int max, int spaces)
2099         static char space[] = "                    ";
2100         int col = 0;
2102         spaces = MIN(max, spaces);
2104         while (spaces > 0) {
2105                 int len = MIN(spaces, sizeof(space) - 1);
2107                 col += draw_chars(view, type, space, len, FALSE);
2108                 spaces -= len;
2109         }
2111         return col;
2114 static bool
2115 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
2117         view->col += draw_chars(view, type, string, view->width + view->yoffset - view->col, trim);
2118         return view->width + view->yoffset <= view->col;
2121 static bool
2122 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
2124         size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2125         int max = view->width + view->yoffset - view->col;
2126         int i;
2128         if (max < size)
2129                 size = max;
2131         set_view_attr(view, type);
2132         /* Using waddch() instead of waddnstr() ensures that
2133          * they'll be rendered correctly for the cursor line. */
2134         for (i = skip; i < size; i++)
2135                 waddch(view->win, graphic[i]);
2137         view->col += size;
2138         if (size < max && skip <= size)
2139                 waddch(view->win, ' ');
2140         view->col++;
2142         return view->width + view->yoffset <= view->col;
2145 static bool
2146 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
2148         int max = MIN(view->width + view->yoffset - view->col, len);
2149         int col;
2151         if (text)
2152                 col = draw_chars(view, type, text, max - 1, trim);
2153         else
2154                 col = draw_space(view, type, max - 1, max - 1);
2156         view->col += col;
2157         view->col += draw_space(view, LINE_DEFAULT, max - col, max - col);
2158         return view->width + view->yoffset <= view->col;
2161 static bool
2162 draw_date(struct view *view, time_t *time)
2164         const char *date = time ? mkdate(time) : "";
2165         int cols = opt_date == DATE_SHORT ? DATE_SHORT_COLS : DATE_COLS;
2167         return draw_field(view, LINE_DATE, date, cols, FALSE);
2170 static bool
2171 draw_author(struct view *view, const char *author)
2173         bool trim = opt_author_cols == 0 || opt_author_cols > 5 || !author;
2175         if (!trim) {
2176                 static char initials[10];
2177                 size_t pos;
2179 #define is_initial_sep(c) (isspace(c) || ispunct(c) || (c) == '@')
2181                 memset(initials, 0, sizeof(initials));
2182                 for (pos = 0; *author && pos < opt_author_cols - 1; author++, pos++) {
2183                         while (is_initial_sep(*author))
2184                                 author++;
2185                         strncpy(&initials[pos], author, sizeof(initials) - 1 - pos);
2186                         while (*author && !is_initial_sep(author[1]))
2187                                 author++;
2188                 }
2190                 author = initials;
2191         }
2193         return draw_field(view, LINE_AUTHOR, author, opt_author_cols, trim);
2196 static bool
2197 draw_mode(struct view *view, mode_t mode)
2199         const char *str;
2201         if (S_ISDIR(mode))
2202                 str = "drwxr-xr-x";
2203         else if (S_ISLNK(mode))
2204                 str = "lrwxrwxrwx";
2205         else if (S_ISGITLINK(mode))
2206                 str = "m---------";
2207         else if (S_ISREG(mode) && mode & S_IXUSR)
2208                 str = "-rwxr-xr-x";
2209         else if (S_ISREG(mode))
2210                 str = "-rw-r--r--";
2211         else
2212                 str = "----------";
2214         return draw_field(view, LINE_MODE, str, STRING_SIZE("-rw-r--r-- "), FALSE);
2217 static bool
2218 draw_lineno(struct view *view, unsigned int lineno)
2220         char number[10];
2221         int digits3 = view->digits < 3 ? 3 : view->digits;
2222         int max = MIN(view->width + view->yoffset - view->col, digits3);
2223         char *text = NULL;
2225         lineno += view->offset + 1;
2226         if (lineno == 1 || (lineno % opt_num_interval) == 0) {
2227                 static char fmt[] = "%1ld";
2229                 fmt[1] = '0' + (view->digits <= 9 ? digits3 : 1);
2230                 if (string_format(number, fmt, lineno))
2231                         text = number;
2232         }
2233         if (text)
2234                 view->col += draw_chars(view, LINE_LINE_NUMBER, text, max, TRUE);
2235         else
2236                 view->col += draw_space(view, LINE_LINE_NUMBER, max, digits3);
2237         return draw_graphic(view, LINE_DEFAULT, &line_graphics[LINE_GRAPHIC_VLINE], 1);
2240 static bool
2241 draw_view_line(struct view *view, unsigned int lineno)
2243         struct line *line;
2244         bool selected = (view->offset + lineno == view->lineno);
2246         assert(view_is_displayed(view));
2248         if (view->offset + lineno >= view->lines)
2249                 return FALSE;
2251         line = &view->line[view->offset + lineno];
2253         wmove(view->win, lineno, 0);
2254         if (line->cleareol)
2255                 wclrtoeol(view->win);
2256         view->col = 0;
2257         view->curline = line;
2258         view->curtype = LINE_NONE;
2259         line->selected = FALSE;
2260         line->dirty = line->cleareol = 0;
2262         if (selected) {
2263                 set_view_attr(view, LINE_CURSOR);
2264                 line->selected = TRUE;
2265                 view->ops->select(view, line);
2266         }
2268         return view->ops->draw(view, line, lineno);
2271 static void
2272 redraw_view_dirty(struct view *view)
2274         bool dirty = FALSE;
2275         int lineno;
2277         for (lineno = 0; lineno < view->height; lineno++) {
2278                 if (view->offset + lineno >= view->lines)
2279                         break;
2280                 if (!view->line[view->offset + lineno].dirty)
2281                         continue;
2282                 dirty = TRUE;
2283                 if (!draw_view_line(view, lineno))
2284                         break;
2285         }
2287         if (!dirty)
2288                 return;
2289         wnoutrefresh(view->win);
2292 static void
2293 redraw_view_from(struct view *view, int lineno)
2295         assert(0 <= lineno && lineno < view->height);
2297         for (; lineno < view->height; lineno++) {
2298                 if (!draw_view_line(view, lineno))
2299                         break;
2300         }
2302         wnoutrefresh(view->win);
2305 static void
2306 redraw_view(struct view *view)
2308         werase(view->win);
2309         redraw_view_from(view, 0);
2313 static void
2314 update_view_title(struct view *view)
2316         char buf[SIZEOF_STR];
2317         char state[SIZEOF_STR];
2318         size_t bufpos = 0, statelen = 0;
2320         assert(view_is_displayed(view));
2322         if (view != VIEW(REQ_VIEW_STATUS) && view->lines) {
2323                 unsigned int view_lines = view->offset + view->height;
2324                 unsigned int lines = view->lines
2325                                    ? MIN(view_lines, view->lines) * 100 / view->lines
2326                                    : 0;
2328                 string_format_from(state, &statelen, " - %s %d of %d (%d%%)",
2329                                    view->ops->type,
2330                                    view->lineno + 1,
2331                                    view->lines,
2332                                    lines);
2334         }
2336         if (view->pipe) {
2337                 time_t secs = time(NULL) - view->start_time;
2339                 /* Three git seconds are a long time ... */
2340                 if (secs > 2)
2341                         string_format_from(state, &statelen, " loading %lds", secs);
2342         }
2344         string_format_from(buf, &bufpos, "[%s]", view->name);
2345         if (*view->ref && bufpos < view->width) {
2346                 size_t refsize = strlen(view->ref);
2347                 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2349                 if (minsize < view->width)
2350                         refsize = view->width - minsize + 7;
2351                 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2352         }
2354         if (statelen && bufpos < view->width) {
2355                 string_format_from(buf, &bufpos, "%s", state);
2356         }
2358         if (view == display[current_view])
2359                 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2360         else
2361                 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2363         mvwaddnstr(view->title, 0, 0, buf, bufpos);
2364         wclrtoeol(view->title);
2365         wnoutrefresh(view->title);
2368 static int
2369 apply_step(double step, int value)
2371         if (step >= 1)
2372                 return (int) step;
2373         value *= step + 0.01;
2374         return value ? value : 1;
2377 static void
2378 resize_display(void)
2380         int offset, i;
2381         struct view *base = display[0];
2382         struct view *view = display[1] ? display[1] : display[0];
2384         /* Setup window dimensions */
2386         getmaxyx(stdscr, base->height, base->width);
2388         /* Make room for the status window. */
2389         base->height -= 1;
2391         if (view != base) {
2392                 /* Horizontal split. */
2393                 view->width   = base->width;
2394                 view->height  = apply_step(opt_scale_split_view, base->height);
2395                 view->height  = MAX(view->height, MIN_VIEW_HEIGHT);
2396                 view->height  = MIN(view->height, base->height - MIN_VIEW_HEIGHT);
2397                 base->height -= view->height;
2399                 /* Make room for the title bar. */
2400                 view->height -= 1;
2401         }
2403         /* Make room for the title bar. */
2404         base->height -= 1;
2406         offset = 0;
2408         foreach_displayed_view (view, i) {
2409                 if (!view->win) {
2410                         view->win = newwin(view->height, 0, offset, 0);
2411                         if (!view->win)
2412                                 die("Failed to create %s view", view->name);
2414                         scrollok(view->win, FALSE);
2416                         view->title = newwin(1, 0, offset + view->height, 0);
2417                         if (!view->title)
2418                                 die("Failed to create title window");
2420                 } else {
2421                         wresize(view->win, view->height, view->width);
2422                         mvwin(view->win,   offset, 0);
2423                         mvwin(view->title, offset + view->height, 0);
2424                 }
2426                 offset += view->height + 1;
2427         }
2430 static void
2431 redraw_display(bool clear)
2433         struct view *view;
2434         int i;
2436         foreach_displayed_view (view, i) {
2437                 if (clear)
2438                         wclear(view->win);
2439                 redraw_view(view);
2440                 update_view_title(view);
2441         }
2444 static void
2445 toggle_date_option(enum date *date)
2447         static const char *help[] = {
2448                 "no",
2449                 "default",
2450                 "relative",
2451                 "short"
2452         };
2454         opt_date = (opt_date + 1) % ARRAY_SIZE(help);
2455         redraw_display(FALSE);
2456         report("Displaying %s dates", help[opt_date]);
2459 static void
2460 toggle_view_option(bool *option, const char *help)
2462         *option = !*option;
2463         redraw_display(FALSE);
2464         report("%sabling %s", *option ? "En" : "Dis", help);
2467 static void
2468 open_option_menu(void)
2470         const struct menu_item menu[] = {
2471                 { '.', "line numbers", &opt_line_number },
2472                 { 'D', "date display", &opt_date },
2473                 { 'A', "author display", &opt_author },
2474                 { 'g', "revision graph display", &opt_rev_graph },
2475                 { 'F', "reference display", &opt_show_refs },
2476                 { 0 }
2477         };
2478         int selected = 0;
2480         if (prompt_menu("Toggle option", menu, &selected)) {
2481                 if (menu[selected].data == &opt_date)
2482                         toggle_date_option(menu[selected].data);
2483                 else
2484                         toggle_view_option(menu[selected].data, menu[selected].text);
2485         }
2488 static void
2489 maximize_view(struct view *view)
2491         memset(display, 0, sizeof(display));
2492         current_view = 0;
2493         display[current_view] = view;
2494         resize_display();
2495         redraw_display(FALSE);
2496         report("");
2500 /*
2501  * Navigation
2502  */
2504 static bool
2505 goto_view_line(struct view *view, unsigned long offset, unsigned long lineno)
2507         if (lineno >= view->lines)
2508                 lineno = view->lines > 0 ? view->lines - 1 : 0;
2510         if (offset > lineno || offset + view->height <= lineno) {
2511                 unsigned long half = view->height / 2;
2513                 if (lineno > half)
2514                         offset = lineno - half;
2515                 else
2516                         offset = 0;
2517         }
2519         if (offset != view->offset || lineno != view->lineno) {
2520                 view->offset = offset;
2521                 view->lineno = lineno;
2522                 return TRUE;
2523         }
2525         return FALSE;
2528 /* Scrolling backend */
2529 static void
2530 do_scroll_view(struct view *view, int lines)
2532         bool redraw_current_line = FALSE;
2534         /* The rendering expects the new offset. */
2535         view->offset += lines;
2537         assert(0 <= view->offset && view->offset < view->lines);
2538         assert(lines);
2540         /* Move current line into the view. */
2541         if (view->lineno < view->offset) {
2542                 view->lineno = view->offset;
2543                 redraw_current_line = TRUE;
2544         } else if (view->lineno >= view->offset + view->height) {
2545                 view->lineno = view->offset + view->height - 1;
2546                 redraw_current_line = TRUE;
2547         }
2549         assert(view->offset <= view->lineno && view->lineno < view->lines);
2551         /* Redraw the whole screen if scrolling is pointless. */
2552         if (view->height < ABS(lines)) {
2553                 redraw_view(view);
2555         } else {
2556                 int line = lines > 0 ? view->height - lines : 0;
2557                 int end = line + ABS(lines);
2559                 scrollok(view->win, TRUE);
2560                 wscrl(view->win, lines);
2561                 scrollok(view->win, FALSE);
2563                 while (line < end && draw_view_line(view, line))
2564                         line++;
2566                 if (redraw_current_line)
2567                         draw_view_line(view, view->lineno - view->offset);
2568                 wnoutrefresh(view->win);
2569         }
2571         view->has_scrolled = TRUE;
2572         report("");
2575 /* Scroll frontend */
2576 static void
2577 scroll_view(struct view *view, enum request request)
2579         int lines = 1;
2581         assert(view_is_displayed(view));
2583         switch (request) {
2584         case REQ_SCROLL_LEFT:
2585                 if (view->yoffset == 0) {
2586                         report("Cannot scroll beyond the first column");
2587                         return;
2588                 }
2589                 if (view->yoffset <= apply_step(opt_hscroll, view->width))
2590                         view->yoffset = 0;
2591                 else
2592                         view->yoffset -= apply_step(opt_hscroll, view->width);
2593                 redraw_view_from(view, 0);
2594                 report("");
2595                 return;
2596         case REQ_SCROLL_RIGHT:
2597                 view->yoffset += apply_step(opt_hscroll, view->width);
2598                 redraw_view(view);
2599                 report("");
2600                 return;
2601         case REQ_SCROLL_PAGE_DOWN:
2602                 lines = view->height;
2603         case REQ_SCROLL_LINE_DOWN:
2604                 if (view->offset + lines > view->lines)
2605                         lines = view->lines - view->offset;
2607                 if (lines == 0 || view->offset + view->height >= view->lines) {
2608                         report("Cannot scroll beyond the last line");
2609                         return;
2610                 }
2611                 break;
2613         case REQ_SCROLL_PAGE_UP:
2614                 lines = view->height;
2615         case REQ_SCROLL_LINE_UP:
2616                 if (lines > view->offset)
2617                         lines = view->offset;
2619                 if (lines == 0) {
2620                         report("Cannot scroll beyond the first line");
2621                         return;
2622                 }
2624                 lines = -lines;
2625                 break;
2627         default:
2628                 die("request %d not handled in switch", request);
2629         }
2631         do_scroll_view(view, lines);
2634 /* Cursor moving */
2635 static void
2636 move_view(struct view *view, enum request request)
2638         int scroll_steps = 0;
2639         int steps;
2641         switch (request) {
2642         case REQ_MOVE_FIRST_LINE:
2643                 steps = -view->lineno;
2644                 break;
2646         case REQ_MOVE_LAST_LINE:
2647                 steps = view->lines - view->lineno - 1;
2648                 break;
2650         case REQ_MOVE_PAGE_UP:
2651                 steps = view->height > view->lineno
2652                       ? -view->lineno : -view->height;
2653                 break;
2655         case REQ_MOVE_PAGE_DOWN:
2656                 steps = view->lineno + view->height >= view->lines
2657                       ? view->lines - view->lineno - 1 : view->height;
2658                 break;
2660         case REQ_MOVE_UP:
2661                 steps = -1;
2662                 break;
2664         case REQ_MOVE_DOWN:
2665                 steps = 1;
2666                 break;
2668         default:
2669                 die("request %d not handled in switch", request);
2670         }
2672         if (steps <= 0 && view->lineno == 0) {
2673                 report("Cannot move beyond the first line");
2674                 return;
2676         } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2677                 report("Cannot move beyond the last line");
2678                 return;
2679         }
2681         /* Move the current line */
2682         view->lineno += steps;
2683         assert(0 <= view->lineno && view->lineno < view->lines);
2685         /* Check whether the view needs to be scrolled */
2686         if (view->lineno < view->offset ||
2687             view->lineno >= view->offset + view->height) {
2688                 scroll_steps = steps;
2689                 if (steps < 0 && -steps > view->offset) {
2690                         scroll_steps = -view->offset;
2692                 } else if (steps > 0) {
2693                         if (view->lineno == view->lines - 1 &&
2694                             view->lines > view->height) {
2695                                 scroll_steps = view->lines - view->offset - 1;
2696                                 if (scroll_steps >= view->height)
2697                                         scroll_steps -= view->height - 1;
2698                         }
2699                 }
2700         }
2702         if (!view_is_displayed(view)) {
2703                 view->offset += scroll_steps;
2704                 assert(0 <= view->offset && view->offset < view->lines);
2705                 view->ops->select(view, &view->line[view->lineno]);
2706                 return;
2707         }
2709         /* Repaint the old "current" line if we be scrolling */
2710         if (ABS(steps) < view->height)
2711                 draw_view_line(view, view->lineno - steps - view->offset);
2713         if (scroll_steps) {
2714                 do_scroll_view(view, scroll_steps);
2715                 return;
2716         }
2718         /* Draw the current line */
2719         draw_view_line(view, view->lineno - view->offset);
2721         wnoutrefresh(view->win);
2722         report("");
2726 /*
2727  * Searching
2728  */
2730 static void search_view(struct view *view, enum request request);
2732 static bool
2733 grep_text(struct view *view, const char *text[])
2735         regmatch_t pmatch;
2736         size_t i;
2738         for (i = 0; text[i]; i++)
2739                 if (*text[i] &&
2740                     regexec(view->regex, text[i], 1, &pmatch, 0) != REG_NOMATCH)
2741                         return TRUE;
2742         return FALSE;
2745 static void
2746 select_view_line(struct view *view, unsigned long lineno)
2748         unsigned long old_lineno = view->lineno;
2749         unsigned long old_offset = view->offset;
2751         if (goto_view_line(view, view->offset, lineno)) {
2752                 if (view_is_displayed(view)) {
2753                         if (old_offset != view->offset) {
2754                                 redraw_view(view);
2755                         } else {
2756                                 draw_view_line(view, old_lineno - view->offset);
2757                                 draw_view_line(view, view->lineno - view->offset);
2758                                 wnoutrefresh(view->win);
2759                         }
2760                 } else {
2761                         view->ops->select(view, &view->line[view->lineno]);
2762                 }
2763         }
2766 static void
2767 find_next(struct view *view, enum request request)
2769         unsigned long lineno = view->lineno;
2770         int direction;
2772         if (!*view->grep) {
2773                 if (!*opt_search)
2774                         report("No previous search");
2775                 else
2776                         search_view(view, request);
2777                 return;
2778         }
2780         switch (request) {
2781         case REQ_SEARCH:
2782         case REQ_FIND_NEXT:
2783                 direction = 1;
2784                 break;
2786         case REQ_SEARCH_BACK:
2787         case REQ_FIND_PREV:
2788                 direction = -1;
2789                 break;
2791         default:
2792                 return;
2793         }
2795         if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
2796                 lineno += direction;
2798         /* Note, lineno is unsigned long so will wrap around in which case it
2799          * will become bigger than view->lines. */
2800         for (; lineno < view->lines; lineno += direction) {
2801                 if (view->ops->grep(view, &view->line[lineno])) {
2802                         select_view_line(view, lineno);
2803                         report("Line %ld matches '%s'", lineno + 1, view->grep);
2804                         return;
2805                 }
2806         }
2808         report("No match found for '%s'", view->grep);
2811 static void
2812 search_view(struct view *view, enum request request)
2814         int regex_err;
2816         if (view->regex) {
2817                 regfree(view->regex);
2818                 *view->grep = 0;
2819         } else {
2820                 view->regex = calloc(1, sizeof(*view->regex));
2821                 if (!view->regex)
2822                         return;
2823         }
2825         regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
2826         if (regex_err != 0) {
2827                 char buf[SIZEOF_STR] = "unknown error";
2829                 regerror(regex_err, view->regex, buf, sizeof(buf));
2830                 report("Search failed: %s", buf);
2831                 return;
2832         }
2834         string_copy(view->grep, opt_search);
2836         find_next(view, request);
2839 /*
2840  * Incremental updating
2841  */
2843 static void
2844 reset_view(struct view *view)
2846         int i;
2848         for (i = 0; i < view->lines; i++)
2849                 free(view->line[i].data);
2850         free(view->line);
2852         view->p_offset = view->offset;
2853         view->p_yoffset = view->yoffset;
2854         view->p_lineno = view->lineno;
2856         view->line = NULL;
2857         view->offset = 0;
2858         view->yoffset = 0;
2859         view->lines  = 0;
2860         view->lineno = 0;
2861         view->vid[0] = 0;
2862         view->update_secs = 0;
2865 static void
2866 free_argv(const char *argv[])
2868         int argc;
2870         for (argc = 0; argv[argc]; argc++)
2871                 free((void *) argv[argc]);
2874 static bool
2875 format_argv(const char *dst_argv[], const char *src_argv[], enum format_flags flags)
2877         char buf[SIZEOF_STR];
2878         int argc;
2879         bool noreplace = flags == FORMAT_NONE;
2881         free_argv(dst_argv);
2883         for (argc = 0; src_argv[argc]; argc++) {
2884                 const char *arg = src_argv[argc];
2885                 size_t bufpos = 0;
2887                 while (arg) {
2888                         char *next = strstr(arg, "%(");
2889                         int len = next - arg;
2890                         const char *value;
2892                         if (!next || noreplace) {
2893                                 if (flags == FORMAT_DASH && !strcmp(arg, "--"))
2894                                         noreplace = TRUE;
2895                                 len = strlen(arg);
2896                                 value = "";
2898                         } else if (!prefixcmp(next, "%(directory)")) {
2899                                 value = opt_path;
2901                         } else if (!prefixcmp(next, "%(file)")) {
2902                                 value = opt_file;
2904                         } else if (!prefixcmp(next, "%(ref)")) {
2905                                 value = *opt_ref ? opt_ref : "HEAD";
2907                         } else if (!prefixcmp(next, "%(head)")) {
2908                                 value = ref_head;
2910                         } else if (!prefixcmp(next, "%(commit)")) {
2911                                 value = ref_commit;
2913                         } else if (!prefixcmp(next, "%(blob)")) {
2914                                 value = ref_blob;
2916                         } else {
2917                                 report("Unknown replacement: `%s`", next);
2918                                 return FALSE;
2919                         }
2921                         if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
2922                                 return FALSE;
2924                         arg = next && !noreplace ? strchr(next, ')') + 1 : NULL;
2925                 }
2927                 dst_argv[argc] = strdup(buf);
2928                 if (!dst_argv[argc])
2929                         break;
2930         }
2932         dst_argv[argc] = NULL;
2934         return src_argv[argc] == NULL;
2937 static bool
2938 restore_view_position(struct view *view)
2940         if (!view->p_restore || (view->pipe && view->lines <= view->p_lineno))
2941                 return FALSE;
2943         /* Changing the view position cancels the restoring. */
2944         /* FIXME: Changing back to the first line is not detected. */
2945         if (view->offset != 0 || view->lineno != 0) {
2946                 view->p_restore = FALSE;
2947                 return FALSE;
2948         }
2950         if (goto_view_line(view, view->p_offset, view->p_lineno) &&
2951             view_is_displayed(view))
2952                 werase(view->win);
2954         view->yoffset = view->p_yoffset;
2955         view->p_restore = FALSE;
2957         return TRUE;
2960 static void
2961 end_update(struct view *view, bool force)
2963         if (!view->pipe)
2964                 return;
2965         while (!view->ops->read(view, NULL))
2966                 if (!force)
2967                         return;
2968         set_nonblocking_input(FALSE);
2969         if (force)
2970                 kill_io(view->pipe);
2971         done_io(view->pipe);
2972         view->pipe = NULL;
2975 static void
2976 setup_update(struct view *view, const char *vid)
2978         set_nonblocking_input(TRUE);
2979         reset_view(view);
2980         string_copy_rev(view->vid, vid);
2981         view->pipe = &view->io;
2982         view->start_time = time(NULL);
2985 static bool
2986 prepare_update(struct view *view, const char *argv[], const char *dir,
2987                enum format_flags flags)
2989         if (view->pipe)
2990                 end_update(view, TRUE);
2991         return init_io_rd(&view->io, argv, dir, flags);
2994 static bool
2995 prepare_update_file(struct view *view, const char *name)
2997         if (view->pipe)
2998                 end_update(view, TRUE);
2999         return io_open(&view->io, "%s", name);
3002 static bool
3003 begin_update(struct view *view, bool refresh)
3005         if (view->pipe)
3006                 end_update(view, TRUE);
3008         if (!refresh) {
3009                 if (view->ops->prepare) {
3010                         if (!view->ops->prepare(view))
3011                                 return FALSE;
3012                 } else if (!init_io_rd(&view->io, view->ops->argv, NULL, FORMAT_ALL)) {
3013                         return FALSE;
3014                 }
3016                 /* Put the current ref_* value to the view title ref
3017                  * member. This is needed by the blob view. Most other
3018                  * views sets it automatically after loading because the
3019                  * first line is a commit line. */
3020                 string_copy_rev(view->ref, view->id);
3021         }
3023         if (!start_io(&view->io))
3024                 return FALSE;
3026         setup_update(view, view->id);
3028         return TRUE;
3031 static bool
3032 update_view(struct view *view)
3034         char out_buffer[BUFSIZ * 2];
3035         char *line;
3036         /* Clear the view and redraw everything since the tree sorting
3037          * might have rearranged things. */
3038         bool redraw = view->lines == 0;
3039         bool can_read = TRUE;
3041         if (!view->pipe)
3042                 return TRUE;
3044         if (!io_can_read(view->pipe)) {
3045                 if (view->lines == 0 && view_is_displayed(view)) {
3046                         time_t secs = time(NULL) - view->start_time;
3048                         if (secs > 1 && secs > view->update_secs) {
3049                                 if (view->update_secs == 0)
3050                                         redraw_view(view);
3051                                 update_view_title(view);
3052                                 view->update_secs = secs;
3053                         }
3054                 }
3055                 return TRUE;
3056         }
3058         for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
3059                 if (opt_iconv != ICONV_NONE) {
3060                         ICONV_CONST char *inbuf = line;
3061                         size_t inlen = strlen(line) + 1;
3063                         char *outbuf = out_buffer;
3064                         size_t outlen = sizeof(out_buffer);
3066                         size_t ret;
3068                         ret = iconv(opt_iconv, &inbuf, &inlen, &outbuf, &outlen);
3069                         if (ret != (size_t) -1)
3070                                 line = out_buffer;
3071                 }
3073                 if (!view->ops->read(view, line)) {
3074                         report("Allocation failure");
3075                         end_update(view, TRUE);
3076                         return FALSE;
3077                 }
3078         }
3080         {
3081                 unsigned long lines = view->lines;
3082                 int digits;
3084                 for (digits = 0; lines; digits++)
3085                         lines /= 10;
3087                 /* Keep the displayed view in sync with line number scaling. */
3088                 if (digits != view->digits) {
3089                         view->digits = digits;
3090                         if (opt_line_number || view == VIEW(REQ_VIEW_BLAME))
3091                                 redraw = TRUE;
3092                 }
3093         }
3095         if (io_error(view->pipe)) {
3096                 report("Failed to read: %s", io_strerror(view->pipe));
3097                 end_update(view, TRUE);
3099         } else if (io_eof(view->pipe)) {
3100                 report("");
3101                 end_update(view, FALSE);
3102         }
3104         if (restore_view_position(view))
3105                 redraw = TRUE;
3107         if (!view_is_displayed(view))
3108                 return TRUE;
3110         if (redraw)
3111                 redraw_view_from(view, 0);
3112         else
3113                 redraw_view_dirty(view);
3115         /* Update the title _after_ the redraw so that if the redraw picks up a
3116          * commit reference in view->ref it'll be available here. */
3117         update_view_title(view);
3118         return TRUE;
3121 DEFINE_ALLOCATOR(realloc_lines, struct line, 256)
3123 static struct line *
3124 add_line_data(struct view *view, void *data, enum line_type type)
3126         struct line *line;
3128         if (!realloc_lines(&view->line, view->lines, 1))
3129                 return NULL;
3131         line = &view->line[view->lines++];
3132         memset(line, 0, sizeof(*line));
3133         line->type = type;
3134         line->data = data;
3135         line->dirty = 1;
3137         return line;
3140 static struct line *
3141 add_line_text(struct view *view, const char *text, enum line_type type)
3143         char *data = text ? strdup(text) : NULL;
3145         return data ? add_line_data(view, data, type) : NULL;
3148 static struct line *
3149 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
3151         char buf[SIZEOF_STR];
3152         va_list args;
3154         va_start(args, fmt);
3155         if (vsnprintf(buf, sizeof(buf), fmt, args) >= sizeof(buf))
3156                 buf[0] = 0;
3157         va_end(args);
3159         return buf[0] ? add_line_text(view, buf, type) : NULL;
3162 /*
3163  * View opening
3164  */
3166 enum open_flags {
3167         OPEN_DEFAULT = 0,       /* Use default view switching. */
3168         OPEN_SPLIT = 1,         /* Split current view. */
3169         OPEN_RELOAD = 4,        /* Reload view even if it is the current. */
3170         OPEN_REFRESH = 16,      /* Refresh view using previous command. */
3171         OPEN_PREPARED = 32,     /* Open already prepared command. */
3172 };
3174 static void
3175 open_view(struct view *prev, enum request request, enum open_flags flags)
3177         bool split = !!(flags & OPEN_SPLIT);
3178         bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
3179         bool nomaximize = !!(flags & OPEN_REFRESH);
3180         struct view *view = VIEW(request);
3181         int nviews = displayed_views();
3182         struct view *base_view = display[0];
3184         if (view == prev && nviews == 1 && !reload) {
3185                 report("Already in %s view", view->name);
3186                 return;
3187         }
3189         if (view->git_dir && !opt_git_dir[0]) {
3190                 report("The %s view is disabled in pager view", view->name);
3191                 return;
3192         }
3194         if (split) {
3195                 display[1] = view;
3196                 current_view = 1;
3197         } else if (!nomaximize) {
3198                 /* Maximize the current view. */
3199                 memset(display, 0, sizeof(display));
3200                 current_view = 0;
3201                 display[current_view] = view;
3202         }
3204         /* No parent signals that this is the first loaded view. */
3205         if (prev && view != prev) {
3206                 view->parent = prev;
3207         }
3209         /* Resize the view when switching between split- and full-screen,
3210          * or when switching between two different full-screen views. */
3211         if (nviews != displayed_views() ||
3212             (nviews == 1 && base_view != display[0]))
3213                 resize_display();
3215         if (view->ops->open) {
3216                 if (view->pipe)
3217                         end_update(view, TRUE);
3218                 if (!view->ops->open(view)) {
3219                         report("Failed to load %s view", view->name);
3220                         return;
3221                 }
3222                 restore_view_position(view);
3224         } else if ((reload || strcmp(view->vid, view->id)) &&
3225                    !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
3226                 report("Failed to load %s view", view->name);
3227                 return;
3228         }
3230         if (split && prev->lineno - prev->offset >= prev->height) {
3231                 /* Take the title line into account. */
3232                 int lines = prev->lineno - prev->offset - prev->height + 1;
3234                 /* Scroll the view that was split if the current line is
3235                  * outside the new limited view. */
3236                 do_scroll_view(prev, lines);
3237         }
3239         if (prev && view != prev) {
3240                 if (split) {
3241                         /* "Blur" the previous view. */
3242                         update_view_title(prev);
3243                 }
3244         }
3246         if (view->pipe && view->lines == 0) {
3247                 /* Clear the old view and let the incremental updating refill
3248                  * the screen. */
3249                 werase(view->win);
3250                 view->p_restore = flags & (OPEN_RELOAD | OPEN_REFRESH);
3251                 report("");
3252         } else if (view_is_displayed(view)) {
3253                 redraw_view(view);
3254                 report("");
3255         }
3258 static void
3259 open_external_viewer(const char *argv[], const char *dir)
3261         def_prog_mode();           /* save current tty modes */
3262         endwin();                  /* restore original tty modes */
3263         run_io_fg(argv, dir);
3264         fprintf(stderr, "Press Enter to continue");
3265         getc(opt_tty);
3266         reset_prog_mode();
3267         redraw_display(TRUE);
3270 static void
3271 open_mergetool(const char *file)
3273         const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
3275         open_external_viewer(mergetool_argv, opt_cdup);
3278 static void
3279 open_editor(bool from_root, const char *file)
3281         const char *editor_argv[] = { "vi", file, NULL };
3282         const char *editor;
3284         editor = getenv("GIT_EDITOR");
3285         if (!editor && *opt_editor)
3286                 editor = opt_editor;
3287         if (!editor)
3288                 editor = getenv("VISUAL");
3289         if (!editor)
3290                 editor = getenv("EDITOR");
3291         if (!editor)
3292                 editor = "vi";
3294         editor_argv[0] = editor;
3295         open_external_viewer(editor_argv, from_root ? opt_cdup : NULL);
3298 static void
3299 open_run_request(enum request request)
3301         struct run_request *req = get_run_request(request);
3302         const char *argv[ARRAY_SIZE(req->argv)] = { NULL };
3304         if (!req) {
3305                 report("Unknown run request");
3306                 return;
3307         }
3309         if (format_argv(argv, req->argv, FORMAT_ALL))
3310                 open_external_viewer(argv, NULL);
3311         free_argv(argv);
3314 /*
3315  * User request switch noodle
3316  */
3318 static int
3319 view_driver(struct view *view, enum request request)
3321         int i;
3323         if (request == REQ_NONE)
3324                 return TRUE;
3326         if (request > REQ_NONE) {
3327                 open_run_request(request);
3328                 /* FIXME: When all views can refresh always do this. */
3329                 if (view == VIEW(REQ_VIEW_STATUS) ||
3330                     view == VIEW(REQ_VIEW_MAIN) ||
3331                     view == VIEW(REQ_VIEW_LOG) ||
3332                     view == VIEW(REQ_VIEW_BRANCH) ||
3333                     view == VIEW(REQ_VIEW_STAGE))
3334                         request = REQ_REFRESH;
3335                 else
3336                         return TRUE;
3337         }
3339         if (view && view->lines) {
3340                 request = view->ops->request(view, request, &view->line[view->lineno]);
3341                 if (request == REQ_NONE)
3342                         return TRUE;
3343         }
3345         switch (request) {
3346         case REQ_MOVE_UP:
3347         case REQ_MOVE_DOWN:
3348         case REQ_MOVE_PAGE_UP:
3349         case REQ_MOVE_PAGE_DOWN:
3350         case REQ_MOVE_FIRST_LINE:
3351         case REQ_MOVE_LAST_LINE:
3352                 move_view(view, request);
3353                 break;
3355         case REQ_SCROLL_LEFT:
3356         case REQ_SCROLL_RIGHT:
3357         case REQ_SCROLL_LINE_DOWN:
3358         case REQ_SCROLL_LINE_UP:
3359         case REQ_SCROLL_PAGE_DOWN:
3360         case REQ_SCROLL_PAGE_UP:
3361                 scroll_view(view, request);
3362                 break;
3364         case REQ_VIEW_BLAME:
3365                 if (!opt_file[0]) {
3366                         report("No file chosen, press %s to open tree view",
3367                                get_key(view->keymap, REQ_VIEW_TREE));
3368                         break;
3369                 }
3370                 open_view(view, request, OPEN_DEFAULT);
3371                 break;
3373         case REQ_VIEW_BLOB:
3374                 if (!ref_blob[0]) {
3375                         report("No file chosen, press %s to open tree view",
3376                                get_key(view->keymap, REQ_VIEW_TREE));
3377                         break;
3378                 }
3379                 open_view(view, request, OPEN_DEFAULT);
3380                 break;
3382         case REQ_VIEW_PAGER:
3383                 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3384                         report("No pager content, press %s to run command from prompt",
3385                                get_key(view->keymap, REQ_PROMPT));
3386                         break;
3387                 }
3388                 open_view(view, request, OPEN_DEFAULT);
3389                 break;
3391         case REQ_VIEW_STAGE:
3392                 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3393                         report("No stage content, press %s to open the status view and choose file",
3394                                get_key(view->keymap, REQ_VIEW_STATUS));
3395                         break;
3396                 }
3397                 open_view(view, request, OPEN_DEFAULT);
3398                 break;
3400         case REQ_VIEW_STATUS:
3401                 if (opt_is_inside_work_tree == FALSE) {
3402                         report("The status view requires a working tree");
3403                         break;
3404                 }
3405                 open_view(view, request, OPEN_DEFAULT);
3406                 break;
3408         case REQ_VIEW_MAIN:
3409         case REQ_VIEW_DIFF:
3410         case REQ_VIEW_LOG:
3411         case REQ_VIEW_TREE:
3412         case REQ_VIEW_HELP:
3413         case REQ_VIEW_BRANCH:
3414                 open_view(view, request, OPEN_DEFAULT);
3415                 break;
3417         case REQ_NEXT:
3418         case REQ_PREVIOUS:
3419                 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3421                 if ((view == VIEW(REQ_VIEW_DIFF) &&
3422                      view->parent == VIEW(REQ_VIEW_MAIN)) ||
3423                    (view == VIEW(REQ_VIEW_DIFF) &&
3424                      view->parent == VIEW(REQ_VIEW_BLAME)) ||
3425                    (view == VIEW(REQ_VIEW_STAGE) &&
3426                      view->parent == VIEW(REQ_VIEW_STATUS)) ||
3427                    (view == VIEW(REQ_VIEW_BLOB) &&
3428                      view->parent == VIEW(REQ_VIEW_TREE)) ||
3429                    (view == VIEW(REQ_VIEW_MAIN) &&
3430                      view->parent == VIEW(REQ_VIEW_BRANCH))) {
3431                         int line;
3433                         view = view->parent;
3434                         line = view->lineno;
3435                         move_view(view, request);
3436                         if (view_is_displayed(view))
3437                                 update_view_title(view);
3438                         if (line != view->lineno)
3439                                 view->ops->request(view, REQ_ENTER,
3440                                                    &view->line[view->lineno]);
3442                 } else {
3443                         move_view(view, request);
3444                 }
3445                 break;
3447         case REQ_VIEW_NEXT:
3448         {
3449                 int nviews = displayed_views();
3450                 int next_view = (current_view + 1) % nviews;
3452                 if (next_view == current_view) {
3453                         report("Only one view is displayed");
3454                         break;
3455                 }
3457                 current_view = next_view;
3458                 /* Blur out the title of the previous view. */
3459                 update_view_title(view);
3460                 report("");
3461                 break;
3462         }
3463         case REQ_REFRESH:
3464                 report("Refreshing is not yet supported for the %s view", view->name);
3465                 break;
3467         case REQ_MAXIMIZE:
3468                 if (displayed_views() == 2)
3469                         maximize_view(view);
3470                 break;
3472         case REQ_OPTIONS:
3473                 open_option_menu();
3474                 break;
3476         case REQ_TOGGLE_LINENO:
3477                 toggle_view_option(&opt_line_number, "line numbers");
3478                 break;
3480         case REQ_TOGGLE_DATE:
3481                 toggle_date_option(&opt_date);
3482                 break;
3484         case REQ_TOGGLE_AUTHOR:
3485                 toggle_view_option(&opt_author, "author display");
3486                 break;
3488         case REQ_TOGGLE_REV_GRAPH:
3489                 toggle_view_option(&opt_rev_graph, "revision graph display");
3490                 break;
3492         case REQ_TOGGLE_REFS:
3493                 toggle_view_option(&opt_show_refs, "reference display");
3494                 break;
3496         case REQ_TOGGLE_SORT_FIELD:
3497         case REQ_TOGGLE_SORT_ORDER:
3498                 report("Sorting is not yet supported for the %s view", view->name);
3499                 break;
3501         case REQ_SEARCH:
3502         case REQ_SEARCH_BACK:
3503                 search_view(view, request);
3504                 break;
3506         case REQ_FIND_NEXT:
3507         case REQ_FIND_PREV:
3508                 find_next(view, request);
3509                 break;
3511         case REQ_STOP_LOADING:
3512                 for (i = 0; i < ARRAY_SIZE(views); i++) {
3513                         view = &views[i];
3514                         if (view->pipe)
3515                                 report("Stopped loading the %s view", view->name),
3516                         end_update(view, TRUE);
3517                 }
3518                 break;
3520         case REQ_SHOW_VERSION:
3521                 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3522                 return TRUE;
3524         case REQ_SCREEN_REDRAW:
3525                 redraw_display(TRUE);
3526                 break;
3528         case REQ_EDIT:
3529                 report("Nothing to edit");
3530                 break;
3532         case REQ_ENTER:
3533                 report("Nothing to enter");
3534                 break;
3536         case REQ_VIEW_CLOSE:
3537                 /* XXX: Mark closed views by letting view->parent point to the
3538                  * view itself. Parents to closed view should never be
3539                  * followed. */
3540                 if (view->parent &&
3541                     view->parent->parent != view->parent) {
3542                         maximize_view(view->parent);
3543                         view->parent = view;
3544                         break;
3545                 }
3546                 /* Fall-through */
3547         case REQ_QUIT:
3548                 return FALSE;
3550         default:
3551                 report("Unknown key, press %s for help",
3552                        get_key(view->keymap, REQ_VIEW_HELP));
3553                 return TRUE;
3554         }
3556         return TRUE;
3560 /*
3561  * View backend utilities
3562  */
3564 enum sort_field {
3565         ORDERBY_NAME,
3566         ORDERBY_DATE,
3567         ORDERBY_AUTHOR,
3568 };
3570 struct sort_state {
3571         const enum sort_field *fields;
3572         size_t size, current;
3573         bool reverse;
3574 };
3576 #define SORT_STATE(fields) { fields, ARRAY_SIZE(fields), 0 }
3577 #define get_sort_field(state) ((state).fields[(state).current])
3578 #define sort_order(state, result) ((state).reverse ? -(result) : (result))
3580 static void
3581 sort_view(struct view *view, enum request request, struct sort_state *state,
3582           int (*compare)(const void *, const void *))
3584         switch (request) {
3585         case REQ_TOGGLE_SORT_FIELD:
3586                 state->current = (state->current + 1) % state->size;
3587                 break;
3589         case REQ_TOGGLE_SORT_ORDER:
3590                 state->reverse = !state->reverse;
3591                 break;
3592         default:
3593                 die("Not a sort request");
3594         }
3596         qsort(view->line, view->lines, sizeof(*view->line), compare);
3597         redraw_view(view);
3600 DEFINE_ALLOCATOR(realloc_authors, const char *, 256)
3602 /* Small author cache to reduce memory consumption. It uses binary
3603  * search to lookup or find place to position new entries. No entries
3604  * are ever freed. */
3605 static const char *
3606 get_author(const char *name)
3608         static const char **authors;
3609         static size_t authors_size;
3610         int from = 0, to = authors_size - 1;
3612         while (from <= to) {
3613                 size_t pos = (to + from) / 2;
3614                 int cmp = strcmp(name, authors[pos]);
3616                 if (!cmp)
3617                         return authors[pos];
3619                 if (cmp < 0)
3620                         to = pos - 1;
3621                 else
3622                         from = pos + 1;
3623         }
3625         if (!realloc_authors(&authors, authors_size, 1))
3626                 return NULL;
3627         name = strdup(name);
3628         if (!name)
3629                 return NULL;
3631         memmove(authors + from + 1, authors + from, (authors_size - from) * sizeof(*authors));
3632         authors[from] = name;
3633         authors_size++;
3635         return name;
3638 static void
3639 parse_timezone(time_t *time, const char *zone)
3641         long tz;
3643         tz  = ('0' - zone[1]) * 60 * 60 * 10;
3644         tz += ('0' - zone[2]) * 60 * 60;
3645         tz += ('0' - zone[3]) * 60;
3646         tz += ('0' - zone[4]);
3648         if (zone[0] == '-')
3649                 tz = -tz;
3651         *time -= tz;
3654 /* Parse author lines where the name may be empty:
3655  *      author  <email@address.tld> 1138474660 +0100
3656  */
3657 static void
3658 parse_author_line(char *ident, const char **author, time_t *time)
3660         char *nameend = strchr(ident, '<');
3661         char *emailend = strchr(ident, '>');
3663         if (nameend && emailend)
3664                 *nameend = *emailend = 0;
3665         ident = chomp_string(ident);
3666         if (!*ident) {
3667                 if (nameend)
3668                         ident = chomp_string(nameend + 1);
3669                 if (!*ident)
3670                         ident = "Unknown";
3671         }
3673         *author = get_author(ident);
3675         /* Parse epoch and timezone */
3676         if (emailend && emailend[1] == ' ') {
3677                 char *secs = emailend + 2;
3678                 char *zone = strchr(secs, ' ');
3680                 *time = (time_t) atol(secs);
3682                 if (zone && strlen(zone) == STRING_SIZE(" +0700"))
3683                         parse_timezone(time, zone + 1);
3684         }
3687 static bool
3688 open_commit_parent_menu(char buf[SIZEOF_STR], int *parents)
3690         char rev[SIZEOF_REV];
3691         const char *revlist_argv[] = {
3692                 "git", "log", "--no-color", "-1", "--pretty=format:%s", rev, NULL
3693         };
3694         struct menu_item *items;
3695         char text[SIZEOF_STR];
3696         bool ok = TRUE;
3697         int i;
3699         items = calloc(*parents + 1, sizeof(*items));
3700         if (!items)
3701                 return FALSE;
3703         for (i = 0; i < *parents; i++) {
3704                 string_copy_rev(rev, &buf[SIZEOF_REV * i]);
3705                 if (!run_io_buf(revlist_argv, text, sizeof(text)) ||
3706                     !(items[i].text = strdup(text))) {
3707                         ok = FALSE;
3708                         break;
3709                 }
3710         }
3712         if (ok) {
3713                 *parents = 0;
3714                 ok = prompt_menu("Select parent", items, parents);
3715         }
3716         for (i = 0; items[i].text; i++)
3717                 free((char *) items[i].text);
3718         free(items);
3719         return ok;
3722 static bool
3723 select_commit_parent(const char *id, char rev[SIZEOF_REV], const char *path)
3725         char buf[SIZEOF_STR * 4];
3726         const char *revlist_argv[] = {
3727                 "git", "log", "--no-color", "-1",
3728                         "--pretty=format:%P", id, "--", path, NULL
3729         };
3730         int parents;
3732         if (!run_io_buf(revlist_argv, buf, sizeof(buf)) ||
3733             (parents = strlen(buf) / 40) < 0) {
3734                 report("Failed to get parent information");
3735                 return FALSE;
3737         } else if (parents == 0) {
3738                 if (path)
3739                         report("Path '%s' does not exist in the parent", path);
3740                 else
3741                         report("The selected commit has no parents");
3742                 return FALSE;
3743         }
3745         if (parents > 1 && !open_commit_parent_menu(buf, &parents))
3746                 return FALSE;
3748         string_copy_rev(rev, &buf[41 * parents]);
3749         return TRUE;
3752 /*
3753  * Pager backend
3754  */
3756 static bool
3757 pager_draw(struct view *view, struct line *line, unsigned int lineno)
3759         char text[SIZEOF_STR];
3761         if (opt_line_number && draw_lineno(view, lineno))
3762                 return TRUE;
3764         string_expand(text, sizeof(text), line->data, opt_tab_size);
3765         draw_text(view, line->type, text, TRUE);
3766         return TRUE;
3769 static bool
3770 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
3772         const char *describe_argv[] = { "git", "describe", commit_id, NULL };
3773         char ref[SIZEOF_STR];
3775         if (!run_io_buf(describe_argv, ref, sizeof(ref)) || !*ref)
3776                 return TRUE;
3778         /* This is the only fatal call, since it can "corrupt" the buffer. */
3779         if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
3780                 return FALSE;
3782         return TRUE;
3785 static void
3786 add_pager_refs(struct view *view, struct line *line)
3788         char buf[SIZEOF_STR];
3789         char *commit_id = (char *)line->data + STRING_SIZE("commit ");
3790         struct ref_list *list;
3791         size_t bufpos = 0, i;
3792         const char *sep = "Refs: ";
3793         bool is_tag = FALSE;
3795         assert(line->type == LINE_COMMIT);
3797         list = get_ref_list(commit_id);
3798         if (!list) {
3799                 if (view == VIEW(REQ_VIEW_DIFF))
3800                         goto try_add_describe_ref;
3801                 return;
3802         }
3804         for (i = 0; i < list->size; i++) {
3805                 struct ref *ref = list->refs[i];
3806                 const char *fmt = ref->tag    ? "%s[%s]" :
3807                                   ref->remote ? "%s<%s>" : "%s%s";
3809                 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
3810                         return;
3811                 sep = ", ";
3812                 if (ref->tag)
3813                         is_tag = TRUE;
3814         }
3816         if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
3817 try_add_describe_ref:
3818                 /* Add <tag>-g<commit_id> "fake" reference. */
3819                 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
3820                         return;
3821         }
3823         if (bufpos == 0)
3824                 return;
3826         add_line_text(view, buf, LINE_PP_REFS);
3829 static bool
3830 pager_read(struct view *view, char *data)
3832         struct line *line;
3834         if (!data)
3835                 return TRUE;
3837         line = add_line_text(view, data, get_line_type(data));
3838         if (!line)
3839                 return FALSE;
3841         if (line->type == LINE_COMMIT &&
3842             (view == VIEW(REQ_VIEW_DIFF) ||
3843              view == VIEW(REQ_VIEW_LOG)))
3844                 add_pager_refs(view, line);
3846         return TRUE;
3849 static enum request
3850 pager_request(struct view *view, enum request request, struct line *line)
3852         int split = 0;
3854         if (request != REQ_ENTER)
3855                 return request;
3857         if (line->type == LINE_COMMIT &&
3858            (view == VIEW(REQ_VIEW_LOG) ||
3859             view == VIEW(REQ_VIEW_PAGER))) {
3860                 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
3861                 split = 1;
3862         }
3864         /* Always scroll the view even if it was split. That way
3865          * you can use Enter to scroll through the log view and
3866          * split open each commit diff. */
3867         scroll_view(view, REQ_SCROLL_LINE_DOWN);
3869         /* FIXME: A minor workaround. Scrolling the view will call report("")
3870          * but if we are scrolling a non-current view this won't properly
3871          * update the view title. */
3872         if (split)
3873                 update_view_title(view);
3875         return REQ_NONE;
3878 static bool
3879 pager_grep(struct view *view, struct line *line)
3881         const char *text[] = { line->data, NULL };
3883         return grep_text(view, text);
3886 static void
3887 pager_select(struct view *view, struct line *line)
3889         if (line->type == LINE_COMMIT) {
3890                 char *text = (char *)line->data + STRING_SIZE("commit ");
3892                 if (view != VIEW(REQ_VIEW_PAGER))
3893                         string_copy_rev(view->ref, text);
3894                 string_copy_rev(ref_commit, text);
3895         }
3898 static struct view_ops pager_ops = {
3899         "line",
3900         NULL,
3901         NULL,
3902         pager_read,
3903         pager_draw,
3904         pager_request,
3905         pager_grep,
3906         pager_select,
3907 };
3909 static const char *log_argv[SIZEOF_ARG] = {
3910         "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
3911 };
3913 static enum request
3914 log_request(struct view *view, enum request request, struct line *line)
3916         switch (request) {
3917         case REQ_REFRESH:
3918                 load_refs();
3919                 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
3920                 return REQ_NONE;
3921         default:
3922                 return pager_request(view, request, line);
3923         }
3926 static struct view_ops log_ops = {
3927         "line",
3928         log_argv,
3929         NULL,
3930         pager_read,
3931         pager_draw,
3932         log_request,
3933         pager_grep,
3934         pager_select,
3935 };
3937 static const char *diff_argv[SIZEOF_ARG] = {
3938         "git", "show", "--pretty=fuller", "--no-color", "--root",
3939                 "--patch-with-stat", "--find-copies-harder", "-C", "%(commit)", NULL
3940 };
3942 static struct view_ops diff_ops = {
3943         "line",
3944         diff_argv,
3945         NULL,
3946         pager_read,
3947         pager_draw,
3948         pager_request,
3949         pager_grep,
3950         pager_select,
3951 };
3953 /*
3954  * Help backend
3955  */
3957 static bool help_keymap_hidden[ARRAY_SIZE(keymap_table)];
3959 static char *
3960 help_name(char buf[SIZEOF_STR], const char *name, size_t namelen)
3962         int bufpos;
3964         for (bufpos = 0; bufpos <= namelen; bufpos++) {
3965                 buf[bufpos] = tolower(name[bufpos]);
3966                 if (buf[bufpos] == '_')
3967                         buf[bufpos] = '-';
3968         }
3970         buf[bufpos] = 0;
3971         return buf;
3974 #define help_keymap_name(buf, keymap) \
3975         help_name(buf, keymap_table[keymap].name, keymap_table[keymap].namelen)
3977 static bool
3978 help_open_keymap_title(struct view *view, enum keymap keymap)
3980         char buf[SIZEOF_STR];
3981         struct line *line;
3983         line = add_line_format(view, LINE_HELP_KEYMAP, "[%c] %s bindings",
3984                                help_keymap_hidden[keymap] ? '+' : '-',
3985                                help_keymap_name(buf, keymap));
3986         if (line)
3987                 line->other = keymap;
3989         return help_keymap_hidden[keymap];
3992 static void
3993 help_open_keymap(struct view *view, enum keymap keymap)
3995         const char *group = NULL;
3996         char buf[SIZEOF_STR];
3997         size_t bufpos;
3998         bool add_title = TRUE;
3999         int i;
4001         for (i = 0; i < ARRAY_SIZE(req_info); i++) {
4002                 const char *key = NULL;
4004                 if (req_info[i].request == REQ_NONE)
4005                         continue;
4007                 if (!req_info[i].request) {
4008                         group = req_info[i].help;
4009                         continue;
4010                 }
4012                 key = get_keys(keymap, req_info[i].request, TRUE);
4013                 if (!key || !*key)
4014                         continue;
4016                 if (add_title && help_open_keymap_title(view, keymap))
4017                         return;
4018                 add_title = false;
4020                 if (group) {
4021                         add_line_text(view, group, LINE_HELP_GROUP);
4022                         group = NULL;
4023                 }
4025                 add_line_format(view, LINE_DEFAULT, "    %-25s %-20s %s", key,
4026                                 help_name(buf, req_info[i].name, req_info[i].namelen),
4027                                 req_info[i].help);
4028         }
4030         group = "External commands:";
4032         for (i = 0; i < run_requests; i++) {
4033                 struct run_request *req = get_run_request(REQ_NONE + i + 1);
4034                 const char *key;
4035                 int argc;
4037                 if (!req || req->keymap != keymap)
4038                         continue;
4040                 key = get_key_name(req->key);
4041                 if (!*key)
4042                         key = "(no key defined)";
4044                 if (add_title && help_open_keymap_title(view, keymap))
4045                         return;
4046                 if (group) {
4047                         add_line_text(view, group, LINE_HELP_GROUP);
4048                         group = NULL;
4049                 }
4051                 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
4052                         if (!string_format_from(buf, &bufpos, "%s%s",
4053                                                 argc ? " " : "", req->argv[argc]))
4054                                 return;
4056                 add_line_format(view, LINE_DEFAULT, "    %-25s `%s`", key, buf);
4057         }
4060 static bool
4061 help_open(struct view *view)
4063         enum keymap keymap;
4065         reset_view(view);
4066         add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
4067         add_line_text(view, "", LINE_DEFAULT);
4069         for (keymap = 0; keymap < ARRAY_SIZE(keymap_table); keymap++)
4070                 help_open_keymap(view, keymap);
4072         return TRUE;
4075 static enum request
4076 help_request(struct view *view, enum request request, struct line *line)
4078         switch (request) {
4079         case REQ_ENTER:
4080                 if (line->type == LINE_HELP_KEYMAP) {
4081                         help_keymap_hidden[line->other] =
4082                                 !help_keymap_hidden[line->other];
4083                         view->p_restore = TRUE;
4084                         open_view(view, REQ_VIEW_HELP, OPEN_REFRESH);
4085                 }
4087                 return REQ_NONE;
4088         default:
4089                 return pager_request(view, request, line);
4090         }
4093 static struct view_ops help_ops = {
4094         "line",
4095         NULL,
4096         help_open,
4097         NULL,
4098         pager_draw,
4099         help_request,
4100         pager_grep,
4101         pager_select,
4102 };
4105 /*
4106  * Tree backend
4107  */
4109 struct tree_stack_entry {
4110         struct tree_stack_entry *prev;  /* Entry below this in the stack */
4111         unsigned long lineno;           /* Line number to restore */
4112         char *name;                     /* Position of name in opt_path */
4113 };
4115 /* The top of the path stack. */
4116 static struct tree_stack_entry *tree_stack = NULL;
4117 unsigned long tree_lineno = 0;
4119 static void
4120 pop_tree_stack_entry(void)
4122         struct tree_stack_entry *entry = tree_stack;
4124         tree_lineno = entry->lineno;
4125         entry->name[0] = 0;
4126         tree_stack = entry->prev;
4127         free(entry);
4130 static void
4131 push_tree_stack_entry(const char *name, unsigned long lineno)
4133         struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
4134         size_t pathlen = strlen(opt_path);
4136         if (!entry)
4137                 return;
4139         entry->prev = tree_stack;
4140         entry->name = opt_path + pathlen;
4141         tree_stack = entry;
4143         if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
4144                 pop_tree_stack_entry();
4145                 return;
4146         }
4148         /* Move the current line to the first tree entry. */
4149         tree_lineno = 1;
4150         entry->lineno = lineno;
4153 /* Parse output from git-ls-tree(1):
4154  *
4155  * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
4156  */
4158 #define SIZEOF_TREE_ATTR \
4159         STRING_SIZE("100644 blob f931e1d229c3e185caad4449bf5b66ed72462657\t")
4161 #define SIZEOF_TREE_MODE \
4162         STRING_SIZE("100644 ")
4164 #define TREE_ID_OFFSET \
4165         STRING_SIZE("100644 blob ")
4167 struct tree_entry {
4168         char id[SIZEOF_REV];
4169         mode_t mode;
4170         time_t time;                    /* Date from the author ident. */
4171         const char *author;             /* Author of the commit. */
4172         char name[1];
4173 };
4175 static const char *
4176 tree_path(const struct line *line)
4178         return ((struct tree_entry *) line->data)->name;
4181 static int
4182 tree_compare_entry(const struct line *line1, const struct line *line2)
4184         if (line1->type != line2->type)
4185                 return line1->type == LINE_TREE_DIR ? -1 : 1;
4186         return strcmp(tree_path(line1), tree_path(line2));
4189 static const enum sort_field tree_sort_fields[] = {
4190         ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
4191 };
4192 static struct sort_state tree_sort_state = SORT_STATE(tree_sort_fields);
4194 static int
4195 tree_compare(const void *l1, const void *l2)
4197         const struct line *line1 = (const struct line *) l1;
4198         const struct line *line2 = (const struct line *) l2;
4199         const struct tree_entry *entry1 = ((const struct line *) l1)->data;
4200         const struct tree_entry *entry2 = ((const struct line *) l2)->data;
4202         if (line1->type == LINE_TREE_HEAD)
4203                 return -1;
4204         if (line2->type == LINE_TREE_HEAD)
4205                 return 1;
4207         switch (get_sort_field(tree_sort_state)) {
4208         case ORDERBY_DATE:
4209                 return sort_order(tree_sort_state, entry1->time - entry2->time);
4211         case ORDERBY_AUTHOR:
4212                 return sort_order(tree_sort_state, strcmp(entry1->author, entry2->author));
4214         case ORDERBY_NAME:
4215         default:
4216                 return sort_order(tree_sort_state, tree_compare_entry(line1, line2));
4217         }
4221 static struct line *
4222 tree_entry(struct view *view, enum line_type type, const char *path,
4223            const char *mode, const char *id)
4225         struct tree_entry *entry = calloc(1, sizeof(*entry) + strlen(path));
4226         struct line *line = entry ? add_line_data(view, entry, type) : NULL;
4228         if (!entry || !line) {
4229                 free(entry);
4230                 return NULL;
4231         }
4233         strncpy(entry->name, path, strlen(path));
4234         if (mode)
4235                 entry->mode = strtoul(mode, NULL, 8);
4236         if (id)
4237                 string_copy_rev(entry->id, id);
4239         return line;
4242 static bool
4243 tree_read_date(struct view *view, char *text, bool *read_date)
4245         static const char *author_name;
4246         static time_t author_time;
4248         if (!text && *read_date) {
4249                 *read_date = FALSE;
4250                 return TRUE;
4252         } else if (!text) {
4253                 char *path = *opt_path ? opt_path : ".";
4254                 /* Find next entry to process */
4255                 const char *log_file[] = {
4256                         "git", "log", "--no-color", "--pretty=raw",
4257                                 "--cc", "--raw", view->id, "--", path, NULL
4258                 };
4259                 struct io io = {};
4261                 if (!view->lines) {
4262                         tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL);
4263                         report("Tree is empty");
4264                         return TRUE;
4265                 }
4267                 if (!run_io_rd(&io, log_file, opt_cdup, FORMAT_NONE)) {
4268                         report("Failed to load tree data");
4269                         return TRUE;
4270                 }
4272                 done_io(view->pipe);
4273                 view->io = io;
4274                 *read_date = TRUE;
4275                 return FALSE;
4277         } else if (*text == 'a' && get_line_type(text) == LINE_AUTHOR) {
4278                 parse_author_line(text + STRING_SIZE("author "),
4279                                   &author_name, &author_time);
4281         } else if (*text == ':') {
4282                 char *pos;
4283                 size_t annotated = 1;
4284                 size_t i;
4286                 pos = strchr(text, '\t');
4287                 if (!pos)
4288                         return TRUE;
4289                 text = pos + 1;
4290                 if (*opt_path && !strncmp(text, opt_path, strlen(opt_path)))
4291                         text += strlen(opt_path);
4292                 pos = strchr(text, '/');
4293                 if (pos)
4294                         *pos = 0;
4296                 for (i = 1; i < view->lines; i++) {
4297                         struct line *line = &view->line[i];
4298                         struct tree_entry *entry = line->data;
4300                         annotated += !!entry->author;
4301                         if (entry->author || strcmp(entry->name, text))
4302                                 continue;
4304                         entry->author = author_name;
4305                         entry->time = author_time;
4306                         line->dirty = 1;
4307                         break;
4308                 }
4310                 if (annotated == view->lines)
4311                         kill_io(view->pipe);
4312         }
4313         return TRUE;
4316 static bool
4317 tree_read(struct view *view, char *text)
4319         static bool read_date = FALSE;
4320         struct tree_entry *data;
4321         struct line *entry, *line;
4322         enum line_type type;
4323         size_t textlen = text ? strlen(text) : 0;
4324         char *path = text + SIZEOF_TREE_ATTR;
4326         if (read_date || !text)
4327                 return tree_read_date(view, text, &read_date);
4329         if (textlen <= SIZEOF_TREE_ATTR)
4330                 return FALSE;
4331         if (view->lines == 0 &&
4332             !tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL))
4333                 return FALSE;
4335         /* Strip the path part ... */
4336         if (*opt_path) {
4337                 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
4338                 size_t striplen = strlen(opt_path);
4340                 if (pathlen > striplen)
4341                         memmove(path, path + striplen,
4342                                 pathlen - striplen + 1);
4344                 /* Insert "link" to parent directory. */
4345                 if (view->lines == 1 &&
4346                     !tree_entry(view, LINE_TREE_DIR, "..", "040000", view->ref))
4347                         return FALSE;
4348         }
4350         type = text[SIZEOF_TREE_MODE] == 't' ? LINE_TREE_DIR : LINE_TREE_FILE;
4351         entry = tree_entry(view, type, path, text, text + TREE_ID_OFFSET);
4352         if (!entry)
4353                 return FALSE;
4354         data = entry->data;
4356         /* Skip "Directory ..." and ".." line. */
4357         for (line = &view->line[1 + !!*opt_path]; line < entry; line++) {
4358                 if (tree_compare_entry(line, entry) <= 0)
4359                         continue;
4361                 memmove(line + 1, line, (entry - line) * sizeof(*entry));
4363                 line->data = data;
4364                 line->type = type;
4365                 for (; line <= entry; line++)
4366                         line->dirty = line->cleareol = 1;
4367                 return TRUE;
4368         }
4370         if (tree_lineno > view->lineno) {
4371                 view->lineno = tree_lineno;
4372                 tree_lineno = 0;
4373         }
4375         return TRUE;
4378 static bool
4379 tree_draw(struct view *view, struct line *line, unsigned int lineno)
4381         struct tree_entry *entry = line->data;
4383         if (line->type == LINE_TREE_HEAD) {
4384                 if (draw_text(view, line->type, "Directory path /", TRUE))
4385                         return TRUE;
4386         } else {
4387                 if (draw_mode(view, entry->mode))
4388                         return TRUE;
4390                 if (opt_author && draw_author(view, entry->author))
4391                         return TRUE;
4393                 if (opt_date && draw_date(view, entry->author ? &entry->time : NULL))
4394                         return TRUE;
4395         }
4396         if (draw_text(view, line->type, entry->name, TRUE))
4397                 return TRUE;
4398         return TRUE;
4401 static void
4402 open_blob_editor()
4404         char file[SIZEOF_STR] = "/tmp/tigblob.XXXXXX";
4405         int fd = mkstemp(file);
4407         if (fd == -1)
4408                 report("Failed to create temporary file");
4409         else if (!run_io_append(blob_ops.argv, FORMAT_ALL, fd))
4410                 report("Failed to save blob data to file");
4411         else
4412                 open_editor(FALSE, file);
4413         if (fd != -1)
4414                 unlink(file);
4417 static enum request
4418 tree_request(struct view *view, enum request request, struct line *line)
4420         enum open_flags flags;
4422         switch (request) {
4423         case REQ_VIEW_BLAME:
4424                 if (line->type != LINE_TREE_FILE) {
4425                         report("Blame only supported for files");
4426                         return REQ_NONE;
4427                 }
4429                 string_copy(opt_ref, view->vid);
4430                 return request;
4432         case REQ_EDIT:
4433                 if (line->type != LINE_TREE_FILE) {
4434                         report("Edit only supported for files");
4435                 } else if (!is_head_commit(view->vid)) {
4436                         open_blob_editor();
4437                 } else {
4438                         open_editor(TRUE, opt_file);
4439                 }
4440                 return REQ_NONE;
4442         case REQ_TOGGLE_SORT_FIELD:
4443         case REQ_TOGGLE_SORT_ORDER:
4444                 sort_view(view, request, &tree_sort_state, tree_compare);
4445                 return REQ_NONE;
4447         case REQ_PARENT:
4448                 if (!*opt_path) {
4449                         /* quit view if at top of tree */
4450                         return REQ_VIEW_CLOSE;
4451                 }
4452                 /* fake 'cd  ..' */
4453                 line = &view->line[1];
4454                 break;
4456         case REQ_ENTER:
4457                 break;
4459         default:
4460                 return request;
4461         }
4463         /* Cleanup the stack if the tree view is at a different tree. */
4464         while (!*opt_path && tree_stack)
4465                 pop_tree_stack_entry();
4467         switch (line->type) {
4468         case LINE_TREE_DIR:
4469                 /* Depending on whether it is a subdirectory or parent link
4470                  * mangle the path buffer. */
4471                 if (line == &view->line[1] && *opt_path) {
4472                         pop_tree_stack_entry();
4474                 } else {
4475                         const char *basename = tree_path(line);
4477                         push_tree_stack_entry(basename, view->lineno);
4478                 }
4480                 /* Trees and subtrees share the same ID, so they are not not
4481                  * unique like blobs. */
4482                 flags = OPEN_RELOAD;
4483                 request = REQ_VIEW_TREE;
4484                 break;
4486         case LINE_TREE_FILE:
4487                 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4488                 request = REQ_VIEW_BLOB;
4489                 break;
4491         default:
4492                 return REQ_NONE;
4493         }
4495         open_view(view, request, flags);
4496         if (request == REQ_VIEW_TREE)
4497                 view->lineno = tree_lineno;
4499         return REQ_NONE;
4502 static bool
4503 tree_grep(struct view *view, struct line *line)
4505         struct tree_entry *entry = line->data;
4506         const char *text[] = {
4507                 entry->name,
4508                 opt_author ? entry->author : "",
4509                 opt_date ? mkdate(&entry->time) : "",
4510                 NULL
4511         };
4513         return grep_text(view, text);
4516 static void
4517 tree_select(struct view *view, struct line *line)
4519         struct tree_entry *entry = line->data;
4521         if (line->type == LINE_TREE_FILE) {
4522                 string_copy_rev(ref_blob, entry->id);
4523                 string_format(opt_file, "%s%s", opt_path, tree_path(line));
4525         } else if (line->type != LINE_TREE_DIR) {
4526                 return;
4527         }
4529         string_copy_rev(view->ref, entry->id);
4532 static bool
4533 tree_prepare(struct view *view)
4535         if (view->lines == 0 && opt_prefix[0]) {
4536                 char *pos = opt_prefix;
4538                 while (pos && *pos) {
4539                         char *end = strchr(pos, '/');
4541                         if (end)
4542                                 *end = 0;
4543                         push_tree_stack_entry(pos, 0);
4544                         pos = end;
4545                         if (end) {
4546                                 *end = '/';
4547                                 pos++;
4548                         }
4549                 }
4551         } else if (strcmp(view->vid, view->id)) {
4552                 opt_path[0] = 0;
4553         }
4555         return init_io_rd(&view->io, view->ops->argv, opt_cdup, FORMAT_ALL);
4558 static const char *tree_argv[SIZEOF_ARG] = {
4559         "git", "ls-tree", "%(commit)", "%(directory)", NULL
4560 };
4562 static struct view_ops tree_ops = {
4563         "file",
4564         tree_argv,
4565         NULL,
4566         tree_read,
4567         tree_draw,
4568         tree_request,
4569         tree_grep,
4570         tree_select,
4571         tree_prepare,
4572 };
4574 static bool
4575 blob_read(struct view *view, char *line)
4577         if (!line)
4578                 return TRUE;
4579         return add_line_text(view, line, LINE_DEFAULT) != NULL;
4582 static enum request
4583 blob_request(struct view *view, enum request request, struct line *line)
4585         switch (request) {
4586         case REQ_EDIT:
4587                 open_blob_editor();
4588                 return REQ_NONE;
4589         default:
4590                 return pager_request(view, request, line);
4591         }
4594 static const char *blob_argv[SIZEOF_ARG] = {
4595         "git", "cat-file", "blob", "%(blob)", NULL
4596 };
4598 static struct view_ops blob_ops = {
4599         "line",
4600         blob_argv,
4601         NULL,
4602         blob_read,
4603         pager_draw,
4604         blob_request,
4605         pager_grep,
4606         pager_select,
4607 };
4609 /*
4610  * Blame backend
4611  *
4612  * Loading the blame view is a two phase job:
4613  *
4614  *  1. File content is read either using opt_file from the
4615  *     filesystem or using git-cat-file.
4616  *  2. Then blame information is incrementally added by
4617  *     reading output from git-blame.
4618  */
4620 static const char *blame_head_argv[] = {
4621         "git", "blame", "--incremental", "--", "%(file)", NULL
4622 };
4624 static const char *blame_ref_argv[] = {
4625         "git", "blame", "--incremental", "%(ref)", "--", "%(file)", NULL
4626 };
4628 static const char *blame_cat_file_argv[] = {
4629         "git", "cat-file", "blob", "%(ref):%(file)", NULL
4630 };
4632 struct blame_commit {
4633         char id[SIZEOF_REV];            /* SHA1 ID. */
4634         char title[128];                /* First line of the commit message. */
4635         const char *author;             /* Author of the commit. */
4636         time_t time;                    /* Date from the author ident. */
4637         char filename[128];             /* Name of file. */
4638         bool has_previous;              /* Was a "previous" line detected. */
4639 };
4641 struct blame {
4642         struct blame_commit *commit;
4643         unsigned long lineno;
4644         char text[1];
4645 };
4647 static bool
4648 blame_open(struct view *view)
4650         char path[SIZEOF_STR];
4652         if (!view->parent && *opt_prefix) {
4653                 string_copy(path, opt_file);
4654                 if (!string_format(opt_file, "%s%s", opt_prefix, path))
4655                         return FALSE;
4656         }
4658         if (*opt_ref || !io_open(&view->io, "%s%s", opt_cdup, opt_file)) {
4659                 if (!run_io_rd(&view->io, blame_cat_file_argv, opt_cdup, FORMAT_ALL))
4660                         return FALSE;
4661         }
4663         setup_update(view, opt_file);
4664         string_format(view->ref, "%s ...", opt_file);
4666         return TRUE;
4669 static struct blame_commit *
4670 get_blame_commit(struct view *view, const char *id)
4672         size_t i;
4674         for (i = 0; i < view->lines; i++) {
4675                 struct blame *blame = view->line[i].data;
4677                 if (!blame->commit)
4678                         continue;
4680                 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
4681                         return blame->commit;
4682         }
4684         {
4685                 struct blame_commit *commit = calloc(1, sizeof(*commit));
4687                 if (commit)
4688                         string_ncopy(commit->id, id, SIZEOF_REV);
4689                 return commit;
4690         }
4693 static bool
4694 parse_number(const char **posref, size_t *number, size_t min, size_t max)
4696         const char *pos = *posref;
4698         *posref = NULL;
4699         pos = strchr(pos + 1, ' ');
4700         if (!pos || !isdigit(pos[1]))
4701                 return FALSE;
4702         *number = atoi(pos + 1);
4703         if (*number < min || *number > max)
4704                 return FALSE;
4706         *posref = pos;
4707         return TRUE;
4710 static struct blame_commit *
4711 parse_blame_commit(struct view *view, const char *text, int *blamed)
4713         struct blame_commit *commit;
4714         struct blame *blame;
4715         const char *pos = text + SIZEOF_REV - 2;
4716         size_t orig_lineno = 0;
4717         size_t lineno;
4718         size_t group;
4720         if (strlen(text) <= SIZEOF_REV || pos[1] != ' ')
4721                 return NULL;
4723         if (!parse_number(&pos, &orig_lineno, 1, 9999999) ||
4724             !parse_number(&pos, &lineno, 1, view->lines) ||
4725             !parse_number(&pos, &group, 1, view->lines - lineno + 1))
4726                 return NULL;
4728         commit = get_blame_commit(view, text);
4729         if (!commit)
4730                 return NULL;
4732         *blamed += group;
4733         while (group--) {
4734                 struct line *line = &view->line[lineno + group - 1];
4736                 blame = line->data;
4737                 blame->commit = commit;
4738                 blame->lineno = orig_lineno + group - 1;
4739                 line->dirty = 1;
4740         }
4742         return commit;
4745 static bool
4746 blame_read_file(struct view *view, const char *line, bool *read_file)
4748         if (!line) {
4749                 const char **argv = *opt_ref ? blame_ref_argv : blame_head_argv;
4750                 struct io io = {};
4752                 if (view->lines == 0 && !view->parent)
4753                         die("No blame exist for %s", view->vid);
4755                 if (view->lines == 0 || !run_io_rd(&io, argv, opt_cdup, FORMAT_ALL)) {
4756                         report("Failed to load blame data");
4757                         return TRUE;
4758                 }
4760                 done_io(view->pipe);
4761                 view->io = io;
4762                 *read_file = FALSE;
4763                 return FALSE;
4765         } else {
4766                 size_t linelen = strlen(line);
4767                 struct blame *blame = malloc(sizeof(*blame) + linelen);
4769                 if (!blame)
4770                         return FALSE;
4772                 blame->commit = NULL;
4773                 strncpy(blame->text, line, linelen);
4774                 blame->text[linelen] = 0;
4775                 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
4776         }
4779 static bool
4780 match_blame_header(const char *name, char **line)
4782         size_t namelen = strlen(name);
4783         bool matched = !strncmp(name, *line, namelen);
4785         if (matched)
4786                 *line += namelen;
4788         return matched;
4791 static bool
4792 blame_read(struct view *view, char *line)
4794         static struct blame_commit *commit = NULL;
4795         static int blamed = 0;
4796         static bool read_file = TRUE;
4798         if (read_file)
4799                 return blame_read_file(view, line, &read_file);
4801         if (!line) {
4802                 /* Reset all! */
4803                 commit = NULL;
4804                 blamed = 0;
4805                 read_file = TRUE;
4806                 string_format(view->ref, "%s", view->vid);
4807                 if (view_is_displayed(view)) {
4808                         update_view_title(view);
4809                         redraw_view_from(view, 0);
4810                 }
4811                 return TRUE;
4812         }
4814         if (!commit) {
4815                 commit = parse_blame_commit(view, line, &blamed);
4816                 string_format(view->ref, "%s %2d%%", view->vid,
4817                               view->lines ? blamed * 100 / view->lines : 0);
4819         } else if (match_blame_header("author ", &line)) {
4820                 commit->author = get_author(line);
4822         } else if (match_blame_header("author-time ", &line)) {
4823                 commit->time = (time_t) atol(line);
4825         } else if (match_blame_header("author-tz ", &line)) {
4826                 parse_timezone(&commit->time, line);
4828         } else if (match_blame_header("summary ", &line)) {
4829                 string_ncopy(commit->title, line, strlen(line));
4831         } else if (match_blame_header("previous ", &line)) {
4832                 commit->has_previous = TRUE;
4834         } else if (match_blame_header("filename ", &line)) {
4835                 string_ncopy(commit->filename, line, strlen(line));
4836                 commit = NULL;
4837         }
4839         return TRUE;
4842 static bool
4843 blame_draw(struct view *view, struct line *line, unsigned int lineno)
4845         struct blame *blame = line->data;
4846         time_t *time = NULL;
4847         const char *id = NULL, *author = NULL;
4848         char text[SIZEOF_STR];
4850         if (blame->commit && *blame->commit->filename) {
4851                 id = blame->commit->id;
4852                 author = blame->commit->author;
4853                 time = &blame->commit->time;
4854         }
4856         if (opt_date && draw_date(view, time))
4857                 return TRUE;
4859         if (opt_author && draw_author(view, author))
4860                 return TRUE;
4862         if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
4863                 return TRUE;
4865         if (draw_lineno(view, lineno))
4866                 return TRUE;
4868         string_expand(text, sizeof(text), blame->text, opt_tab_size);
4869         draw_text(view, LINE_DEFAULT, text, TRUE);
4870         return TRUE;
4873 static bool
4874 check_blame_commit(struct blame *blame, bool check_null_id)
4876         if (!blame->commit)
4877                 report("Commit data not loaded yet");
4878         else if (check_null_id && !strcmp(blame->commit->id, NULL_ID))
4879                 report("No commit exist for the selected line");
4880         else
4881                 return TRUE;
4882         return FALSE;
4885 static void
4886 setup_blame_parent_line(struct view *view, struct blame *blame)
4888         const char *diff_tree_argv[] = {
4889                 "git", "diff-tree", "-U0", blame->commit->id,
4890                         "--", blame->commit->filename, NULL
4891         };
4892         struct io io = {};
4893         int parent_lineno = -1;
4894         int blamed_lineno = -1;
4895         char *line;
4897         if (!run_io(&io, diff_tree_argv, NULL, IO_RD))
4898                 return;
4900         while ((line = io_get(&io, '\n', TRUE))) {
4901                 if (*line == '@') {
4902                         char *pos = strchr(line, '+');
4904                         parent_lineno = atoi(line + 4);
4905                         if (pos)
4906                                 blamed_lineno = atoi(pos + 1);
4908                 } else if (*line == '+' && parent_lineno != -1) {
4909                         if (blame->lineno == blamed_lineno - 1 &&
4910                             !strcmp(blame->text, line + 1)) {
4911                                 view->lineno = parent_lineno ? parent_lineno - 1 : 0;
4912                                 break;
4913                         }
4914                         blamed_lineno++;
4915                 }
4916         }
4918         done_io(&io);
4921 static enum request
4922 blame_request(struct view *view, enum request request, struct line *line)
4924         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4925         struct blame *blame = line->data;
4927         switch (request) {
4928         case REQ_VIEW_BLAME:
4929                 if (check_blame_commit(blame, TRUE)) {
4930                         string_copy(opt_ref, blame->commit->id);
4931                         string_copy(opt_file, blame->commit->filename);
4932                         if (blame->lineno)
4933                                 view->lineno = blame->lineno;
4934                         open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4935                 }
4936                 break;
4938         case REQ_PARENT:
4939                 if (check_blame_commit(blame, TRUE) &&
4940                     select_commit_parent(blame->commit->id, opt_ref,
4941                                          blame->commit->filename)) {
4942                         string_copy(opt_file, blame->commit->filename);
4943                         setup_blame_parent_line(view, blame);
4944                         open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4945                 }
4946                 break;
4948         case REQ_ENTER:
4949                 if (!check_blame_commit(blame, FALSE))
4950                         break;
4952                 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
4953                     !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
4954                         break;
4956                 if (!strcmp(blame->commit->id, NULL_ID)) {
4957                         struct view *diff = VIEW(REQ_VIEW_DIFF);
4958                         const char *diff_index_argv[] = {
4959                                 "git", "diff-index", "--root", "--patch-with-stat",
4960                                         "-C", "-M", "HEAD", "--", view->vid, NULL
4961                         };
4963                         if (!blame->commit->has_previous) {
4964                                 diff_index_argv[1] = "diff";
4965                                 diff_index_argv[2] = "--no-color";
4966                                 diff_index_argv[6] = "--";
4967                                 diff_index_argv[7] = "/dev/null";
4968                         }
4970                         if (!prepare_update(diff, diff_index_argv, NULL, FORMAT_DASH)) {
4971                                 report("Failed to allocate diff command");
4972                                 break;
4973                         }
4974                         flags |= OPEN_PREPARED;
4975                 }
4977                 open_view(view, REQ_VIEW_DIFF, flags);
4978                 if (VIEW(REQ_VIEW_DIFF)->pipe && !strcmp(blame->commit->id, NULL_ID))
4979                         string_copy_rev(VIEW(REQ_VIEW_DIFF)->ref, NULL_ID);
4980                 break;
4982         default:
4983                 return request;
4984         }
4986         return REQ_NONE;
4989 static bool
4990 blame_grep(struct view *view, struct line *line)
4992         struct blame *blame = line->data;
4993         struct blame_commit *commit = blame->commit;
4994         const char *text[] = {
4995                 blame->text,
4996                 commit ? commit->title : "",
4997                 commit ? commit->id : "",
4998                 commit && opt_author ? commit->author : "",
4999                 commit && opt_date ? mkdate(&commit->time) : "",
5000                 NULL
5001         };
5003         return grep_text(view, text);
5006 static void
5007 blame_select(struct view *view, struct line *line)
5009         struct blame *blame = line->data;
5010         struct blame_commit *commit = blame->commit;
5012         if (!commit)
5013                 return;
5015         if (!strcmp(commit->id, NULL_ID))
5016                 string_ncopy(ref_commit, "HEAD", 4);
5017         else
5018                 string_copy_rev(ref_commit, commit->id);
5021 static struct view_ops blame_ops = {
5022         "line",
5023         NULL,
5024         blame_open,
5025         blame_read,
5026         blame_draw,
5027         blame_request,
5028         blame_grep,
5029         blame_select,
5030 };
5032 /*
5033  * Branch backend
5034  */
5036 struct branch {
5037         const char *author;             /* Author of the last commit. */
5038         time_t time;                    /* Date of the last activity. */
5039         const struct ref *ref;          /* Name and commit ID information. */
5040 };
5042 static const enum sort_field branch_sort_fields[] = {
5043         ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
5044 };
5045 static struct sort_state branch_sort_state = SORT_STATE(branch_sort_fields);
5047 static int
5048 branch_compare(const void *l1, const void *l2)
5050         const struct branch *branch1 = ((const struct line *) l1)->data;
5051         const struct branch *branch2 = ((const struct line *) l2)->data;
5053         switch (get_sort_field(branch_sort_state)) {
5054         case ORDERBY_DATE:
5055                 return sort_order(branch_sort_state, branch1->time - branch2->time);
5057         case ORDERBY_AUTHOR:
5058                 return sort_order(branch_sort_state, strcmp(branch1->author, branch2->author));
5060         case ORDERBY_NAME:
5061         default:
5062                 return sort_order(branch_sort_state, strcmp(branch1->ref->name, branch2->ref->name));
5063         }
5066 static bool
5067 branch_draw(struct view *view, struct line *line, unsigned int lineno)
5069         struct branch *branch = line->data;
5070         enum line_type type = branch->ref->head ? LINE_MAIN_HEAD : LINE_DEFAULT;
5072         if (opt_date && draw_date(view, branch->author ? &branch->time : NULL))
5073                 return TRUE;
5075         if (opt_author && draw_author(view, branch->author))
5076                 return TRUE;
5078         draw_text(view, type, branch->ref->name, TRUE);
5079         return TRUE;
5082 static enum request
5083 branch_request(struct view *view, enum request request, struct line *line)
5085         switch (request) {
5086         case REQ_REFRESH:
5087                 load_refs();
5088                 open_view(view, REQ_VIEW_BRANCH, OPEN_REFRESH);
5089                 return REQ_NONE;
5091         case REQ_TOGGLE_SORT_FIELD:
5092         case REQ_TOGGLE_SORT_ORDER:
5093                 sort_view(view, request, &branch_sort_state, branch_compare);
5094                 return REQ_NONE;
5096         case REQ_ENTER:
5097                 open_view(view, REQ_VIEW_MAIN, OPEN_SPLIT);
5098                 return REQ_NONE;
5100         default:
5101                 return request;
5102         }
5105 static bool
5106 branch_read(struct view *view, char *line)
5108         static char id[SIZEOF_REV];
5109         struct branch *reference;
5110         size_t i;
5112         if (!line)
5113                 return TRUE;
5115         switch (get_line_type(line)) {
5116         case LINE_COMMIT:
5117                 string_copy_rev(id, line + STRING_SIZE("commit "));
5118                 return TRUE;
5120         case LINE_AUTHOR:
5121                 for (i = 0, reference = NULL; i < view->lines; i++) {
5122                         struct branch *branch = view->line[i].data;
5124                         if (strcmp(branch->ref->id, id))
5125                                 continue;
5127                         view->line[i].dirty = TRUE;
5128                         if (reference) {
5129                                 branch->author = reference->author;
5130                                 branch->time = reference->time;
5131                                 continue;
5132                         }
5134                         parse_author_line(line + STRING_SIZE("author "),
5135                                           &branch->author, &branch->time);
5136                         reference = branch;
5137                 }
5138                 return TRUE;
5140         default:
5141                 return TRUE;
5142         }
5146 static bool
5147 branch_open_visitor(void *data, const struct ref *ref)
5149         struct view *view = data;
5150         struct branch *branch;
5152         if (ref->tag || ref->ltag || ref->remote)
5153                 return TRUE;
5155         branch = calloc(1, sizeof(*branch));
5156         if (!branch)
5157                 return FALSE;
5159         branch->ref = ref;
5160         return !!add_line_data(view, branch, LINE_DEFAULT);
5163 static bool
5164 branch_open(struct view *view)
5166         const char *branch_log[] = {
5167                 "git", "log", "--no-color", "--pretty=raw",
5168                         "--simplify-by-decoration", "--all", NULL
5169         };
5171         if (!run_io_rd(&view->io, branch_log, NULL, FORMAT_NONE)) {
5172                 report("Failed to load branch data");
5173                 return TRUE;
5174         }
5176         setup_update(view, view->id);
5177         foreach_ref(branch_open_visitor, view);
5178         view->p_restore = TRUE;
5180         return TRUE;
5183 static bool
5184 branch_grep(struct view *view, struct line *line)
5186         struct branch *branch = line->data;
5187         const char *text[] = {
5188                 branch->ref->name,
5189                 branch->author,
5190                 NULL
5191         };
5193         return grep_text(view, text);
5196 static void
5197 branch_select(struct view *view, struct line *line)
5199         struct branch *branch = line->data;
5201         string_copy_rev(view->ref, branch->ref->id);
5202         string_copy_rev(ref_commit, branch->ref->id);
5203         string_copy_rev(ref_head, branch->ref->id);
5206 static struct view_ops branch_ops = {
5207         "branch",
5208         NULL,
5209         branch_open,
5210         branch_read,
5211         branch_draw,
5212         branch_request,
5213         branch_grep,
5214         branch_select,
5215 };
5217 /*
5218  * Status backend
5219  */
5221 struct status {
5222         char status;
5223         struct {
5224                 mode_t mode;
5225                 char rev[SIZEOF_REV];
5226                 char name[SIZEOF_STR];
5227         } old;
5228         struct {
5229                 mode_t mode;
5230                 char rev[SIZEOF_REV];
5231                 char name[SIZEOF_STR];
5232         } new;
5233 };
5235 static char status_onbranch[SIZEOF_STR];
5236 static struct status stage_status;
5237 static enum line_type stage_line_type;
5238 static size_t stage_chunks;
5239 static int *stage_chunk;
5241 DEFINE_ALLOCATOR(realloc_ints, int, 32)
5243 /* This should work even for the "On branch" line. */
5244 static inline bool
5245 status_has_none(struct view *view, struct line *line)
5247         return line < view->line + view->lines && !line[1].data;
5250 /* Get fields from the diff line:
5251  * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
5252  */
5253 static inline bool
5254 status_get_diff(struct status *file, const char *buf, size_t bufsize)
5256         const char *old_mode = buf +  1;
5257         const char *new_mode = buf +  8;
5258         const char *old_rev  = buf + 15;
5259         const char *new_rev  = buf + 56;
5260         const char *status   = buf + 97;
5262         if (bufsize < 98 ||
5263             old_mode[-1] != ':' ||
5264             new_mode[-1] != ' ' ||
5265             old_rev[-1]  != ' ' ||
5266             new_rev[-1]  != ' ' ||
5267             status[-1]   != ' ')
5268                 return FALSE;
5270         file->status = *status;
5272         string_copy_rev(file->old.rev, old_rev);
5273         string_copy_rev(file->new.rev, new_rev);
5275         file->old.mode = strtoul(old_mode, NULL, 8);
5276         file->new.mode = strtoul(new_mode, NULL, 8);
5278         file->old.name[0] = file->new.name[0] = 0;
5280         return TRUE;
5283 static bool
5284 status_run(struct view *view, const char *argv[], char status, enum line_type type)
5286         struct status *unmerged = NULL;
5287         char *buf;
5288         struct io io = {};
5290         if (!run_io(&io, argv, NULL, IO_RD))
5291                 return FALSE;
5293         add_line_data(view, NULL, type);
5295         while ((buf = io_get(&io, 0, TRUE))) {
5296                 struct status *file = unmerged;
5298                 if (!file) {
5299                         file = calloc(1, sizeof(*file));
5300                         if (!file || !add_line_data(view, file, type))
5301                                 goto error_out;
5302                 }
5304                 /* Parse diff info part. */
5305                 if (status) {
5306                         file->status = status;
5307                         if (status == 'A')
5308                                 string_copy(file->old.rev, NULL_ID);
5310                 } else if (!file->status || file == unmerged) {
5311                         if (!status_get_diff(file, buf, strlen(buf)))
5312                                 goto error_out;
5314                         buf = io_get(&io, 0, TRUE);
5315                         if (!buf)
5316                                 break;
5318                         /* Collapse all modified entries that follow an
5319                          * associated unmerged entry. */
5320                         if (unmerged == file) {
5321                                 unmerged->status = 'U';
5322                                 unmerged = NULL;
5323                         } else if (file->status == 'U') {
5324                                 unmerged = file;
5325                         }
5326                 }
5328                 /* Grab the old name for rename/copy. */
5329                 if (!*file->old.name &&
5330                     (file->status == 'R' || file->status == 'C')) {
5331                         string_ncopy(file->old.name, buf, strlen(buf));
5333                         buf = io_get(&io, 0, TRUE);
5334                         if (!buf)
5335                                 break;
5336                 }
5338                 /* git-ls-files just delivers a NUL separated list of
5339                  * file names similar to the second half of the
5340                  * git-diff-* output. */
5341                 string_ncopy(file->new.name, buf, strlen(buf));
5342                 if (!*file->old.name)
5343                         string_copy(file->old.name, file->new.name);
5344                 file = NULL;
5345         }
5347         if (io_error(&io)) {
5348 error_out:
5349                 done_io(&io);
5350                 return FALSE;
5351         }
5353         if (!view->line[view->lines - 1].data)
5354                 add_line_data(view, NULL, LINE_STAT_NONE);
5356         done_io(&io);
5357         return TRUE;
5360 /* Don't show unmerged entries in the staged section. */
5361 static const char *status_diff_index_argv[] = {
5362         "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
5363                              "--cached", "-M", "HEAD", NULL
5364 };
5366 static const char *status_diff_files_argv[] = {
5367         "git", "diff-files", "-z", NULL
5368 };
5370 static const char *status_list_other_argv[] = {
5371         "git", "ls-files", "-z", "--others", "--exclude-standard", NULL
5372 };
5374 static const char *status_list_no_head_argv[] = {
5375         "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
5376 };
5378 static const char *update_index_argv[] = {
5379         "git", "update-index", "-q", "--unmerged", "--refresh", NULL
5380 };
5382 /* Restore the previous line number to stay in the context or select a
5383  * line with something that can be updated. */
5384 static void
5385 status_restore(struct view *view)
5387         if (view->p_lineno >= view->lines)
5388                 view->p_lineno = view->lines - 1;
5389         while (view->p_lineno < view->lines && !view->line[view->p_lineno].data)
5390                 view->p_lineno++;
5391         while (view->p_lineno > 0 && !view->line[view->p_lineno].data)
5392                 view->p_lineno--;
5394         /* If the above fails, always skip the "On branch" line. */
5395         if (view->p_lineno < view->lines)
5396                 view->lineno = view->p_lineno;
5397         else
5398                 view->lineno = 1;
5400         if (view->lineno < view->offset)
5401                 view->offset = view->lineno;
5402         else if (view->offset + view->height <= view->lineno)
5403                 view->offset = view->lineno - view->height + 1;
5405         view->p_restore = FALSE;
5408 static void
5409 status_update_onbranch(void)
5411         static const char *paths[][2] = {
5412                 { "rebase-apply/rebasing",      "Rebasing" },
5413                 { "rebase-apply/applying",      "Applying mailbox" },
5414                 { "rebase-apply/",              "Rebasing mailbox" },
5415                 { "rebase-merge/interactive",   "Interactive rebase" },
5416                 { "rebase-merge/",              "Rebase merge" },
5417                 { "MERGE_HEAD",                 "Merging" },
5418                 { "BISECT_LOG",                 "Bisecting" },
5419                 { "HEAD",                       "On branch" },
5420         };
5421         char buf[SIZEOF_STR];
5422         struct stat stat;
5423         int i;
5425         if (is_initial_commit()) {
5426                 string_copy(status_onbranch, "Initial commit");
5427                 return;
5428         }
5430         for (i = 0; i < ARRAY_SIZE(paths); i++) {
5431                 char *head = opt_head;
5433                 if (!string_format(buf, "%s/%s", opt_git_dir, paths[i][0]) ||
5434                     lstat(buf, &stat) < 0)
5435                         continue;
5437                 if (!*opt_head) {
5438                         struct io io = {};
5440                         if (io_open(&io, "%s/rebase-merge/head-name", opt_git_dir) &&
5441                             io_read_buf(&io, buf, sizeof(buf))) {
5442                                 head = buf;
5443                                 if (!prefixcmp(head, "refs/heads/"))
5444                                         head += STRING_SIZE("refs/heads/");
5445                         }
5446                 }
5448                 if (!string_format(status_onbranch, "%s %s", paths[i][1], head))
5449                         string_copy(status_onbranch, opt_head);
5450                 return;
5451         }
5453         string_copy(status_onbranch, "Not currently on any branch");
5456 /* First parse staged info using git-diff-index(1), then parse unstaged
5457  * info using git-diff-files(1), and finally untracked files using
5458  * git-ls-files(1). */
5459 static bool
5460 status_open(struct view *view)
5462         reset_view(view);
5464         add_line_data(view, NULL, LINE_STAT_HEAD);
5465         status_update_onbranch();
5467         run_io_bg(update_index_argv);
5469         if (is_initial_commit()) {
5470                 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
5471                         return FALSE;
5472         } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
5473                 return FALSE;
5474         }
5476         if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
5477             !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
5478                 return FALSE;
5480         /* Restore the exact position or use the specialized restore
5481          * mode? */
5482         if (!view->p_restore)
5483                 status_restore(view);
5484         return TRUE;
5487 static bool
5488 status_draw(struct view *view, struct line *line, unsigned int lineno)
5490         struct status *status = line->data;
5491         enum line_type type;
5492         const char *text;
5494         if (!status) {
5495                 switch (line->type) {
5496                 case LINE_STAT_STAGED:
5497                         type = LINE_STAT_SECTION;
5498                         text = "Changes to be committed:";
5499                         break;
5501                 case LINE_STAT_UNSTAGED:
5502                         type = LINE_STAT_SECTION;
5503                         text = "Changed but not updated:";
5504                         break;
5506                 case LINE_STAT_UNTRACKED:
5507                         type = LINE_STAT_SECTION;
5508                         text = "Untracked files:";
5509                         break;
5511                 case LINE_STAT_NONE:
5512                         type = LINE_DEFAULT;
5513                         text = "  (no files)";
5514                         break;
5516                 case LINE_STAT_HEAD:
5517                         type = LINE_STAT_HEAD;
5518                         text = status_onbranch;
5519                         break;
5521                 default:
5522                         return FALSE;
5523                 }
5524         } else {
5525                 static char buf[] = { '?', ' ', ' ', ' ', 0 };
5527                 buf[0] = status->status;
5528                 if (draw_text(view, line->type, buf, TRUE))
5529                         return TRUE;
5530                 type = LINE_DEFAULT;
5531                 text = status->new.name;
5532         }
5534         draw_text(view, type, text, TRUE);
5535         return TRUE;
5538 static enum request
5539 status_load_error(struct view *view, struct view *stage, const char *path)
5541         if (displayed_views() == 2 || display[current_view] != view)
5542                 maximize_view(view);
5543         report("Failed to load '%s': %s", path, io_strerror(&stage->io));
5544         return REQ_NONE;
5547 static enum request
5548 status_enter(struct view *view, struct line *line)
5550         struct status *status = line->data;
5551         const char *oldpath = status ? status->old.name : NULL;
5552         /* Diffs for unmerged entries are empty when passing the new
5553          * path, so leave it empty. */
5554         const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
5555         const char *info;
5556         enum open_flags split;
5557         struct view *stage = VIEW(REQ_VIEW_STAGE);
5559         if (line->type == LINE_STAT_NONE ||
5560             (!status && line[1].type == LINE_STAT_NONE)) {
5561                 report("No file to diff");
5562                 return REQ_NONE;
5563         }
5565         switch (line->type) {
5566         case LINE_STAT_STAGED:
5567                 if (is_initial_commit()) {
5568                         const char *no_head_diff_argv[] = {
5569                                 "git", "diff", "--no-color", "--patch-with-stat",
5570                                         "--", "/dev/null", newpath, NULL
5571                         };
5573                         if (!prepare_update(stage, no_head_diff_argv, opt_cdup, FORMAT_DASH))
5574                                 return status_load_error(view, stage, newpath);
5575                 } else {
5576                         const char *index_show_argv[] = {
5577                                 "git", "diff-index", "--root", "--patch-with-stat",
5578                                         "-C", "-M", "--cached", "HEAD", "--",
5579                                         oldpath, newpath, NULL
5580                         };
5582                         if (!prepare_update(stage, index_show_argv, opt_cdup, FORMAT_DASH))
5583                                 return status_load_error(view, stage, newpath);
5584                 }
5586                 if (status)
5587                         info = "Staged changes to %s";
5588                 else
5589                         info = "Staged changes";
5590                 break;
5592         case LINE_STAT_UNSTAGED:
5593         {
5594                 const char *files_show_argv[] = {
5595                         "git", "diff-files", "--root", "--patch-with-stat",
5596                                 "-C", "-M", "--", oldpath, newpath, NULL
5597                 };
5599                 if (!prepare_update(stage, files_show_argv, opt_cdup, FORMAT_DASH))
5600                         return status_load_error(view, stage, newpath);
5601                 if (status)
5602                         info = "Unstaged changes to %s";
5603                 else
5604                         info = "Unstaged changes";
5605                 break;
5606         }
5607         case LINE_STAT_UNTRACKED:
5608                 if (!newpath) {
5609                         report("No file to show");
5610                         return REQ_NONE;
5611                 }
5613                 if (!suffixcmp(status->new.name, -1, "/")) {
5614                         report("Cannot display a directory");
5615                         return REQ_NONE;
5616                 }
5618                 if (!prepare_update_file(stage, newpath))
5619                         return status_load_error(view, stage, newpath);
5620                 info = "Untracked file %s";
5621                 break;
5623         case LINE_STAT_HEAD:
5624                 return REQ_NONE;
5626         default:
5627                 die("line type %d not handled in switch", line->type);
5628         }
5630         split = view_is_displayed(view) ? OPEN_SPLIT : 0;
5631         open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split);
5632         if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
5633                 if (status) {
5634                         stage_status = *status;
5635                 } else {
5636                         memset(&stage_status, 0, sizeof(stage_status));
5637                 }
5639                 stage_line_type = line->type;
5640                 stage_chunks = 0;
5641                 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
5642         }
5644         return REQ_NONE;
5647 static bool
5648 status_exists(struct status *status, enum line_type type)
5650         struct view *view = VIEW(REQ_VIEW_STATUS);
5651         unsigned long lineno;
5653         for (lineno = 0; lineno < view->lines; lineno++) {
5654                 struct line *line = &view->line[lineno];
5655                 struct status *pos = line->data;
5657                 if (line->type != type)
5658                         continue;
5659                 if (!pos && (!status || !status->status) && line[1].data) {
5660                         select_view_line(view, lineno);
5661                         return TRUE;
5662                 }
5663                 if (pos && !strcmp(status->new.name, pos->new.name)) {
5664                         select_view_line(view, lineno);
5665                         return TRUE;
5666                 }
5667         }
5669         return FALSE;
5673 static bool
5674 status_update_prepare(struct io *io, enum line_type type)
5676         const char *staged_argv[] = {
5677                 "git", "update-index", "-z", "--index-info", NULL
5678         };
5679         const char *others_argv[] = {
5680                 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
5681         };
5683         switch (type) {
5684         case LINE_STAT_STAGED:
5685                 return run_io(io, staged_argv, opt_cdup, IO_WR);
5687         case LINE_STAT_UNSTAGED:
5688                 return run_io(io, others_argv, opt_cdup, IO_WR);
5690         case LINE_STAT_UNTRACKED:
5691                 return run_io(io, others_argv, NULL, IO_WR);
5693         default:
5694                 die("line type %d not handled in switch", type);
5695                 return FALSE;
5696         }
5699 static bool
5700 status_update_write(struct io *io, struct status *status, enum line_type type)
5702         char buf[SIZEOF_STR];
5703         size_t bufsize = 0;
5705         switch (type) {
5706         case LINE_STAT_STAGED:
5707                 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
5708                                         status->old.mode,
5709                                         status->old.rev,
5710                                         status->old.name, 0))
5711                         return FALSE;
5712                 break;
5714         case LINE_STAT_UNSTAGED:
5715         case LINE_STAT_UNTRACKED:
5716                 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
5717                         return FALSE;
5718                 break;
5720         default:
5721                 die("line type %d not handled in switch", type);
5722         }
5724         return io_write(io, buf, bufsize);
5727 static bool
5728 status_update_file(struct status *status, enum line_type type)
5730         struct io io = {};
5731         bool result;
5733         if (!status_update_prepare(&io, type))
5734                 return FALSE;
5736         result = status_update_write(&io, status, type);
5737         return done_io(&io) && result;
5740 static bool
5741 status_update_files(struct view *view, struct line *line)
5743         char buf[sizeof(view->ref)];
5744         struct io io = {};
5745         bool result = TRUE;
5746         struct line *pos = view->line + view->lines;
5747         int files = 0;
5748         int file, done;
5749         int cursor_y = -1, cursor_x = -1;
5751         if (!status_update_prepare(&io, line->type))
5752                 return FALSE;
5754         for (pos = line; pos < view->line + view->lines && pos->data; pos++)
5755                 files++;
5757         string_copy(buf, view->ref);
5758         getsyx(cursor_y, cursor_x);
5759         for (file = 0, done = 5; result && file < files; line++, file++) {
5760                 int almost_done = file * 100 / files;
5762                 if (almost_done > done) {
5763                         done = almost_done;
5764                         string_format(view->ref, "updating file %u of %u (%d%% done)",
5765                                       file, files, done);
5766                         update_view_title(view);
5767                         setsyx(cursor_y, cursor_x);
5768                         doupdate();
5769                 }
5770                 result = status_update_write(&io, line->data, line->type);
5771         }
5772         string_copy(view->ref, buf);
5774         return done_io(&io) && result;
5777 static bool
5778 status_update(struct view *view)
5780         struct line *line = &view->line[view->lineno];
5782         assert(view->lines);
5784         if (!line->data) {
5785                 /* This should work even for the "On branch" line. */
5786                 if (line < view->line + view->lines && !line[1].data) {
5787                         report("Nothing to update");
5788                         return FALSE;
5789                 }
5791                 if (!status_update_files(view, line + 1)) {
5792                         report("Failed to update file status");
5793                         return FALSE;
5794                 }
5796         } else if (!status_update_file(line->data, line->type)) {
5797                 report("Failed to update file status");
5798                 return FALSE;
5799         }
5801         return TRUE;
5804 static bool
5805 status_revert(struct status *status, enum line_type type, bool has_none)
5807         if (!status || type != LINE_STAT_UNSTAGED) {
5808                 if (type == LINE_STAT_STAGED) {
5809                         report("Cannot revert changes to staged files");
5810                 } else if (type == LINE_STAT_UNTRACKED) {
5811                         report("Cannot revert changes to untracked files");
5812                 } else if (has_none) {
5813                         report("Nothing to revert");
5814                 } else {
5815                         report("Cannot revert changes to multiple files");
5816                 }
5818         } else if (prompt_yesno("Are you sure you want to revert changes?")) {
5819                 char mode[10] = "100644";
5820                 const char *reset_argv[] = {
5821                         "git", "update-index", "--cacheinfo", mode,
5822                                 status->old.rev, status->old.name, NULL
5823                 };
5824                 const char *checkout_argv[] = {
5825                         "git", "checkout", "--", status->old.name, NULL
5826                 };
5828                 if (status->status == 'U') {
5829                         string_format(mode, "%5o", status->old.mode);
5831                         if (status->old.mode == 0 && status->new.mode == 0) {
5832                                 reset_argv[2] = "--force-remove";
5833                                 reset_argv[3] = status->old.name;
5834                                 reset_argv[4] = NULL;
5835                         }
5837                         if (!run_io_fg(reset_argv, opt_cdup))
5838                                 return FALSE;
5839                         if (status->old.mode == 0 && status->new.mode == 0)
5840                                 return TRUE;
5841                 }
5843                 return run_io_fg(checkout_argv, opt_cdup);
5844         }
5846         return FALSE;
5849 static enum request
5850 status_request(struct view *view, enum request request, struct line *line)
5852         struct status *status = line->data;
5854         switch (request) {
5855         case REQ_STATUS_UPDATE:
5856                 if (!status_update(view))
5857                         return REQ_NONE;
5858                 break;
5860         case REQ_STATUS_REVERT:
5861                 if (!status_revert(status, line->type, status_has_none(view, line)))
5862                         return REQ_NONE;
5863                 break;
5865         case REQ_STATUS_MERGE:
5866                 if (!status || status->status != 'U') {
5867                         report("Merging only possible for files with unmerged status ('U').");
5868                         return REQ_NONE;
5869                 }
5870                 open_mergetool(status->new.name);
5871                 break;
5873         case REQ_EDIT:
5874                 if (!status)
5875                         return request;
5876                 if (status->status == 'D') {
5877                         report("File has been deleted.");
5878                         return REQ_NONE;
5879                 }
5881                 open_editor(status->status != '?', status->new.name);
5882                 break;
5884         case REQ_VIEW_BLAME:
5885                 if (status)
5886                         opt_ref[0] = 0;
5887                 return request;
5889         case REQ_ENTER:
5890                 /* After returning the status view has been split to
5891                  * show the stage view. No further reloading is
5892                  * necessary. */
5893                 return status_enter(view, line);
5895         case REQ_REFRESH:
5896                 /* Simply reload the view. */
5897                 break;
5899         default:
5900                 return request;
5901         }
5903         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
5905         return REQ_NONE;
5908 static void
5909 status_select(struct view *view, struct line *line)
5911         struct status *status = line->data;
5912         char file[SIZEOF_STR] = "all files";
5913         const char *text;
5914         const char *key;
5916         if (status && !string_format(file, "'%s'", status->new.name))
5917                 return;
5919         if (!status && line[1].type == LINE_STAT_NONE)
5920                 line++;
5922         switch (line->type) {
5923         case LINE_STAT_STAGED:
5924                 text = "Press %s to unstage %s for commit";
5925                 break;
5927         case LINE_STAT_UNSTAGED:
5928                 text = "Press %s to stage %s for commit";
5929                 break;
5931         case LINE_STAT_UNTRACKED:
5932                 text = "Press %s to stage %s for addition";
5933                 break;
5935         case LINE_STAT_HEAD:
5936         case LINE_STAT_NONE:
5937                 text = "Nothing to update";
5938                 break;
5940         default:
5941                 die("line type %d not handled in switch", line->type);
5942         }
5944         if (status && status->status == 'U') {
5945                 text = "Press %s to resolve conflict in %s";
5946                 key = get_key(KEYMAP_STATUS, REQ_STATUS_MERGE);
5948         } else {
5949                 key = get_key(KEYMAP_STATUS, REQ_STATUS_UPDATE);
5950         }
5952         string_format(view->ref, text, key, file);
5953         if (status)
5954                 string_copy(opt_file, status->new.name);
5957 static bool
5958 status_grep(struct view *view, struct line *line)
5960         struct status *status = line->data;
5962         if (status) {
5963                 const char buf[2] = { status->status, 0 };
5964                 const char *text[] = { status->new.name, buf, NULL };
5966                 return grep_text(view, text);
5967         }
5969         return FALSE;
5972 static struct view_ops status_ops = {
5973         "file",
5974         NULL,
5975         status_open,
5976         NULL,
5977         status_draw,
5978         status_request,
5979         status_grep,
5980         status_select,
5981 };
5984 static bool
5985 stage_diff_write(struct io *io, struct line *line, struct line *end)
5987         while (line < end) {
5988                 if (!io_write(io, line->data, strlen(line->data)) ||
5989                     !io_write(io, "\n", 1))
5990                         return FALSE;
5991                 line++;
5992                 if (line->type == LINE_DIFF_CHUNK ||
5993                     line->type == LINE_DIFF_HEADER)
5994                         break;
5995         }
5997         return TRUE;
6000 static struct line *
6001 stage_diff_find(struct view *view, struct line *line, enum line_type type)
6003         for (; view->line < line; line--)
6004                 if (line->type == type)
6005                         return line;
6007         return NULL;
6010 static bool
6011 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
6013         const char *apply_argv[SIZEOF_ARG] = {
6014                 "git", "apply", "--whitespace=nowarn", NULL
6015         };
6016         struct line *diff_hdr;
6017         struct io io = {};
6018         int argc = 3;
6020         diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
6021         if (!diff_hdr)
6022                 return FALSE;
6024         if (!revert)
6025                 apply_argv[argc++] = "--cached";
6026         if (revert || stage_line_type == LINE_STAT_STAGED)
6027                 apply_argv[argc++] = "-R";
6028         apply_argv[argc++] = "-";
6029         apply_argv[argc++] = NULL;
6030         if (!run_io(&io, apply_argv, opt_cdup, IO_WR))
6031                 return FALSE;
6033         if (!stage_diff_write(&io, diff_hdr, chunk) ||
6034             !stage_diff_write(&io, chunk, view->line + view->lines))
6035                 chunk = NULL;
6037         done_io(&io);
6038         run_io_bg(update_index_argv);
6040         return chunk ? TRUE : FALSE;
6043 static bool
6044 stage_update(struct view *view, struct line *line)
6046         struct line *chunk = NULL;
6048         if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
6049                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6051         if (chunk) {
6052                 if (!stage_apply_chunk(view, chunk, FALSE)) {
6053                         report("Failed to apply chunk");
6054                         return FALSE;
6055                 }
6057         } else if (!stage_status.status) {
6058                 view = VIEW(REQ_VIEW_STATUS);
6060                 for (line = view->line; line < view->line + view->lines; line++)
6061                         if (line->type == stage_line_type)
6062                                 break;
6064                 if (!status_update_files(view, line + 1)) {
6065                         report("Failed to update files");
6066                         return FALSE;
6067                 }
6069         } else if (!status_update_file(&stage_status, stage_line_type)) {
6070                 report("Failed to update file");
6071                 return FALSE;
6072         }
6074         return TRUE;
6077 static bool
6078 stage_revert(struct view *view, struct line *line)
6080         struct line *chunk = NULL;
6082         if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
6083                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6085         if (chunk) {
6086                 if (!prompt_yesno("Are you sure you want to revert changes?"))
6087                         return FALSE;
6089                 if (!stage_apply_chunk(view, chunk, TRUE)) {
6090                         report("Failed to revert chunk");
6091                         return FALSE;
6092                 }
6093                 return TRUE;
6095         } else {
6096                 return status_revert(stage_status.status ? &stage_status : NULL,
6097                                      stage_line_type, FALSE);
6098         }
6102 static void
6103 stage_next(struct view *view, struct line *line)
6105         int i;
6107         if (!stage_chunks) {
6108                 for (line = view->line; line < view->line + view->lines; line++) {
6109                         if (line->type != LINE_DIFF_CHUNK)
6110                                 continue;
6112                         if (!realloc_ints(&stage_chunk, stage_chunks, 1)) {
6113                                 report("Allocation failure");
6114                                 return;
6115                         }
6117                         stage_chunk[stage_chunks++] = line - view->line;
6118                 }
6119         }
6121         for (i = 0; i < stage_chunks; i++) {
6122                 if (stage_chunk[i] > view->lineno) {
6123                         do_scroll_view(view, stage_chunk[i] - view->lineno);
6124                         report("Chunk %d of %d", i + 1, stage_chunks);
6125                         return;
6126                 }
6127         }
6129         report("No next chunk found");
6132 static enum request
6133 stage_request(struct view *view, enum request request, struct line *line)
6135         switch (request) {
6136         case REQ_STATUS_UPDATE:
6137                 if (!stage_update(view, line))
6138                         return REQ_NONE;
6139                 break;
6141         case REQ_STATUS_REVERT:
6142                 if (!stage_revert(view, line))
6143                         return REQ_NONE;
6144                 break;
6146         case REQ_STAGE_NEXT:
6147                 if (stage_line_type == LINE_STAT_UNTRACKED) {
6148                         report("File is untracked; press %s to add",
6149                                get_key(KEYMAP_STAGE, REQ_STATUS_UPDATE));
6150                         return REQ_NONE;
6151                 }
6152                 stage_next(view, line);
6153                 return REQ_NONE;
6155         case REQ_EDIT:
6156                 if (!stage_status.new.name[0])
6157                         return request;
6158                 if (stage_status.status == 'D') {
6159                         report("File has been deleted.");
6160                         return REQ_NONE;
6161                 }
6163                 open_editor(stage_status.status != '?', stage_status.new.name);
6164                 break;
6166         case REQ_REFRESH:
6167                 /* Reload everything ... */
6168                 break;
6170         case REQ_VIEW_BLAME:
6171                 if (stage_status.new.name[0]) {
6172                         string_copy(opt_file, stage_status.new.name);
6173                         opt_ref[0] = 0;
6174                 }
6175                 return request;
6177         case REQ_ENTER:
6178                 return pager_request(view, request, line);
6180         default:
6181                 return request;
6182         }
6184         VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
6185         open_view(view, REQ_VIEW_STATUS, OPEN_REFRESH);
6187         /* Check whether the staged entry still exists, and close the
6188          * stage view if it doesn't. */
6189         if (!status_exists(&stage_status, stage_line_type)) {
6190                 status_restore(VIEW(REQ_VIEW_STATUS));
6191                 return REQ_VIEW_CLOSE;
6192         }
6194         if (stage_line_type == LINE_STAT_UNTRACKED) {
6195                 if (!suffixcmp(stage_status.new.name, -1, "/")) {
6196                         report("Cannot display a directory");
6197                         return REQ_NONE;
6198                 }
6200                 if (!prepare_update_file(view, stage_status.new.name)) {
6201                         report("Failed to open file: %s", strerror(errno));
6202                         return REQ_NONE;
6203                 }
6204         }
6205         open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
6207         return REQ_NONE;
6210 static struct view_ops stage_ops = {
6211         "line",
6212         NULL,
6213         NULL,
6214         pager_read,
6215         pager_draw,
6216         stage_request,
6217         pager_grep,
6218         pager_select,
6219 };
6222 /*
6223  * Revision graph
6224  */
6226 struct commit {
6227         char id[SIZEOF_REV];            /* SHA1 ID. */
6228         char title[128];                /* First line of the commit message. */
6229         const char *author;             /* Author of the commit. */
6230         time_t time;                    /* Date from the author ident. */
6231         struct ref_list *refs;          /* Repository references. */
6232         chtype graph[SIZEOF_REVGRAPH];  /* Ancestry chain graphics. */
6233         size_t graph_size;              /* The width of the graph array. */
6234         bool has_parents;               /* Rewritten --parents seen. */
6235 };
6237 /* Size of rev graph with no  "padding" columns */
6238 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
6240 struct rev_graph {
6241         struct rev_graph *prev, *next, *parents;
6242         char rev[SIZEOF_REVITEMS][SIZEOF_REV];
6243         size_t size;
6244         struct commit *commit;
6245         size_t pos;
6246         unsigned int boundary:1;
6247 };
6249 /* Parents of the commit being visualized. */
6250 static struct rev_graph graph_parents[4];
6252 /* The current stack of revisions on the graph. */
6253 static struct rev_graph graph_stacks[4] = {
6254         { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
6255         { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
6256         { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
6257         { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
6258 };
6260 static inline bool
6261 graph_parent_is_merge(struct rev_graph *graph)
6263         return graph->parents->size > 1;
6266 static inline void
6267 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
6269         struct commit *commit = graph->commit;
6271         if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
6272                 commit->graph[commit->graph_size++] = symbol;
6275 static void
6276 clear_rev_graph(struct rev_graph *graph)
6278         graph->boundary = 0;
6279         graph->size = graph->pos = 0;
6280         graph->commit = NULL;
6281         memset(graph->parents, 0, sizeof(*graph->parents));
6284 static void
6285 done_rev_graph(struct rev_graph *graph)
6287         if (graph_parent_is_merge(graph) &&
6288             graph->pos < graph->size - 1 &&
6289             graph->next->size == graph->size + graph->parents->size - 1) {
6290                 size_t i = graph->pos + graph->parents->size - 1;
6292                 graph->commit->graph_size = i * 2;
6293                 while (i < graph->next->size - 1) {
6294                         append_to_rev_graph(graph, ' ');
6295                         append_to_rev_graph(graph, '\\');
6296                         i++;
6297                 }
6298         }
6300         clear_rev_graph(graph);
6303 static void
6304 push_rev_graph(struct rev_graph *graph, const char *parent)
6306         int i;
6308         /* "Collapse" duplicate parents lines.
6309          *
6310          * FIXME: This needs to also update update the drawn graph but
6311          * for now it just serves as a method for pruning graph lines. */
6312         for (i = 0; i < graph->size; i++)
6313                 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
6314                         return;
6316         if (graph->size < SIZEOF_REVITEMS) {
6317                 string_copy_rev(graph->rev[graph->size++], parent);
6318         }
6321 static chtype
6322 get_rev_graph_symbol(struct rev_graph *graph)
6324         chtype symbol;
6326         if (graph->boundary)
6327                 symbol = REVGRAPH_BOUND;
6328         else if (graph->parents->size == 0)
6329                 symbol = REVGRAPH_INIT;
6330         else if (graph_parent_is_merge(graph))
6331                 symbol = REVGRAPH_MERGE;
6332         else if (graph->pos >= graph->size)
6333                 symbol = REVGRAPH_BRANCH;
6334         else
6335                 symbol = REVGRAPH_COMMIT;
6337         return symbol;
6340 static void
6341 draw_rev_graph(struct rev_graph *graph)
6343         struct rev_filler {
6344                 chtype separator, line;
6345         };
6346         enum { DEFAULT, RSHARP, RDIAG, LDIAG };
6347         static struct rev_filler fillers[] = {
6348                 { ' ',  '|' },
6349                 { '`',  '.' },
6350                 { '\'', ' ' },
6351                 { '/',  ' ' },
6352         };
6353         chtype symbol = get_rev_graph_symbol(graph);
6354         struct rev_filler *filler;
6355         size_t i;
6357         if (opt_line_graphics)
6358                 fillers[DEFAULT].line = line_graphics[LINE_GRAPHIC_VLINE];
6360         filler = &fillers[DEFAULT];
6362         for (i = 0; i < graph->pos; i++) {
6363                 append_to_rev_graph(graph, filler->line);
6364                 if (graph_parent_is_merge(graph->prev) &&
6365                     graph->prev->pos == i)
6366                         filler = &fillers[RSHARP];
6368                 append_to_rev_graph(graph, filler->separator);
6369         }
6371         /* Place the symbol for this revision. */
6372         append_to_rev_graph(graph, symbol);
6374         if (graph->prev->size > graph->size)
6375                 filler = &fillers[RDIAG];
6376         else
6377                 filler = &fillers[DEFAULT];
6379         i++;
6381         for (; i < graph->size; i++) {
6382                 append_to_rev_graph(graph, filler->separator);
6383                 append_to_rev_graph(graph, filler->line);
6384                 if (graph_parent_is_merge(graph->prev) &&
6385                     i < graph->prev->pos + graph->parents->size)
6386                         filler = &fillers[RSHARP];
6387                 if (graph->prev->size > graph->size)
6388                         filler = &fillers[LDIAG];
6389         }
6391         if (graph->prev->size > graph->size) {
6392                 append_to_rev_graph(graph, filler->separator);
6393                 if (filler->line != ' ')
6394                         append_to_rev_graph(graph, filler->line);
6395         }
6398 /* Prepare the next rev graph */
6399 static void
6400 prepare_rev_graph(struct rev_graph *graph)
6402         size_t i;
6404         /* First, traverse all lines of revisions up to the active one. */
6405         for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
6406                 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
6407                         break;
6409                 push_rev_graph(graph->next, graph->rev[graph->pos]);
6410         }
6412         /* Interleave the new revision parent(s). */
6413         for (i = 0; !graph->boundary && i < graph->parents->size; i++)
6414                 push_rev_graph(graph->next, graph->parents->rev[i]);
6416         /* Lastly, put any remaining revisions. */
6417         for (i = graph->pos + 1; i < graph->size; i++)
6418                 push_rev_graph(graph->next, graph->rev[i]);
6421 static void
6422 update_rev_graph(struct view *view, struct rev_graph *graph)
6424         /* If this is the finalizing update ... */
6425         if (graph->commit)
6426                 prepare_rev_graph(graph);
6428         /* Graph visualization needs a one rev look-ahead,
6429          * so the first update doesn't visualize anything. */
6430         if (!graph->prev->commit)
6431                 return;
6433         if (view->lines > 2)
6434                 view->line[view->lines - 3].dirty = 1;
6435         if (view->lines > 1)
6436                 view->line[view->lines - 2].dirty = 1;
6437         draw_rev_graph(graph->prev);
6438         done_rev_graph(graph->prev->prev);
6442 /*
6443  * Main view backend
6444  */
6446 static const char *main_argv[SIZEOF_ARG] = {
6447         "git", "log", "--no-color", "--pretty=raw", "--parents",
6448                       "--topo-order", "%(head)", NULL
6449 };
6451 static bool
6452 main_draw(struct view *view, struct line *line, unsigned int lineno)
6454         struct commit *commit = line->data;
6456         if (!commit->author)
6457                 return FALSE;
6459         if (opt_date && draw_date(view, &commit->time))
6460                 return TRUE;
6462         if (opt_author && draw_author(view, commit->author))
6463                 return TRUE;
6465         if (opt_rev_graph && commit->graph_size &&
6466             draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
6467                 return TRUE;
6469         if (opt_show_refs && commit->refs) {
6470                 size_t i;
6472                 for (i = 0; i < commit->refs->size; i++) {
6473                         struct ref *ref = commit->refs->refs[i];
6474                         enum line_type type;
6476                         if (ref->head)
6477                                 type = LINE_MAIN_HEAD;
6478                         else if (ref->ltag)
6479                                 type = LINE_MAIN_LOCAL_TAG;
6480                         else if (ref->tag)
6481                                 type = LINE_MAIN_TAG;
6482                         else if (ref->tracked)
6483                                 type = LINE_MAIN_TRACKED;
6484                         else if (ref->remote)
6485                                 type = LINE_MAIN_REMOTE;
6486                         else
6487                                 type = LINE_MAIN_REF;
6489                         if (draw_text(view, type, "[", TRUE) ||
6490                             draw_text(view, type, ref->name, TRUE) ||
6491                             draw_text(view, type, "]", TRUE))
6492                                 return TRUE;
6494                         if (draw_text(view, LINE_DEFAULT, " ", TRUE))
6495                                 return TRUE;
6496                 }
6497         }
6499         draw_text(view, LINE_DEFAULT, commit->title, TRUE);
6500         return TRUE;
6503 /* Reads git log --pretty=raw output and parses it into the commit struct. */
6504 static bool
6505 main_read(struct view *view, char *line)
6507         static struct rev_graph *graph = graph_stacks;
6508         enum line_type type;
6509         struct commit *commit;
6511         if (!line) {
6512                 int i;
6514                 if (!view->lines && !view->parent)
6515                         die("No revisions match the given arguments.");
6516                 if (view->lines > 0) {
6517                         commit = view->line[view->lines - 1].data;
6518                         view->line[view->lines - 1].dirty = 1;
6519                         if (!commit->author) {
6520                                 view->lines--;
6521                                 free(commit);
6522                                 graph->commit = NULL;
6523                         }
6524                 }
6525                 update_rev_graph(view, graph);
6527                 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
6528                         clear_rev_graph(&graph_stacks[i]);
6529                 return TRUE;
6530         }
6532         type = get_line_type(line);
6533         if (type == LINE_COMMIT) {
6534                 commit = calloc(1, sizeof(struct commit));
6535                 if (!commit)
6536                         return FALSE;
6538                 line += STRING_SIZE("commit ");
6539                 if (*line == '-') {
6540                         graph->boundary = 1;
6541                         line++;
6542                 }
6544                 string_copy_rev(commit->id, line);
6545                 commit->refs = get_ref_list(commit->id);
6546                 graph->commit = commit;
6547                 add_line_data(view, commit, LINE_MAIN_COMMIT);
6549                 while ((line = strchr(line, ' '))) {
6550                         line++;
6551                         push_rev_graph(graph->parents, line);
6552                         commit->has_parents = TRUE;
6553                 }
6554                 return TRUE;
6555         }
6557         if (!view->lines)
6558                 return TRUE;
6559         commit = view->line[view->lines - 1].data;
6561         switch (type) {
6562         case LINE_PARENT:
6563                 if (commit->has_parents)
6564                         break;
6565                 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
6566                 break;
6568         case LINE_AUTHOR:
6569                 parse_author_line(line + STRING_SIZE("author "),
6570                                   &commit->author, &commit->time);
6571                 update_rev_graph(view, graph);
6572                 graph = graph->next;
6573                 break;
6575         default:
6576                 /* Fill in the commit title if it has not already been set. */
6577                 if (commit->title[0])
6578                         break;
6580                 /* Require titles to start with a non-space character at the
6581                  * offset used by git log. */
6582                 if (strncmp(line, "    ", 4))
6583                         break;
6584                 line += 4;
6585                 /* Well, if the title starts with a whitespace character,
6586                  * try to be forgiving.  Otherwise we end up with no title. */
6587                 while (isspace(*line))
6588                         line++;
6589                 if (*line == '\0')
6590                         break;
6591                 /* FIXME: More graceful handling of titles; append "..." to
6592                  * shortened titles, etc. */
6594                 string_expand(commit->title, sizeof(commit->title), line, 1);
6595                 view->line[view->lines - 1].dirty = 1;
6596         }
6598         return TRUE;
6601 static enum request
6602 main_request(struct view *view, enum request request, struct line *line)
6604         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
6606         switch (request) {
6607         case REQ_ENTER:
6608                 open_view(view, REQ_VIEW_DIFF, flags);
6609                 break;
6610         case REQ_REFRESH:
6611                 load_refs();
6612                 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
6613                 break;
6614         default:
6615                 return request;
6616         }
6618         return REQ_NONE;
6621 static bool
6622 grep_refs(struct ref_list *list, regex_t *regex)
6624         regmatch_t pmatch;
6625         size_t i;
6627         if (!opt_show_refs || !list)
6628                 return FALSE;
6630         for (i = 0; i < list->size; i++) {
6631                 if (regexec(regex, list->refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
6632                         return TRUE;
6633         }
6635         return FALSE;
6638 static bool
6639 main_grep(struct view *view, struct line *line)
6641         struct commit *commit = line->data;
6642         const char *text[] = {
6643                 commit->title,
6644                 opt_author ? commit->author : "",
6645                 opt_date ? mkdate(&commit->time) : "",
6646                 NULL
6647         };
6649         return grep_text(view, text) || grep_refs(commit->refs, view->regex);
6652 static void
6653 main_select(struct view *view, struct line *line)
6655         struct commit *commit = line->data;
6657         string_copy_rev(view->ref, commit->id);
6658         string_copy_rev(ref_commit, view->ref);
6661 static struct view_ops main_ops = {
6662         "commit",
6663         main_argv,
6664         NULL,
6665         main_read,
6666         main_draw,
6667         main_request,
6668         main_grep,
6669         main_select,
6670 };
6673 /*
6674  * Unicode / UTF-8 handling
6675  *
6676  * NOTE: Much of the following code for dealing with Unicode is derived from
6677  * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
6678  * src/intl/charset.c from the UTF-8 branch commit elinks-0.11.0-g31f2c28.
6679  */
6681 static inline int
6682 unicode_width(unsigned long c)
6684         if (c >= 0x1100 &&
6685            (c <= 0x115f                         /* Hangul Jamo */
6686             || c == 0x2329
6687             || c == 0x232a
6688             || (c >= 0x2e80  && c <= 0xa4cf && c != 0x303f)
6689                                                 /* CJK ... Yi */
6690             || (c >= 0xac00  && c <= 0xd7a3)    /* Hangul Syllables */
6691             || (c >= 0xf900  && c <= 0xfaff)    /* CJK Compatibility Ideographs */
6692             || (c >= 0xfe30  && c <= 0xfe6f)    /* CJK Compatibility Forms */
6693             || (c >= 0xff00  && c <= 0xff60)    /* Fullwidth Forms */
6694             || (c >= 0xffe0  && c <= 0xffe6)
6695             || (c >= 0x20000 && c <= 0x2fffd)
6696             || (c >= 0x30000 && c <= 0x3fffd)))
6697                 return 2;
6699         if (c == '\t')
6700                 return opt_tab_size;
6702         return 1;
6705 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
6706  * Illegal bytes are set one. */
6707 static const unsigned char utf8_bytes[256] = {
6708         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,
6709         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,
6710         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,
6711         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,
6712         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,
6713         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,
6714         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,
6715         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,
6716 };
6718 /* Decode UTF-8 multi-byte representation into a Unicode character. */
6719 static inline unsigned long
6720 utf8_to_unicode(const char *string, size_t length)
6722         unsigned long unicode;
6724         switch (length) {
6725         case 1:
6726                 unicode  =   string[0];
6727                 break;
6728         case 2:
6729                 unicode  =  (string[0] & 0x1f) << 6;
6730                 unicode +=  (string[1] & 0x3f);
6731                 break;
6732         case 3:
6733                 unicode  =  (string[0] & 0x0f) << 12;
6734                 unicode += ((string[1] & 0x3f) << 6);
6735                 unicode +=  (string[2] & 0x3f);
6736                 break;
6737         case 4:
6738                 unicode  =  (string[0] & 0x0f) << 18;
6739                 unicode += ((string[1] & 0x3f) << 12);
6740                 unicode += ((string[2] & 0x3f) << 6);
6741                 unicode +=  (string[3] & 0x3f);
6742                 break;
6743         case 5:
6744                 unicode  =  (string[0] & 0x0f) << 24;
6745                 unicode += ((string[1] & 0x3f) << 18);
6746                 unicode += ((string[2] & 0x3f) << 12);
6747                 unicode += ((string[3] & 0x3f) << 6);
6748                 unicode +=  (string[4] & 0x3f);
6749                 break;
6750         case 6:
6751                 unicode  =  (string[0] & 0x01) << 30;
6752                 unicode += ((string[1] & 0x3f) << 24);
6753                 unicode += ((string[2] & 0x3f) << 18);
6754                 unicode += ((string[3] & 0x3f) << 12);
6755                 unicode += ((string[4] & 0x3f) << 6);
6756                 unicode +=  (string[5] & 0x3f);
6757                 break;
6758         default:
6759                 die("Invalid Unicode length");
6760         }
6762         /* Invalid characters could return the special 0xfffd value but NUL
6763          * should be just as good. */
6764         return unicode > 0xffff ? 0 : unicode;
6767 /* Calculates how much of string can be shown within the given maximum width
6768  * and sets trimmed parameter to non-zero value if all of string could not be
6769  * shown. If the reserve flag is TRUE, it will reserve at least one
6770  * trailing character, which can be useful when drawing a delimiter.
6771  *
6772  * Returns the number of bytes to output from string to satisfy max_width. */
6773 static size_t
6774 utf8_length(const char **start, size_t skip, int *width, size_t max_width, int *trimmed, bool reserve)
6776         const char *string = *start;
6777         const char *end = strchr(string, '\0');
6778         unsigned char last_bytes = 0;
6779         size_t last_ucwidth = 0;
6781         *width = 0;
6782         *trimmed = 0;
6784         while (string < end) {
6785                 int c = *(unsigned char *) string;
6786                 unsigned char bytes = utf8_bytes[c];
6787                 size_t ucwidth;
6788                 unsigned long unicode;
6790                 if (string + bytes > end)
6791                         break;
6793                 /* Change representation to figure out whether
6794                  * it is a single- or double-width character. */
6796                 unicode = utf8_to_unicode(string, bytes);
6797                 /* FIXME: Graceful handling of invalid Unicode character. */
6798                 if (!unicode)
6799                         break;
6801                 ucwidth = unicode_width(unicode);
6802                 if (skip > 0) {
6803                         skip -= ucwidth <= skip ? ucwidth : skip;
6804                         *start += bytes;
6805                 }
6806                 *width  += ucwidth;
6807                 if (*width > max_width) {
6808                         *trimmed = 1;
6809                         *width -= ucwidth;
6810                         if (reserve && *width == max_width) {
6811                                 string -= last_bytes;
6812                                 *width -= last_ucwidth;
6813                         }
6814                         break;
6815                 }
6817                 string  += bytes;
6818                 last_bytes = ucwidth ? bytes : 0;
6819                 last_ucwidth = ucwidth;
6820         }
6822         return string - *start;
6826 /*
6827  * Status management
6828  */
6830 /* Whether or not the curses interface has been initialized. */
6831 static bool cursed = FALSE;
6833 /* Terminal hacks and workarounds. */
6834 static bool use_scroll_redrawwin;
6835 static bool use_scroll_status_wclear;
6837 /* The status window is used for polling keystrokes. */
6838 static WINDOW *status_win;
6840 /* Reading from the prompt? */
6841 static bool input_mode = FALSE;
6843 static bool status_empty = FALSE;
6845 /* Update status and title window. */
6846 static void
6847 report(const char *msg, ...)
6849         struct view *view = display[current_view];
6851         if (input_mode)
6852                 return;
6854         if (!view) {
6855                 char buf[SIZEOF_STR];
6856                 va_list args;
6858                 va_start(args, msg);
6859                 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
6860                         buf[sizeof(buf) - 1] = 0;
6861                         buf[sizeof(buf) - 2] = '.';
6862                         buf[sizeof(buf) - 3] = '.';
6863                         buf[sizeof(buf) - 4] = '.';
6864                 }
6865                 va_end(args);
6866                 die("%s", buf);
6867         }
6869         if (!status_empty || *msg) {
6870                 va_list args;
6872                 va_start(args, msg);
6874                 wmove(status_win, 0, 0);
6875                 if (view->has_scrolled && use_scroll_status_wclear)
6876                         wclear(status_win);
6877                 if (*msg) {
6878                         vwprintw(status_win, msg, args);
6879                         status_empty = FALSE;
6880                 } else {
6881                         status_empty = TRUE;
6882                 }
6883                 wclrtoeol(status_win);
6884                 wnoutrefresh(status_win);
6886                 va_end(args);
6887         }
6889         update_view_title(view);
6892 /* Controls when nodelay should be in effect when polling user input. */
6893 static void
6894 set_nonblocking_input(bool loading)
6896         static unsigned int loading_views;
6898         if ((loading == FALSE && loading_views-- == 1) ||
6899             (loading == TRUE  && loading_views++ == 0))
6900                 nodelay(status_win, loading);
6903 static void
6904 init_display(void)
6906         const char *term;
6907         int x, y;
6909         /* Initialize the curses library */
6910         if (isatty(STDIN_FILENO)) {
6911                 cursed = !!initscr();
6912                 opt_tty = stdin;
6913         } else {
6914                 /* Leave stdin and stdout alone when acting as a pager. */
6915                 opt_tty = fopen("/dev/tty", "r+");
6916                 if (!opt_tty)
6917                         die("Failed to open /dev/tty");
6918                 cursed = !!newterm(NULL, opt_tty, opt_tty);
6919         }
6921         if (!cursed)
6922                 die("Failed to initialize curses");
6924         nonl();         /* Disable conversion and detect newlines from input. */
6925         cbreak();       /* Take input chars one at a time, no wait for \n */
6926         noecho();       /* Don't echo input */
6927         leaveok(stdscr, FALSE);
6929         if (has_colors())
6930                 init_colors();
6932         getmaxyx(stdscr, y, x);
6933         status_win = newwin(1, 0, y - 1, 0);
6934         if (!status_win)
6935                 die("Failed to create status window");
6937         /* Enable keyboard mapping */
6938         keypad(status_win, TRUE);
6939         wbkgdset(status_win, get_line_attr(LINE_STATUS));
6941         TABSIZE = opt_tab_size;
6942         if (opt_line_graphics) {
6943                 line_graphics[LINE_GRAPHIC_VLINE] = ACS_VLINE;
6944         }
6946         term = getenv("XTERM_VERSION") ? NULL : getenv("COLORTERM");
6947         if (term && !strcmp(term, "gnome-terminal")) {
6948                 /* In the gnome-terminal-emulator, the message from
6949                  * scrolling up one line when impossible followed by
6950                  * scrolling down one line causes corruption of the
6951                  * status line. This is fixed by calling wclear. */
6952                 use_scroll_status_wclear = TRUE;
6953                 use_scroll_redrawwin = FALSE;
6955         } else if (term && !strcmp(term, "xrvt-xpm")) {
6956                 /* No problems with full optimizations in xrvt-(unicode)
6957                  * and aterm. */
6958                 use_scroll_status_wclear = use_scroll_redrawwin = FALSE;
6960         } else {
6961                 /* When scrolling in (u)xterm the last line in the
6962                  * scrolling direction will update slowly. */
6963                 use_scroll_redrawwin = TRUE;
6964                 use_scroll_status_wclear = FALSE;
6965         }
6968 static int
6969 get_input(int prompt_position)
6971         struct view *view;
6972         int i, key, cursor_y, cursor_x;
6974         if (prompt_position)
6975                 input_mode = TRUE;
6977         while (TRUE) {
6978                 foreach_view (view, i) {
6979                         update_view(view);
6980                         if (view_is_displayed(view) && view->has_scrolled &&
6981                             use_scroll_redrawwin)
6982                                 redrawwin(view->win);
6983                         view->has_scrolled = FALSE;
6984                 }
6986                 /* Update the cursor position. */
6987                 if (prompt_position) {
6988                         getbegyx(status_win, cursor_y, cursor_x);
6989                         cursor_x = prompt_position;
6990                 } else {
6991                         view = display[current_view];
6992                         getbegyx(view->win, cursor_y, cursor_x);
6993                         cursor_x = view->width - 1;
6994                         cursor_y += view->lineno - view->offset;
6995                 }
6996                 setsyx(cursor_y, cursor_x);
6998                 /* Refresh, accept single keystroke of input */
6999                 doupdate();
7000                 key = wgetch(status_win);
7002                 /* wgetch() with nodelay() enabled returns ERR when
7003                  * there's no input. */
7004                 if (key == ERR) {
7006                 } else if (key == KEY_RESIZE) {
7007                         int height, width;
7009                         getmaxyx(stdscr, height, width);
7011                         wresize(status_win, 1, width);
7012                         mvwin(status_win, height - 1, 0);
7013                         wnoutrefresh(status_win);
7014                         resize_display();
7015                         redraw_display(TRUE);
7017                 } else {
7018                         input_mode = FALSE;
7019                         return key;
7020                 }
7021         }
7024 static char *
7025 prompt_input(const char *prompt, input_handler handler, void *data)
7027         enum input_status status = INPUT_OK;
7028         static char buf[SIZEOF_STR];
7029         size_t pos = 0;
7031         buf[pos] = 0;
7033         while (status == INPUT_OK || status == INPUT_SKIP) {
7034                 int key;
7036                 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
7037                 wclrtoeol(status_win);
7039                 key = get_input(pos + 1);
7040                 switch (key) {
7041                 case KEY_RETURN:
7042                 case KEY_ENTER:
7043                 case '\n':
7044                         status = pos ? INPUT_STOP : INPUT_CANCEL;
7045                         break;
7047                 case KEY_BACKSPACE:
7048                         if (pos > 0)
7049                                 buf[--pos] = 0;
7050                         else
7051                                 status = INPUT_CANCEL;
7052                         break;
7054                 case KEY_ESC:
7055                         status = INPUT_CANCEL;
7056                         break;
7058                 default:
7059                         if (pos >= sizeof(buf)) {
7060                                 report("Input string too long");
7061                                 return NULL;
7062                         }
7064                         status = handler(data, buf, key);
7065                         if (status == INPUT_OK)
7066                                 buf[pos++] = (char) key;
7067                 }
7068         }
7070         /* Clear the status window */
7071         status_empty = FALSE;
7072         report("");
7074         if (status == INPUT_CANCEL)
7075                 return NULL;
7077         buf[pos++] = 0;
7079         return buf;
7082 static enum input_status
7083 prompt_yesno_handler(void *data, char *buf, int c)
7085         if (c == 'y' || c == 'Y')
7086                 return INPUT_STOP;
7087         if (c == 'n' || c == 'N')
7088                 return INPUT_CANCEL;
7089         return INPUT_SKIP;
7092 static bool
7093 prompt_yesno(const char *prompt)
7095         char prompt2[SIZEOF_STR];
7097         if (!string_format(prompt2, "%s [Yy/Nn]", prompt))
7098                 return FALSE;
7100         return !!prompt_input(prompt2, prompt_yesno_handler, NULL);
7103 static enum input_status
7104 read_prompt_handler(void *data, char *buf, int c)
7106         return isprint(c) ? INPUT_OK : INPUT_SKIP;
7109 static char *
7110 read_prompt(const char *prompt)
7112         return prompt_input(prompt, read_prompt_handler, NULL);
7115 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected)
7117         enum input_status status = INPUT_OK;
7118         int size = 0;
7120         while (items[size].text)
7121                 size++;
7123         while (status == INPUT_OK) {
7124                 const struct menu_item *item = &items[*selected];
7125                 int key;
7126                 int i;
7128                 mvwprintw(status_win, 0, 0, "%s (%d of %d) ",
7129                           prompt, *selected + 1, size);
7130                 if (item->hotkey)
7131                         wprintw(status_win, "[%c] ", (char) item->hotkey);
7132                 wprintw(status_win, "%s", item->text);
7133                 wclrtoeol(status_win);
7135                 key = get_input(COLS - 1);
7136                 switch (key) {
7137                 case KEY_RETURN:
7138                 case KEY_ENTER:
7139                 case '\n':
7140                         status = INPUT_STOP;
7141                         break;
7143                 case KEY_LEFT:
7144                 case KEY_UP:
7145                         *selected = *selected - 1;
7146                         if (*selected < 0)
7147                                 *selected = size - 1;
7148                         break;
7150                 case KEY_RIGHT:
7151                 case KEY_DOWN:
7152                         *selected = (*selected + 1) % size;
7153                         break;
7155                 case KEY_ESC:
7156                         status = INPUT_CANCEL;
7157                         break;
7159                 default:
7160                         for (i = 0; items[i].text; i++)
7161                                 if (items[i].hotkey == key) {
7162                                         *selected = i;
7163                                         status = INPUT_STOP;
7164                                         break;
7165                                 }
7166                 }
7167         }
7169         /* Clear the status window */
7170         status_empty = FALSE;
7171         report("");
7173         return status != INPUT_CANCEL;
7176 /*
7177  * Repository properties
7178  */
7180 static struct ref **refs = NULL;
7181 static size_t refs_size = 0;
7183 static struct ref_list **ref_lists = NULL;
7184 static size_t ref_lists_size = 0;
7186 DEFINE_ALLOCATOR(realloc_refs, struct ref *, 256)
7187 DEFINE_ALLOCATOR(realloc_refs_list, struct ref *, 8)
7188 DEFINE_ALLOCATOR(realloc_ref_lists, struct ref_list *, 8)
7190 static int
7191 compare_refs(const void *ref1_, const void *ref2_)
7193         const struct ref *ref1 = *(const struct ref **)ref1_;
7194         const struct ref *ref2 = *(const struct ref **)ref2_;
7196         if (ref1->tag != ref2->tag)
7197                 return ref2->tag - ref1->tag;
7198         if (ref1->ltag != ref2->ltag)
7199                 return ref2->ltag - ref2->ltag;
7200         if (ref1->head != ref2->head)
7201                 return ref2->head - ref1->head;
7202         if (ref1->tracked != ref2->tracked)
7203                 return ref2->tracked - ref1->tracked;
7204         if (ref1->remote != ref2->remote)
7205                 return ref2->remote - ref1->remote;
7206         return strcmp(ref1->name, ref2->name);
7209 static void
7210 foreach_ref(bool (*visitor)(void *data, const struct ref *ref), void *data)
7212         size_t i;
7214         for (i = 0; i < refs_size; i++)
7215                 if (!visitor(data, refs[i]))
7216                         break;
7219 static struct ref_list *
7220 get_ref_list(const char *id)
7222         struct ref_list *list;
7223         size_t i;
7225         for (i = 0; i < ref_lists_size; i++)
7226                 if (!strcmp(id, ref_lists[i]->id))
7227                         return ref_lists[i];
7229         if (!realloc_ref_lists(&ref_lists, ref_lists_size, 1))
7230                 return NULL;
7231         list = calloc(1, sizeof(*list));
7232         if (!list)
7233                 return NULL;
7235         for (i = 0; i < refs_size; i++) {
7236                 if (!strcmp(id, refs[i]->id) &&
7237                     realloc_refs_list(&list->refs, list->size, 1))
7238                         list->refs[list->size++] = refs[i];
7239         }
7241         if (!list->refs) {
7242                 free(list);
7243                 return NULL;
7244         }
7246         qsort(list->refs, list->size, sizeof(*list->refs), compare_refs);
7247         ref_lists[ref_lists_size++] = list;
7248         return list;
7251 static int
7252 read_ref(char *id, size_t idlen, char *name, size_t namelen)
7254         struct ref *ref = NULL;
7255         bool tag = FALSE;
7256         bool ltag = FALSE;
7257         bool remote = FALSE;
7258         bool tracked = FALSE;
7259         bool head = FALSE;
7260         int from = 0, to = refs_size - 1;
7262         if (!prefixcmp(name, "refs/tags/")) {
7263                 if (!suffixcmp(name, namelen, "^{}")) {
7264                         namelen -= 3;
7265                         name[namelen] = 0;
7266                 } else {
7267                         ltag = TRUE;
7268                 }
7270                 tag = TRUE;
7271                 namelen -= STRING_SIZE("refs/tags/");
7272                 name    += STRING_SIZE("refs/tags/");
7274         } else if (!prefixcmp(name, "refs/remotes/")) {
7275                 remote = TRUE;
7276                 namelen -= STRING_SIZE("refs/remotes/");
7277                 name    += STRING_SIZE("refs/remotes/");
7278                 tracked  = !strcmp(opt_remote, name);
7280         } else if (!prefixcmp(name, "refs/heads/")) {
7281                 namelen -= STRING_SIZE("refs/heads/");
7282                 name    += STRING_SIZE("refs/heads/");
7283                 head     = !strncmp(opt_head, name, namelen);
7285         } else if (!strcmp(name, "HEAD")) {
7286                 string_ncopy(opt_head_rev, id, idlen);
7287                 return OK;
7288         }
7290         /* If we are reloading or it's an annotated tag, replace the
7291          * previous SHA1 with the resolved commit id; relies on the fact
7292          * git-ls-remote lists the commit id of an annotated tag right
7293          * before the commit id it points to. */
7294         while (from <= to) {
7295                 size_t pos = (to + from) / 2;
7296                 int cmp = strcmp(name, refs[pos]->name);
7298                 if (!cmp) {
7299                         ref = refs[pos];
7300                         break;
7301                 }
7303                 if (cmp < 0)
7304                         to = pos - 1;
7305                 else
7306                         from = pos + 1;
7307         }
7309         if (!ref) {
7310                 if (!realloc_refs(&refs, refs_size, 1))
7311                         return ERR;
7312                 ref = calloc(1, sizeof(*ref) + namelen);
7313                 if (!ref)
7314                         return ERR;
7315                 memmove(refs + from + 1, refs + from,
7316                         (refs_size - from) * sizeof(*refs));
7317                 refs[from] = ref;
7318                 strncpy(ref->name, name, namelen);
7319                 refs_size++;
7320         }
7322         ref->head = head;
7323         ref->tag = tag;
7324         ref->ltag = ltag;
7325         ref->remote = remote;
7326         ref->tracked = tracked;
7327         string_copy_rev(ref->id, id);
7329         return OK;
7332 static int
7333 load_refs(void)
7335         const char *head_argv[] = {
7336                 "git", "symbolic-ref", "HEAD", NULL
7337         };
7338         static const char *ls_remote_argv[SIZEOF_ARG] = {
7339                 "git", "ls-remote", opt_git_dir, NULL
7340         };
7341         static bool init = FALSE;
7342         size_t i;
7344         if (!init) {
7345                 argv_from_env(ls_remote_argv, "TIG_LS_REMOTE");
7346                 init = TRUE;
7347         }
7349         if (!*opt_git_dir)
7350                 return OK;
7352         if (run_io_buf(head_argv, opt_head, sizeof(opt_head)) &&
7353             !prefixcmp(opt_head, "refs/heads/")) {
7354                 char *offset = opt_head + STRING_SIZE("refs/heads/");
7356                 memmove(opt_head, offset, strlen(offset) + 1);
7357         }
7359         for (i = 0; i < refs_size; i++)
7360                 refs[i]->id[0] = 0;
7362         if (run_io_load(ls_remote_argv, "\t", read_ref) == ERR)
7363                 return ERR;
7365         /* Update the ref lists to reflect changes. */
7366         for (i = 0; i < ref_lists_size; i++) {
7367                 struct ref_list *list = ref_lists[i];
7368                 size_t old, new;
7370                 for (old = new = 0; old < list->size; old++)
7371                         if (!strcmp(list->id, list->refs[old]->id))
7372                                 list->refs[new++] = list->refs[old];
7373                 list->size = new;
7374         }
7376         return OK;
7379 static void
7380 set_remote_branch(const char *name, const char *value, size_t valuelen)
7382         if (!strcmp(name, ".remote")) {
7383                 string_ncopy(opt_remote, value, valuelen);
7385         } else if (*opt_remote && !strcmp(name, ".merge")) {
7386                 size_t from = strlen(opt_remote);
7388                 if (!prefixcmp(value, "refs/heads/"))
7389                         value += STRING_SIZE("refs/heads/");
7391                 if (!string_format_from(opt_remote, &from, "/%s", value))
7392                         opt_remote[0] = 0;
7393         }
7396 static void
7397 set_repo_config_option(char *name, char *value, int (*cmd)(int, const char **))
7399         const char *argv[SIZEOF_ARG] = { name, "=" };
7400         int argc = 1 + (cmd == option_set_command);
7401         int error = ERR;
7403         if (!argv_from_string(argv, &argc, value))
7404                 config_msg = "Too many option arguments";
7405         else
7406                 error = cmd(argc, argv);
7408         if (error == ERR)
7409                 warn("Option 'tig.%s': %s", name, config_msg);
7412 static bool
7413 set_environment_variable(const char *name, const char *value)
7415         size_t len = strlen(name) + 1 + strlen(value) + 1;
7416         char *env = malloc(len);
7418         if (env &&
7419             string_nformat(env, len, NULL, "%s=%s", name, value) &&
7420             putenv(env) == 0)
7421                 return TRUE;
7422         free(env);
7423         return FALSE;
7426 static void
7427 set_work_tree(const char *value)
7429         char cwd[SIZEOF_STR];
7431         if (!getcwd(cwd, sizeof(cwd)))
7432                 die("Failed to get cwd path: %s", strerror(errno));
7433         if (chdir(opt_git_dir) < 0)
7434                 die("Failed to chdir(%s): %s", strerror(errno));
7435         if (!getcwd(opt_git_dir, sizeof(opt_git_dir)))
7436                 die("Failed to get git path: %s", strerror(errno));
7437         if (chdir(cwd) < 0)
7438                 die("Failed to chdir(%s): %s", cwd, strerror(errno));
7439         if (chdir(value) < 0)
7440                 die("Failed to chdir(%s): %s", value, strerror(errno));
7441         if (!getcwd(cwd, sizeof(cwd)))
7442                 die("Failed to get cwd path: %s", strerror(errno));
7443         if (!set_environment_variable("GIT_WORK_TREE", cwd))
7444                 die("Failed to set GIT_WORK_TREE to '%s'", cwd);
7445         if (!set_environment_variable("GIT_DIR", opt_git_dir))
7446                 die("Failed to set GIT_DIR to '%s'", opt_git_dir);
7447         opt_is_inside_work_tree = TRUE;
7450 static int
7451 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
7453         if (!strcmp(name, "i18n.commitencoding"))
7454                 string_ncopy(opt_encoding, value, valuelen);
7456         else if (!strcmp(name, "core.editor"))
7457                 string_ncopy(opt_editor, value, valuelen);
7459         else if (!strcmp(name, "core.worktree"))
7460                 set_work_tree(value);
7462         else if (!prefixcmp(name, "tig.color."))
7463                 set_repo_config_option(name + 10, value, option_color_command);
7465         else if (!prefixcmp(name, "tig.bind."))
7466                 set_repo_config_option(name + 9, value, option_bind_command);
7468         else if (!prefixcmp(name, "tig."))
7469                 set_repo_config_option(name + 4, value, option_set_command);
7471         else if (*opt_head && !prefixcmp(name, "branch.") &&
7472                  !strncmp(name + 7, opt_head, strlen(opt_head)))
7473                 set_remote_branch(name + 7 + strlen(opt_head), value, valuelen);
7475         return OK;
7478 static int
7479 load_git_config(void)
7481         const char *config_list_argv[] = { "git", "config", "--list", NULL };
7483         return run_io_load(config_list_argv, "=", read_repo_config_option);
7486 static int
7487 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
7489         if (!opt_git_dir[0]) {
7490                 string_ncopy(opt_git_dir, name, namelen);
7492         } else if (opt_is_inside_work_tree == -1) {
7493                 /* This can be 3 different values depending on the
7494                  * version of git being used. If git-rev-parse does not
7495                  * understand --is-inside-work-tree it will simply echo
7496                  * the option else either "true" or "false" is printed.
7497                  * Default to true for the unknown case. */
7498                 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
7500         } else if (*name == '.') {
7501                 string_ncopy(opt_cdup, name, namelen);
7503         } else {
7504                 string_ncopy(opt_prefix, name, namelen);
7505         }
7507         return OK;
7510 static int
7511 load_repo_info(void)
7513         const char *rev_parse_argv[] = {
7514                 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
7515                         "--show-cdup", "--show-prefix", NULL
7516         };
7518         return run_io_load(rev_parse_argv, "=", read_repo_info);
7522 /*
7523  * Main
7524  */
7526 static const char usage[] =
7527 "tig " TIG_VERSION " (" __DATE__ ")\n"
7528 "\n"
7529 "Usage: tig        [options] [revs] [--] [paths]\n"
7530 "   or: tig show   [options] [revs] [--] [paths]\n"
7531 "   or: tig blame  [rev] path\n"
7532 "   or: tig status\n"
7533 "   or: tig <      [git command output]\n"
7534 "\n"
7535 "Options:\n"
7536 "  -v, --version   Show version and exit\n"
7537 "  -h, --help      Show help message and exit";
7539 static void __NORETURN
7540 quit(int sig)
7542         /* XXX: Restore tty modes and let the OS cleanup the rest! */
7543         if (cursed)
7544                 endwin();
7545         exit(0);
7548 static void __NORETURN
7549 die(const char *err, ...)
7551         va_list args;
7553         endwin();
7555         va_start(args, err);
7556         fputs("tig: ", stderr);
7557         vfprintf(stderr, err, args);
7558         fputs("\n", stderr);
7559         va_end(args);
7561         exit(1);
7564 static void
7565 warn(const char *msg, ...)
7567         va_list args;
7569         va_start(args, msg);
7570         fputs("tig warning: ", stderr);
7571         vfprintf(stderr, msg, args);
7572         fputs("\n", stderr);
7573         va_end(args);
7576 static enum request
7577 parse_options(int argc, const char *argv[])
7579         enum request request = REQ_VIEW_MAIN;
7580         const char *subcommand;
7581         bool seen_dashdash = FALSE;
7582         /* XXX: This is vulnerable to the user overriding options
7583          * required for the main view parser. */
7584         const char *custom_argv[SIZEOF_ARG] = {
7585                 "git", "log", "--no-color", "--pretty=raw", "--parents",
7586                         "--topo-order", NULL
7587         };
7588         int i, j = 6;
7590         if (!isatty(STDIN_FILENO)) {
7591                 io_open(&VIEW(REQ_VIEW_PAGER)->io, "");
7592                 return REQ_VIEW_PAGER;
7593         }
7595         if (argc <= 1)
7596                 return REQ_NONE;
7598         subcommand = argv[1];
7599         if (!strcmp(subcommand, "status")) {
7600                 if (argc > 2)
7601                         warn("ignoring arguments after `%s'", subcommand);
7602                 return REQ_VIEW_STATUS;
7604         } else if (!strcmp(subcommand, "blame")) {
7605                 if (argc <= 2 || argc > 4)
7606                         die("invalid number of options to blame\n\n%s", usage);
7608                 i = 2;
7609                 if (argc == 4) {
7610                         string_ncopy(opt_ref, argv[i], strlen(argv[i]));
7611                         i++;
7612                 }
7614                 string_ncopy(opt_file, argv[i], strlen(argv[i]));
7615                 return REQ_VIEW_BLAME;
7617         } else if (!strcmp(subcommand, "show")) {
7618                 request = REQ_VIEW_DIFF;
7620         } else {
7621                 subcommand = NULL;
7622         }
7624         if (subcommand) {
7625                 custom_argv[1] = subcommand;
7626                 j = 2;
7627         }
7629         for (i = 1 + !!subcommand; i < argc; i++) {
7630                 const char *opt = argv[i];
7632                 if (seen_dashdash || !strcmp(opt, "--")) {
7633                         seen_dashdash = TRUE;
7635                 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
7636                         printf("tig version %s\n", TIG_VERSION);
7637                         quit(0);
7639                 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
7640                         printf("%s\n", usage);
7641                         quit(0);
7642                 }
7644                 custom_argv[j++] = opt;
7645                 if (j >= ARRAY_SIZE(custom_argv))
7646                         die("command too long");
7647         }
7649         if (!prepare_update(VIEW(request), custom_argv, NULL, FORMAT_NONE))
7650                 die("Failed to format arguments");
7652         return request;
7655 int
7656 main(int argc, const char *argv[])
7658         enum request request = parse_options(argc, argv);
7659         struct view *view;
7660         size_t i;
7662         signal(SIGINT, quit);
7663         signal(SIGPIPE, SIG_IGN);
7665         if (setlocale(LC_ALL, "")) {
7666                 char *codeset = nl_langinfo(CODESET);
7668                 string_ncopy(opt_codeset, codeset, strlen(codeset));
7669         }
7671         if (load_repo_info() == ERR)
7672                 die("Failed to load repo info.");
7674         if (load_options() == ERR)
7675                 die("Failed to load user config.");
7677         if (load_git_config() == ERR)
7678                 die("Failed to load repo config.");
7680         /* Require a git repository unless when running in pager mode. */
7681         if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
7682                 die("Not a git repository");
7684         if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
7685                 opt_utf8 = FALSE;
7687         if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
7688                 opt_iconv = iconv_open(opt_codeset, opt_encoding);
7689                 if (opt_iconv == ICONV_NONE)
7690                         die("Failed to initialize character set conversion");
7691         }
7693         if (load_refs() == ERR)
7694                 die("Failed to load refs.");
7696         foreach_view (view, i)
7697                 argv_from_env(view->ops->argv, view->cmd_env);
7699         init_display();
7701         if (request != REQ_NONE)
7702                 open_view(NULL, request, OPEN_PREPARED);
7703         request = request == REQ_NONE ? REQ_VIEW_MAIN : REQ_NONE;
7705         while (view_driver(display[current_view], request)) {
7706                 int key = get_input(0);
7708                 view = display[current_view];
7709                 request = get_keybinding(view->keymap, key);
7711                 /* Some low-level request handling. This keeps access to
7712                  * status_win restricted. */
7713                 switch (request) {
7714                 case REQ_PROMPT:
7715                 {
7716                         char *cmd = read_prompt(":");
7718                         if (cmd && isdigit(*cmd)) {
7719                                 int lineno = view->lineno + 1;
7721                                 if (parse_int(&lineno, cmd, 1, view->lines + 1) == OK) {
7722                                         select_view_line(view, lineno - 1);
7723                                         report("");
7724                                 } else {
7725                                         report("Unable to parse '%s' as a line number", cmd);
7726                                 }
7728                         } else if (cmd) {
7729                                 struct view *next = VIEW(REQ_VIEW_PAGER);
7730                                 const char *argv[SIZEOF_ARG] = { "git" };
7731                                 int argc = 1;
7733                                 /* When running random commands, initially show the
7734                                  * command in the title. However, it maybe later be
7735                                  * overwritten if a commit line is selected. */
7736                                 string_ncopy(next->ref, cmd, strlen(cmd));
7738                                 if (!argv_from_string(argv, &argc, cmd)) {
7739                                         report("Too many arguments");
7740                                 } else if (!prepare_update(next, argv, NULL, FORMAT_DASH)) {
7741                                         report("Failed to format command");
7742                                 } else {
7743                                         open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
7744                                 }
7745                         }
7747                         request = REQ_NONE;
7748                         break;
7749                 }
7750                 case REQ_SEARCH:
7751                 case REQ_SEARCH_BACK:
7752                 {
7753                         const char *prompt = request == REQ_SEARCH ? "/" : "?";
7754                         char *search = read_prompt(prompt);
7756                         if (search)
7757                                 string_ncopy(opt_search, search, strlen(search));
7758                         else if (*opt_search)
7759                                 request = request == REQ_SEARCH ?
7760                                         REQ_FIND_NEXT :
7761                                         REQ_FIND_PREV;
7762                         else
7763                                 request = REQ_NONE;
7764                         break;
7765                 }
7766                 default:
7767                         break;
7768                 }
7769         }
7771         quit(0);
7773         return 0;