Code

Refactor toggle_date_option into a generic enum_map based toggler
[tig.git] / tig.c
1 /* Copyright (c) 2006-2009 Jonas Fonseca <fonseca@diku.dk>
2  *
3  * This program is free software; you can redistribute it and/or
4  * modify it under the terms of the GNU General Public License as
5  * published by the Free Software Foundation; either version 2 of
6  * the License, or (at your option) any later version.
7  *
8  * This program is distributed in the hope that it will be useful,
9  * but WITHOUT ANY WARRANTY; without even the implied warranty of
10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11  * GNU General Public License for more details.
12  */
14 #ifdef HAVE_CONFIG_H
15 #include "config.h"
16 #endif
18 #ifndef TIG_VERSION
19 #define TIG_VERSION "unknown-version"
20 #endif
22 #ifndef DEBUG
23 #define NDEBUG
24 #endif
26 #include <assert.h>
27 #include <errno.h>
28 #include <ctype.h>
29 #include <signal.h>
30 #include <stdarg.h>
31 #include <stdio.h>
32 #include <stdlib.h>
33 #include <string.h>
34 #include <sys/types.h>
35 #include <sys/wait.h>
36 #include <sys/stat.h>
37 #include <sys/select.h>
38 #include <unistd.h>
39 #include <sys/time.h>
40 #include <time.h>
41 #include <fcntl.h>
43 #include <regex.h>
45 #include <locale.h>
46 #include <langinfo.h>
47 #include <iconv.h>
49 /* ncurses(3): Must be defined to have extended wide-character functions. */
50 #define _XOPEN_SOURCE_EXTENDED
52 #ifdef HAVE_NCURSESW_NCURSES_H
53 #include <ncursesw/ncurses.h>
54 #else
55 #ifdef HAVE_NCURSES_NCURSES_H
56 #include <ncurses/ncurses.h>
57 #else
58 #include <ncurses.h>
59 #endif
60 #endif
62 #if __GNUC__ >= 3
63 #define __NORETURN __attribute__((__noreturn__))
64 #else
65 #define __NORETURN
66 #endif
68 static void __NORETURN die(const char *err, ...);
69 static void warn(const char *msg, ...);
70 static void report(const char *msg, ...);
71 static void set_nonblocking_input(bool loading);
72 static size_t utf8_length(const char **string, size_t col, int *width, size_t max_width, int *trimmed, bool reserve);
74 #define ABS(x)          ((x) >= 0  ? (x) : -(x))
75 #define MIN(x, y)       ((x) < (y) ? (x) :  (y))
76 #define MAX(x, y)       ((x) > (y) ? (x) :  (y))
78 #define ARRAY_SIZE(x)   (sizeof(x) / sizeof(x[0]))
79 #define STRING_SIZE(x)  (sizeof(x) - 1)
81 #define SIZEOF_STR      1024    /* Default string size. */
82 #define SIZEOF_REF      256     /* Size of symbolic or SHA1 ID. */
83 #define SIZEOF_REV      41      /* Holds a SHA-1 and an ending NUL. */
84 #define SIZEOF_ARG      32      /* Default argument array size. */
86 /* Revision graph */
88 #define REVGRAPH_INIT   'I'
89 #define REVGRAPH_MERGE  'M'
90 #define REVGRAPH_BRANCH '+'
91 #define REVGRAPH_COMMIT '*'
92 #define REVGRAPH_BOUND  '^'
94 #define SIZEOF_REVGRAPH 19      /* Size of revision ancestry graphics. */
96 /* This color name can be used to refer to the default term colors. */
97 #define COLOR_DEFAULT   (-1)
99 #define ICONV_NONE      ((iconv_t) -1)
100 #ifndef ICONV_CONST
101 #define ICONV_CONST     /* nothing */
102 #endif
104 /* The format and size of the date column in the main view. */
105 #define DATE_FORMAT     "%Y-%m-%d %H:%M"
106 #define DATE_COLS       STRING_SIZE("2006-04-29 14:21 ")
107 #define DATE_SHORT_COLS STRING_SIZE("2006-04-29 ")
109 #define ID_COLS         8
111 #define MIN_VIEW_HEIGHT 4
113 #define NULL_ID         "0000000000000000000000000000000000000000"
115 #define S_ISGITLINK(mode) (((mode) & S_IFMT) == 0160000)
117 /* Some ASCII-shorthands fitted into the ncurses namespace. */
118 #define KEY_TAB         '\t'
119 #define KEY_RETURN      '\r'
120 #define KEY_ESC         27
123 struct ref {
124         char id[SIZEOF_REV];    /* Commit SHA1 ID */
125         unsigned int head:1;    /* Is it the current HEAD? */
126         unsigned int tag:1;     /* Is it a tag? */
127         unsigned int ltag:1;    /* If so, is the tag local? */
128         unsigned int remote:1;  /* Is it a remote ref? */
129         unsigned int tracked:1; /* Is it the remote for the current HEAD? */
130         char name[1];           /* Ref name; tag or head names are shortened. */
131 };
133 struct ref_list {
134         char id[SIZEOF_REV];    /* Commit SHA1 ID */
135         size_t size;            /* Number of refs. */
136         struct ref **refs;      /* References for this ID. */
137 };
139 static struct ref_list *get_ref_list(const char *id);
140 static void foreach_ref(bool (*visitor)(void *data, const struct ref *ref), void *data);
141 static int load_refs(void);
143 enum format_flags {
144         FORMAT_ALL,             /* Perform replacement in all arguments. */
145         FORMAT_DASH,            /* Perform replacement up until "--". */
146         FORMAT_NONE             /* No replacement should be performed. */
147 };
149 static bool format_argv(const char *dst[], const char *src[], enum format_flags flags);
151 enum input_status {
152         INPUT_OK,
153         INPUT_SKIP,
154         INPUT_STOP,
155         INPUT_CANCEL
156 };
158 typedef enum input_status (*input_handler)(void *data, char *buf, int c);
160 static char *prompt_input(const char *prompt, input_handler handler, void *data);
161 static bool prompt_yesno(const char *prompt);
163 struct menu_item {
164         int hotkey;
165         const char *text;
166         void *data;
167 };
169 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected);
171 /*
172  * Allocation helpers ... Entering macro hell to never be seen again.
173  */
175 #define DEFINE_ALLOCATOR(name, type, chunk_size)                                \
176 static type *                                                                   \
177 name(type **mem, size_t size, size_t increase)                                  \
178 {                                                                               \
179         size_t num_chunks = (size + chunk_size - 1) / chunk_size;               \
180         size_t num_chunks_new = (size + increase + chunk_size - 1) / chunk_size;\
181         type *tmp = *mem;                                                       \
182                                                                                 \
183         if (mem == NULL || num_chunks != num_chunks_new) {                      \
184                 tmp = realloc(tmp, num_chunks_new * chunk_size * sizeof(type)); \
185                 if (tmp)                                                        \
186                         *mem = tmp;                                             \
187         }                                                                       \
188                                                                                 \
189         return tmp;                                                             \
192 /*
193  * String helpers
194  */
196 static inline void
197 string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t srclen)
199         if (srclen > dstlen - 1)
200                 srclen = dstlen - 1;
202         strncpy(dst, src, srclen);
203         dst[srclen] = 0;
206 /* Shorthands for safely copying into a fixed buffer. */
208 #define string_copy(dst, src) \
209         string_ncopy_do(dst, sizeof(dst), src, sizeof(src))
211 #define string_ncopy(dst, src, srclen) \
212         string_ncopy_do(dst, sizeof(dst), src, srclen)
214 #define string_copy_rev(dst, src) \
215         string_ncopy_do(dst, SIZEOF_REV, src, SIZEOF_REV - 1)
217 #define string_add(dst, from, src) \
218         string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src))
220 static void
221 string_expand(char *dst, size_t dstlen, const char *src, int tabsize)
223         size_t size, pos;
225         for (size = pos = 0; size < dstlen - 1 && src[pos]; pos++) {
226                 if (src[pos] == '\t') {
227                         size_t expanded = tabsize - (size % tabsize);
229                         if (expanded + size >= dstlen - 1)
230                                 expanded = dstlen - size - 1;
231                         memcpy(dst + size, "        ", expanded);
232                         size += expanded;
233                 } else {
234                         dst[size++] = src[pos];
235                 }
236         }
238         dst[size] = 0;
241 static char *
242 chomp_string(char *name)
244         int namelen;
246         while (isspace(*name))
247                 name++;
249         namelen = strlen(name) - 1;
250         while (namelen > 0 && isspace(name[namelen]))
251                 name[namelen--] = 0;
253         return name;
256 static bool
257 string_nformat(char *buf, size_t bufsize, size_t *bufpos, const char *fmt, ...)
259         va_list args;
260         size_t pos = bufpos ? *bufpos : 0;
262         va_start(args, fmt);
263         pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
264         va_end(args);
266         if (bufpos)
267                 *bufpos = pos;
269         return pos >= bufsize ? FALSE : TRUE;
272 #define string_format(buf, fmt, args...) \
273         string_nformat(buf, sizeof(buf), NULL, fmt, args)
275 #define string_format_from(buf, from, fmt, args...) \
276         string_nformat(buf, sizeof(buf), from, fmt, args)
278 static int
279 string_enum_compare(const char *str1, const char *str2, int len)
281         size_t i;
283 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
285         /* Diff-Header == DIFF_HEADER */
286         for (i = 0; i < len; i++) {
287                 if (toupper(str1[i]) == toupper(str2[i]))
288                         continue;
290                 if (string_enum_sep(str1[i]) &&
291                     string_enum_sep(str2[i]))
292                         continue;
294                 return str1[i] - str2[i];
295         }
297         return 0;
300 struct enum_map {
301         const char *name;
302         int namelen;
303         int value;
304 };
306 #define ENUM_MAP(name, value) { name, STRING_SIZE(name), value }
308 static char *
309 enum_map_name(const char *name, size_t namelen)
311         static char buf[SIZEOF_STR];
312         int bufpos;
314         for (bufpos = 0; bufpos <= namelen; bufpos++) {
315                 buf[bufpos] = tolower(name[bufpos]);
316                 if (buf[bufpos] == '_')
317                         buf[bufpos] = '-';
318         }
320         buf[bufpos] = 0;
321         return buf;
324 #define enum_name(entry) enum_map_name((entry).name, (entry).namelen)
326 static bool
327 map_enum_do(const struct enum_map *map, size_t map_size, int *value, const char *name)
329         size_t namelen = strlen(name);
330         int i;
332         for (i = 0; i < map_size; i++)
333                 if (namelen == map[i].namelen &&
334                     !string_enum_compare(name, map[i].name, namelen)) {
335                         *value = map[i].value;
336                         return TRUE;
337                 }
339         return FALSE;
342 #define map_enum(attr, map, name) \
343         map_enum_do(map, ARRAY_SIZE(map), attr, name)
345 #define prefixcmp(str1, str2) \
346         strncmp(str1, str2, STRING_SIZE(str2))
348 static inline int
349 suffixcmp(const char *str, int slen, const char *suffix)
351         size_t len = slen >= 0 ? slen : strlen(str);
352         size_t suffixlen = strlen(suffix);
354         return suffixlen < len ? strcmp(str + len - suffixlen, suffix) : -1;
358 /*
359  * What value of "tz" was in effect back then at "time" in the
360  * local timezone?
361  */
362 static int local_tzoffset(time_t time)
364         time_t t, t_local;
365         struct tm tm;
366         int offset, eastwest; 
368         t = time;
369         localtime_r(&t, &tm);
370         t_local = mktime(&tm);
372         if (t_local < t) {
373                 eastwest = -1;
374                 offset = t - t_local;
375         } else {
376                 eastwest = 1;
377                 offset = t_local - t;
378         }
379         offset /= 60; /* in minutes */
380         offset = (offset % 60) + ((offset / 60) * 100);
381         return offset * eastwest;
384 enum date {
385         DATE_NO = 0,
386         DATE_DEFAULT,
387         DATE_RELATIVE,
388         DATE_SHORT
389 };
391 static const struct enum_map date_map[] = {
392 #define DATE_(name) ENUM_MAP(#name, DATE_##name)
393         DATE_(NO),
394         DATE_(DEFAULT),
395         DATE_(RELATIVE),
396         DATE_(SHORT)
397 #undef  DATE_
398 };
400 static char *
401 string_date(const time_t *time, enum date date)
403         static char buf[DATE_COLS + 1];
404         static const struct enum_map reldate[] = {
405                 { "second", 1,                  60 * 2 },
406                 { "minute", 60,                 60 * 60 * 2 },
407                 { "hour",   60 * 60,            60 * 60 * 24 * 2 },
408                 { "day",    60 * 60 * 24,       60 * 60 * 24 * 7 * 2 },
409                 { "week",   60 * 60 * 24 * 7,   60 * 60 * 24 * 7 * 5 },
410                 { "month",  60 * 60 * 24 * 30,  60 * 60 * 24 * 30 * 12 },
411         };
412         struct tm tm;
414         if (date == DATE_RELATIVE) {
415                 struct timeval now;
416                 time_t date = *time + local_tzoffset(*time);
417                 time_t seconds;
418                 int i;
420                 gettimeofday(&now, NULL);
421                 seconds = now.tv_sec < date ? date - now.tv_sec : now.tv_sec - date;
422                 for (i = 0; i < ARRAY_SIZE(reldate); i++) {
423                         if (seconds >= reldate[i].value)
424                                 continue;
426                         seconds /= reldate[i].namelen;
427                         if (!string_format(buf, "%ld %s%s %s",
428                                            seconds, reldate[i].name,
429                                            seconds > 1 ? "s" : "",
430                                            now.tv_sec >= date ? "ago" : "ahead"))
431                                 break;
432                         return buf;
433                 }
434         }
436         gmtime_r(time, &tm);
437         return strftime(buf, sizeof(buf), DATE_FORMAT, &tm) ? buf : NULL;
441 static bool
442 argv_from_string(const char *argv[SIZEOF_ARG], int *argc, char *cmd)
444         int valuelen;
446         while (*cmd && *argc < SIZEOF_ARG && (valuelen = strcspn(cmd, " \t"))) {
447                 bool advance = cmd[valuelen] != 0;
449                 cmd[valuelen] = 0;
450                 argv[(*argc)++] = chomp_string(cmd);
451                 cmd = chomp_string(cmd + valuelen + advance);
452         }
454         if (*argc < SIZEOF_ARG)
455                 argv[*argc] = NULL;
456         return *argc < SIZEOF_ARG;
459 static void
460 argv_from_env(const char **argv, const char *name)
462         char *env = argv ? getenv(name) : NULL;
463         int argc = 0;
465         if (env && *env)
466                 env = strdup(env);
467         if (env && !argv_from_string(argv, &argc, env))
468                 die("Too many arguments in the `%s` environment variable", name);
472 /*
473  * Executing external commands.
474  */
476 enum io_type {
477         IO_FD,                  /* File descriptor based IO. */
478         IO_BG,                  /* Execute command in the background. */
479         IO_FG,                  /* Execute command with same std{in,out,err}. */
480         IO_RD,                  /* Read only fork+exec IO. */
481         IO_WR,                  /* Write only fork+exec IO. */
482         IO_AP,                  /* Append fork+exec output to file. */
483 };
485 struct io {
486         enum io_type type;      /* The requested type of pipe. */
487         const char *dir;        /* Directory from which to execute. */
488         pid_t pid;              /* Pipe for reading or writing. */
489         int pipe;               /* Pipe end for reading or writing. */
490         int error;              /* Error status. */
491         const char *argv[SIZEOF_ARG];   /* Shell command arguments. */
492         char *buf;              /* Read buffer. */
493         size_t bufalloc;        /* Allocated buffer size. */
494         size_t bufsize;         /* Buffer content size. */
495         char *bufpos;           /* Current buffer position. */
496         unsigned int eof:1;     /* Has end of file been reached. */
497 };
499 static void
500 reset_io(struct io *io)
502         io->pipe = -1;
503         io->pid = 0;
504         io->buf = io->bufpos = NULL;
505         io->bufalloc = io->bufsize = 0;
506         io->error = 0;
507         io->eof = 0;
510 static void
511 init_io(struct io *io, const char *dir, enum io_type type)
513         reset_io(io);
514         io->type = type;
515         io->dir = dir;
518 static bool
519 init_io_rd(struct io *io, const char *argv[], const char *dir,
520                 enum format_flags flags)
522         init_io(io, dir, IO_RD);
523         return format_argv(io->argv, argv, flags);
526 static bool
527 io_open(struct io *io, const char *fmt, ...)
529         char name[SIZEOF_STR] = "";
530         bool fits;
531         va_list args;
533         init_io(io, NULL, IO_FD);
535         va_start(args, fmt);
536         fits = vsnprintf(name, sizeof(name), fmt, args) < sizeof(name);
537         va_end(args);
539         if (!fits) {
540                 io->error = ENAMETOOLONG;
541                 return FALSE;
542         }
543         io->pipe = *name ? open(name, O_RDONLY) : STDIN_FILENO;
544         if (io->pipe == -1)
545                 io->error = errno;
546         return io->pipe != -1;
549 static bool
550 kill_io(struct io *io)
552         return io->pid == 0 || kill(io->pid, SIGKILL) != -1;
555 static bool
556 done_io(struct io *io)
558         pid_t pid = io->pid;
560         if (io->pipe != -1)
561                 close(io->pipe);
562         free(io->buf);
563         reset_io(io);
565         while (pid > 0) {
566                 int status;
567                 pid_t waiting = waitpid(pid, &status, 0);
569                 if (waiting < 0) {
570                         if (errno == EINTR)
571                                 continue;
572                         report("waitpid failed (%s)", strerror(errno));
573                         return FALSE;
574                 }
576                 return waiting == pid &&
577                        !WIFSIGNALED(status) &&
578                        WIFEXITED(status) &&
579                        !WEXITSTATUS(status);
580         }
582         return TRUE;
585 static bool
586 start_io(struct io *io)
588         int pipefds[2] = { -1, -1 };
590         if (io->type == IO_FD)
591                 return TRUE;
593         if ((io->type == IO_RD || io->type == IO_WR) &&
594             pipe(pipefds) < 0)
595                 return FALSE;
596         else if (io->type == IO_AP)
597                 pipefds[1] = io->pipe;
599         if ((io->pid = fork())) {
600                 if (pipefds[!(io->type == IO_WR)] != -1)
601                         close(pipefds[!(io->type == IO_WR)]);
602                 if (io->pid != -1) {
603                         io->pipe = pipefds[!!(io->type == IO_WR)];
604                         return TRUE;
605                 }
607         } else {
608                 if (io->type != IO_FG) {
609                         int devnull = open("/dev/null", O_RDWR);
610                         int readfd  = io->type == IO_WR ? pipefds[0] : devnull;
611                         int writefd = (io->type == IO_RD || io->type == IO_AP)
612                                                         ? pipefds[1] : devnull;
614                         dup2(readfd,  STDIN_FILENO);
615                         dup2(writefd, STDOUT_FILENO);
616                         dup2(devnull, STDERR_FILENO);
618                         close(devnull);
619                         if (pipefds[0] != -1)
620                                 close(pipefds[0]);
621                         if (pipefds[1] != -1)
622                                 close(pipefds[1]);
623                 }
625                 if (io->dir && *io->dir && chdir(io->dir) == -1)
626                         die("Failed to change directory: %s", strerror(errno));
628                 execvp(io->argv[0], (char *const*) io->argv);
629                 die("Failed to execute program: %s", strerror(errno));
630         }
632         if (pipefds[!!(io->type == IO_WR)] != -1)
633                 close(pipefds[!!(io->type == IO_WR)]);
634         return FALSE;
637 static bool
638 run_io(struct io *io, const char **argv, const char *dir, enum io_type type)
640         init_io(io, dir, type);
641         if (!format_argv(io->argv, argv, FORMAT_NONE))
642                 return FALSE;
643         return start_io(io);
646 static int
647 run_io_do(struct io *io)
649         return start_io(io) && done_io(io);
652 static int
653 run_io_bg(const char **argv)
655         struct io io = {};
657         init_io(&io, NULL, IO_BG);
658         if (!format_argv(io.argv, argv, FORMAT_NONE))
659                 return FALSE;
660         return run_io_do(&io);
663 static bool
664 run_io_fg(const char **argv, const char *dir)
666         struct io io = {};
668         init_io(&io, dir, IO_FG);
669         if (!format_argv(io.argv, argv, FORMAT_NONE))
670                 return FALSE;
671         return run_io_do(&io);
674 static bool
675 run_io_append(const char **argv, enum format_flags flags, int fd)
677         struct io io = {};
679         init_io(&io, NULL, IO_AP);
680         io.pipe = fd;
681         if (format_argv(io.argv, argv, flags))
682                 return run_io_do(&io);
683         close(fd);
684         return FALSE;
687 static bool
688 run_io_rd(struct io *io, const char **argv, const char *dir, enum format_flags flags)
690         return init_io_rd(io, argv, dir, flags) && start_io(io);
693 static bool
694 io_eof(struct io *io)
696         return io->eof;
699 static int
700 io_error(struct io *io)
702         return io->error;
705 static char *
706 io_strerror(struct io *io)
708         return strerror(io->error);
711 static bool
712 io_can_read(struct io *io)
714         struct timeval tv = { 0, 500 };
715         fd_set fds;
717         FD_ZERO(&fds);
718         FD_SET(io->pipe, &fds);
720         return select(io->pipe + 1, &fds, NULL, NULL, &tv) > 0;
723 static ssize_t
724 io_read(struct io *io, void *buf, size_t bufsize)
726         do {
727                 ssize_t readsize = read(io->pipe, buf, bufsize);
729                 if (readsize < 0 && (errno == EAGAIN || errno == EINTR))
730                         continue;
731                 else if (readsize == -1)
732                         io->error = errno;
733                 else if (readsize == 0)
734                         io->eof = 1;
735                 return readsize;
736         } while (1);
739 DEFINE_ALLOCATOR(realloc_io_buf, char, BUFSIZ)
741 static char *
742 io_get(struct io *io, int c, bool can_read)
744         char *eol;
745         ssize_t readsize;
747         while (TRUE) {
748                 if (io->bufsize > 0) {
749                         eol = memchr(io->bufpos, c, io->bufsize);
750                         if (eol) {
751                                 char *line = io->bufpos;
753                                 *eol = 0;
754                                 io->bufpos = eol + 1;
755                                 io->bufsize -= io->bufpos - line;
756                                 return line;
757                         }
758                 }
760                 if (io_eof(io)) {
761                         if (io->bufsize) {
762                                 io->bufpos[io->bufsize] = 0;
763                                 io->bufsize = 0;
764                                 return io->bufpos;
765                         }
766                         return NULL;
767                 }
769                 if (!can_read)
770                         return NULL;
772                 if (io->bufsize > 0 && io->bufpos > io->buf)
773                         memmove(io->buf, io->bufpos, io->bufsize);
775                 if (io->bufalloc == io->bufsize) {
776                         if (!realloc_io_buf(&io->buf, io->bufalloc, BUFSIZ))
777                                 return NULL;
778                         io->bufalloc += BUFSIZ;
779                 }
781                 io->bufpos = io->buf;
782                 readsize = io_read(io, io->buf + io->bufsize, io->bufalloc - io->bufsize);
783                 if (io_error(io))
784                         return NULL;
785                 io->bufsize += readsize;
786         }
789 static bool
790 io_write(struct io *io, const void *buf, size_t bufsize)
792         size_t written = 0;
794         while (!io_error(io) && written < bufsize) {
795                 ssize_t size;
797                 size = write(io->pipe, buf + written, bufsize - written);
798                 if (size < 0 && (errno == EAGAIN || errno == EINTR))
799                         continue;
800                 else if (size == -1)
801                         io->error = errno;
802                 else
803                         written += size;
804         }
806         return written == bufsize;
809 static bool
810 io_read_buf(struct io *io, char buf[], size_t bufsize)
812         char *result = io_get(io, '\n', TRUE);
814         if (result) {
815                 result = chomp_string(result);
816                 string_ncopy_do(buf, bufsize, result, strlen(result));
817         }
819         return done_io(io) && result;
822 static bool
823 run_io_buf(const char **argv, char buf[], size_t bufsize)
825         struct io io = {};
827         return run_io_rd(&io, argv, NULL, FORMAT_NONE)
828             && io_read_buf(&io, buf, bufsize);
831 static int
832 io_load(struct io *io, const char *separators,
833         int (*read_property)(char *, size_t, char *, size_t))
835         char *name;
836         int state = OK;
838         if (!start_io(io))
839                 return ERR;
841         while (state == OK && (name = io_get(io, '\n', TRUE))) {
842                 char *value;
843                 size_t namelen;
844                 size_t valuelen;
846                 name = chomp_string(name);
847                 namelen = strcspn(name, separators);
849                 if (name[namelen]) {
850                         name[namelen] = 0;
851                         value = chomp_string(name + namelen + 1);
852                         valuelen = strlen(value);
854                 } else {
855                         value = "";
856                         valuelen = 0;
857                 }
859                 state = read_property(name, namelen, value, valuelen);
860         }
862         if (state != ERR && io_error(io))
863                 state = ERR;
864         done_io(io);
866         return state;
869 static int
870 run_io_load(const char **argv, const char *separators,
871             int (*read_property)(char *, size_t, char *, size_t))
873         struct io io = {};
875         return init_io_rd(&io, argv, NULL, FORMAT_NONE)
876                 ? io_load(&io, separators, read_property) : ERR;
880 /*
881  * User requests
882  */
884 #define REQ_INFO \
885         /* XXX: Keep the view request first and in sync with views[]. */ \
886         REQ_GROUP("View switching") \
887         REQ_(VIEW_MAIN,         "Show main view"), \
888         REQ_(VIEW_DIFF,         "Show diff view"), \
889         REQ_(VIEW_LOG,          "Show log view"), \
890         REQ_(VIEW_TREE,         "Show tree view"), \
891         REQ_(VIEW_BLOB,         "Show blob view"), \
892         REQ_(VIEW_BLAME,        "Show blame view"), \
893         REQ_(VIEW_BRANCH,       "Show branch view"), \
894         REQ_(VIEW_HELP,         "Show help page"), \
895         REQ_(VIEW_PAGER,        "Show pager view"), \
896         REQ_(VIEW_STATUS,       "Show status view"), \
897         REQ_(VIEW_STAGE,        "Show stage view"), \
898         \
899         REQ_GROUP("View manipulation") \
900         REQ_(ENTER,             "Enter current line and scroll"), \
901         REQ_(NEXT,              "Move to next"), \
902         REQ_(PREVIOUS,          "Move to previous"), \
903         REQ_(PARENT,            "Move to parent"), \
904         REQ_(VIEW_NEXT,         "Move focus to next view"), \
905         REQ_(REFRESH,           "Reload and refresh"), \
906         REQ_(MAXIMIZE,          "Maximize the current view"), \
907         REQ_(VIEW_CLOSE,        "Close the current view"), \
908         REQ_(QUIT,              "Close all views and quit"), \
909         \
910         REQ_GROUP("View specific requests") \
911         REQ_(STATUS_UPDATE,     "Update file status"), \
912         REQ_(STATUS_REVERT,     "Revert file changes"), \
913         REQ_(STATUS_MERGE,      "Merge file using external tool"), \
914         REQ_(STAGE_NEXT,        "Find next chunk to stage"), \
915         \
916         REQ_GROUP("Cursor navigation") \
917         REQ_(MOVE_UP,           "Move cursor one line up"), \
918         REQ_(MOVE_DOWN,         "Move cursor one line down"), \
919         REQ_(MOVE_PAGE_DOWN,    "Move cursor one page down"), \
920         REQ_(MOVE_PAGE_UP,      "Move cursor one page up"), \
921         REQ_(MOVE_FIRST_LINE,   "Move cursor to first line"), \
922         REQ_(MOVE_LAST_LINE,    "Move cursor to last line"), \
923         \
924         REQ_GROUP("Scrolling") \
925         REQ_(SCROLL_LEFT,       "Scroll two columns left"), \
926         REQ_(SCROLL_RIGHT,      "Scroll two columns right"), \
927         REQ_(SCROLL_LINE_UP,    "Scroll one line up"), \
928         REQ_(SCROLL_LINE_DOWN,  "Scroll one line down"), \
929         REQ_(SCROLL_PAGE_UP,    "Scroll one page up"), \
930         REQ_(SCROLL_PAGE_DOWN,  "Scroll one page down"), \
931         \
932         REQ_GROUP("Searching") \
933         REQ_(SEARCH,            "Search the view"), \
934         REQ_(SEARCH_BACK,       "Search backwards in the view"), \
935         REQ_(FIND_NEXT,         "Find next search match"), \
936         REQ_(FIND_PREV,         "Find previous search match"), \
937         \
938         REQ_GROUP("Option manipulation") \
939         REQ_(OPTIONS,           "Open option menu"), \
940         REQ_(TOGGLE_LINENO,     "Toggle line numbers"), \
941         REQ_(TOGGLE_DATE,       "Toggle date display"), \
942         REQ_(TOGGLE_DATE_SHORT, "Toggle short (date-only) dates"), \
943         REQ_(TOGGLE_AUTHOR,     "Toggle author display"), \
944         REQ_(TOGGLE_REV_GRAPH,  "Toggle revision graph visualization"), \
945         REQ_(TOGGLE_REFS,       "Toggle reference display (tags/branches)"), \
946         REQ_(TOGGLE_SORT_ORDER, "Toggle ascending/descending sort order"), \
947         REQ_(TOGGLE_SORT_FIELD, "Toggle field to sort by"), \
948         \
949         REQ_GROUP("Misc") \
950         REQ_(PROMPT,            "Bring up the prompt"), \
951         REQ_(SCREEN_REDRAW,     "Redraw the screen"), \
952         REQ_(SHOW_VERSION,      "Show version information"), \
953         REQ_(STOP_LOADING,      "Stop all loading views"), \
954         REQ_(EDIT,              "Open in editor"), \
955         REQ_(NONE,              "Do nothing")
958 /* User action requests. */
959 enum request {
960 #define REQ_GROUP(help)
961 #define REQ_(req, help) REQ_##req
963         /* Offset all requests to avoid conflicts with ncurses getch values. */
964         REQ_OFFSET = KEY_MAX + 1,
965         REQ_INFO
967 #undef  REQ_GROUP
968 #undef  REQ_
969 };
971 struct request_info {
972         enum request request;
973         const char *name;
974         int namelen;
975         const char *help;
976 };
978 static const struct request_info req_info[] = {
979 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
980 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
981         REQ_INFO
982 #undef  REQ_GROUP
983 #undef  REQ_
984 };
986 static enum request
987 get_request(const char *name)
989         int namelen = strlen(name);
990         int i;
992         for (i = 0; i < ARRAY_SIZE(req_info); i++)
993                 if (req_info[i].namelen == namelen &&
994                     !string_enum_compare(req_info[i].name, name, namelen))
995                         return req_info[i].request;
997         return REQ_NONE;
1001 /*
1002  * Options
1003  */
1005 /* Option and state variables. */
1006 static enum date opt_date               = DATE_DEFAULT;
1007 static bool opt_author                  = TRUE;
1008 static bool opt_line_number             = FALSE;
1009 static bool opt_line_graphics           = TRUE;
1010 static bool opt_rev_graph               = FALSE;
1011 static bool opt_show_refs               = TRUE;
1012 static int opt_num_interval             = 5;
1013 static double opt_hscroll               = 0.50;
1014 static double opt_scale_split_view      = 2.0 / 3.0;
1015 static int opt_tab_size                 = 8;
1016 static int opt_author_cols              = 19;
1017 static char opt_path[SIZEOF_STR]        = "";
1018 static char opt_file[SIZEOF_STR]        = "";
1019 static char opt_ref[SIZEOF_REF]         = "";
1020 static char opt_head[SIZEOF_REF]        = "";
1021 static char opt_head_rev[SIZEOF_REV]    = "";
1022 static char opt_remote[SIZEOF_REF]      = "";
1023 static char opt_encoding[20]            = "UTF-8";
1024 static char opt_codeset[20]             = "UTF-8";
1025 static iconv_t opt_iconv_in             = ICONV_NONE;
1026 static iconv_t opt_iconv_out            = ICONV_NONE;
1027 static char opt_search[SIZEOF_STR]      = "";
1028 static char opt_cdup[SIZEOF_STR]        = "";
1029 static char opt_prefix[SIZEOF_STR]      = "";
1030 static char opt_git_dir[SIZEOF_STR]     = "";
1031 static signed char opt_is_inside_work_tree      = -1; /* set to TRUE or FALSE */
1032 static char opt_editor[SIZEOF_STR]      = "";
1033 static FILE *opt_tty                    = NULL;
1035 #define is_initial_commit()     (!*opt_head_rev)
1036 #define is_head_commit(rev)     (!strcmp((rev), "HEAD") || !strcmp(opt_head_rev, (rev)))
1037 #define mkdate(time)            string_date(time, opt_date)
1040 /*
1041  * Line-oriented content detection.
1042  */
1044 #define LINE_INFO \
1045 LINE(DIFF_HEADER,  "diff --git ",       COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1046 LINE(DIFF_CHUNK,   "@@",                COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1047 LINE(DIFF_ADD,     "+",                 COLOR_GREEN,    COLOR_DEFAULT,  0), \
1048 LINE(DIFF_DEL,     "-",                 COLOR_RED,      COLOR_DEFAULT,  0), \
1049 LINE(DIFF_INDEX,        "index ",         COLOR_BLUE,   COLOR_DEFAULT,  0), \
1050 LINE(DIFF_OLDMODE,      "old file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
1051 LINE(DIFF_NEWMODE,      "new file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
1052 LINE(DIFF_COPY_FROM,    "copy from",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
1053 LINE(DIFF_COPY_TO,      "copy to",        COLOR_YELLOW, COLOR_DEFAULT,  0), \
1054 LINE(DIFF_RENAME_FROM,  "rename from",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
1055 LINE(DIFF_RENAME_TO,    "rename to",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
1056 LINE(DIFF_SIMILARITY,   "similarity ",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
1057 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
1058 LINE(DIFF_TREE,         "diff-tree ",     COLOR_BLUE,   COLOR_DEFAULT,  0), \
1059 LINE(PP_AUTHOR,    "Author: ",          COLOR_CYAN,     COLOR_DEFAULT,  0), \
1060 LINE(PP_COMMIT,    "Commit: ",          COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1061 LINE(PP_MERGE,     "Merge: ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
1062 LINE(PP_DATE,      "Date:   ",          COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1063 LINE(PP_ADATE,     "AuthorDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1064 LINE(PP_CDATE,     "CommitDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1065 LINE(PP_REFS,      "Refs: ",            COLOR_RED,      COLOR_DEFAULT,  0), \
1066 LINE(COMMIT,       "commit ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
1067 LINE(PARENT,       "parent ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
1068 LINE(TREE,         "tree ",             COLOR_BLUE,     COLOR_DEFAULT,  0), \
1069 LINE(AUTHOR,       "author ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
1070 LINE(COMMITTER,    "committer ",        COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1071 LINE(SIGNOFF,      "    Signed-off-by", COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1072 LINE(ACKED,        "    Acked-by",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1073 LINE(DEFAULT,      "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
1074 LINE(CURSOR,       "",                  COLOR_WHITE,    COLOR_GREEN,    A_BOLD), \
1075 LINE(STATUS,       "",                  COLOR_GREEN,    COLOR_DEFAULT,  0), \
1076 LINE(DELIMITER,    "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1077 LINE(DATE,         "",                  COLOR_BLUE,     COLOR_DEFAULT,  0), \
1078 LINE(MODE,         "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1079 LINE(LINE_NUMBER,  "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1080 LINE(TITLE_BLUR,   "",                  COLOR_WHITE,    COLOR_BLUE,     0), \
1081 LINE(TITLE_FOCUS,  "",                  COLOR_WHITE,    COLOR_BLUE,     A_BOLD), \
1082 LINE(MAIN_COMMIT,  "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
1083 LINE(MAIN_TAG,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  A_BOLD), \
1084 LINE(MAIN_LOCAL_TAG,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1085 LINE(MAIN_REMOTE,  "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1086 LINE(MAIN_TRACKED, "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_BOLD), \
1087 LINE(MAIN_REF,     "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1088 LINE(MAIN_HEAD,    "",                  COLOR_CYAN,     COLOR_DEFAULT,  A_BOLD), \
1089 LINE(MAIN_REVGRAPH,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1090 LINE(TREE_HEAD,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_BOLD), \
1091 LINE(TREE_DIR,     "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_NORMAL), \
1092 LINE(TREE_FILE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
1093 LINE(STAT_HEAD,    "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1094 LINE(STAT_SECTION, "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1095 LINE(STAT_NONE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
1096 LINE(STAT_STAGED,  "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1097 LINE(STAT_UNSTAGED,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1098 LINE(STAT_UNTRACKED,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1099 LINE(HELP_KEYMAP,  "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1100 LINE(HELP_GROUP,   "",                  COLOR_BLUE,     COLOR_DEFAULT,  0), \
1101 LINE(BLAME_ID,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0)
1103 enum line_type {
1104 #define LINE(type, line, fg, bg, attr) \
1105         LINE_##type
1106         LINE_INFO,
1107         LINE_NONE
1108 #undef  LINE
1109 };
1111 struct line_info {
1112         const char *name;       /* Option name. */
1113         int namelen;            /* Size of option name. */
1114         const char *line;       /* The start of line to match. */
1115         int linelen;            /* Size of string to match. */
1116         int fg, bg, attr;       /* Color and text attributes for the lines. */
1117 };
1119 static struct line_info line_info[] = {
1120 #define LINE(type, line, fg, bg, attr) \
1121         { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
1122         LINE_INFO
1123 #undef  LINE
1124 };
1126 static enum line_type
1127 get_line_type(const char *line)
1129         int linelen = strlen(line);
1130         enum line_type type;
1132         for (type = 0; type < ARRAY_SIZE(line_info); type++)
1133                 /* Case insensitive search matches Signed-off-by lines better. */
1134                 if (linelen >= line_info[type].linelen &&
1135                     !strncasecmp(line_info[type].line, line, line_info[type].linelen))
1136                         return type;
1138         return LINE_DEFAULT;
1141 static inline int
1142 get_line_attr(enum line_type type)
1144         assert(type < ARRAY_SIZE(line_info));
1145         return COLOR_PAIR(type) | line_info[type].attr;
1148 static struct line_info *
1149 get_line_info(const char *name)
1151         size_t namelen = strlen(name);
1152         enum line_type type;
1154         for (type = 0; type < ARRAY_SIZE(line_info); type++)
1155                 if (namelen == line_info[type].namelen &&
1156                     !string_enum_compare(line_info[type].name, name, namelen))
1157                         return &line_info[type];
1159         return NULL;
1162 static void
1163 init_colors(void)
1165         int default_bg = line_info[LINE_DEFAULT].bg;
1166         int default_fg = line_info[LINE_DEFAULT].fg;
1167         enum line_type type;
1169         start_color();
1171         if (assume_default_colors(default_fg, default_bg) == ERR) {
1172                 default_bg = COLOR_BLACK;
1173                 default_fg = COLOR_WHITE;
1174         }
1176         for (type = 0; type < ARRAY_SIZE(line_info); type++) {
1177                 struct line_info *info = &line_info[type];
1178                 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
1179                 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
1181                 init_pair(type, fg, bg);
1182         }
1185 struct line {
1186         enum line_type type;
1188         /* State flags */
1189         unsigned int selected:1;
1190         unsigned int dirty:1;
1191         unsigned int cleareol:1;
1192         unsigned int other:16;
1194         void *data;             /* User data */
1195 };
1198 /*
1199  * Keys
1200  */
1202 struct keybinding {
1203         int alias;
1204         enum request request;
1205 };
1207 static const struct keybinding default_keybindings[] = {
1208         /* View switching */
1209         { 'm',          REQ_VIEW_MAIN },
1210         { 'd',          REQ_VIEW_DIFF },
1211         { 'l',          REQ_VIEW_LOG },
1212         { 't',          REQ_VIEW_TREE },
1213         { 'f',          REQ_VIEW_BLOB },
1214         { 'B',          REQ_VIEW_BLAME },
1215         { 'H',          REQ_VIEW_BRANCH },
1216         { 'p',          REQ_VIEW_PAGER },
1217         { 'h',          REQ_VIEW_HELP },
1218         { 'S',          REQ_VIEW_STATUS },
1219         { 'c',          REQ_VIEW_STAGE },
1221         /* View manipulation */
1222         { 'q',          REQ_VIEW_CLOSE },
1223         { KEY_TAB,      REQ_VIEW_NEXT },
1224         { KEY_RETURN,   REQ_ENTER },
1225         { KEY_UP,       REQ_PREVIOUS },
1226         { KEY_DOWN,     REQ_NEXT },
1227         { 'R',          REQ_REFRESH },
1228         { KEY_F(5),     REQ_REFRESH },
1229         { 'O',          REQ_MAXIMIZE },
1231         /* Cursor navigation */
1232         { 'k',          REQ_MOVE_UP },
1233         { 'j',          REQ_MOVE_DOWN },
1234         { KEY_HOME,     REQ_MOVE_FIRST_LINE },
1235         { KEY_END,      REQ_MOVE_LAST_LINE },
1236         { KEY_NPAGE,    REQ_MOVE_PAGE_DOWN },
1237         { ' ',          REQ_MOVE_PAGE_DOWN },
1238         { KEY_PPAGE,    REQ_MOVE_PAGE_UP },
1239         { 'b',          REQ_MOVE_PAGE_UP },
1240         { '-',          REQ_MOVE_PAGE_UP },
1242         /* Scrolling */
1243         { KEY_LEFT,     REQ_SCROLL_LEFT },
1244         { KEY_RIGHT,    REQ_SCROLL_RIGHT },
1245         { KEY_IC,       REQ_SCROLL_LINE_UP },
1246         { KEY_DC,       REQ_SCROLL_LINE_DOWN },
1247         { 'w',          REQ_SCROLL_PAGE_UP },
1248         { 's',          REQ_SCROLL_PAGE_DOWN },
1250         /* Searching */
1251         { '/',          REQ_SEARCH },
1252         { '?',          REQ_SEARCH_BACK },
1253         { 'n',          REQ_FIND_NEXT },
1254         { 'N',          REQ_FIND_PREV },
1256         /* Misc */
1257         { 'Q',          REQ_QUIT },
1258         { 'z',          REQ_STOP_LOADING },
1259         { 'v',          REQ_SHOW_VERSION },
1260         { 'r',          REQ_SCREEN_REDRAW },
1261         { 'o',          REQ_OPTIONS },
1262         { '.',          REQ_TOGGLE_LINENO },
1263         { 'D',          REQ_TOGGLE_DATE },
1264         { 'A',          REQ_TOGGLE_AUTHOR },
1265         { 'g',          REQ_TOGGLE_REV_GRAPH },
1266         { 'F',          REQ_TOGGLE_REFS },
1267         { 'I',          REQ_TOGGLE_SORT_ORDER },
1268         { 'i',          REQ_TOGGLE_SORT_FIELD },
1269         { ':',          REQ_PROMPT },
1270         { 'u',          REQ_STATUS_UPDATE },
1271         { '!',          REQ_STATUS_REVERT },
1272         { 'M',          REQ_STATUS_MERGE },
1273         { '@',          REQ_STAGE_NEXT },
1274         { ',',          REQ_PARENT },
1275         { 'e',          REQ_EDIT },
1276 };
1278 #define KEYMAP_INFO \
1279         KEYMAP_(GENERIC), \
1280         KEYMAP_(MAIN), \
1281         KEYMAP_(DIFF), \
1282         KEYMAP_(LOG), \
1283         KEYMAP_(TREE), \
1284         KEYMAP_(BLOB), \
1285         KEYMAP_(BLAME), \
1286         KEYMAP_(BRANCH), \
1287         KEYMAP_(PAGER), \
1288         KEYMAP_(HELP), \
1289         KEYMAP_(STATUS), \
1290         KEYMAP_(STAGE)
1292 enum keymap {
1293 #define KEYMAP_(name) KEYMAP_##name
1294         KEYMAP_INFO
1295 #undef  KEYMAP_
1296 };
1298 static const struct enum_map keymap_table[] = {
1299 #define KEYMAP_(name) ENUM_MAP(#name, KEYMAP_##name)
1300         KEYMAP_INFO
1301 #undef  KEYMAP_
1302 };
1304 #define set_keymap(map, name) map_enum(map, keymap_table, name)
1306 struct keybinding_table {
1307         struct keybinding *data;
1308         size_t size;
1309 };
1311 static struct keybinding_table keybindings[ARRAY_SIZE(keymap_table)];
1313 static void
1314 add_keybinding(enum keymap keymap, enum request request, int key)
1316         struct keybinding_table *table = &keybindings[keymap];
1318         table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data));
1319         if (!table->data)
1320                 die("Failed to allocate keybinding");
1321         table->data[table->size].alias = key;
1322         table->data[table->size++].request = request;
1325 /* Looks for a key binding first in the given map, then in the generic map, and
1326  * lastly in the default keybindings. */
1327 static enum request
1328 get_keybinding(enum keymap keymap, int key)
1330         size_t i;
1332         for (i = 0; i < keybindings[keymap].size; i++)
1333                 if (keybindings[keymap].data[i].alias == key)
1334                         return keybindings[keymap].data[i].request;
1336         for (i = 0; i < keybindings[KEYMAP_GENERIC].size; i++)
1337                 if (keybindings[KEYMAP_GENERIC].data[i].alias == key)
1338                         return keybindings[KEYMAP_GENERIC].data[i].request;
1340         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1341                 if (default_keybindings[i].alias == key)
1342                         return default_keybindings[i].request;
1344         return (enum request) key;
1348 struct key {
1349         const char *name;
1350         int value;
1351 };
1353 static const struct key key_table[] = {
1354         { "Enter",      KEY_RETURN },
1355         { "Space",      ' ' },
1356         { "Backspace",  KEY_BACKSPACE },
1357         { "Tab",        KEY_TAB },
1358         { "Escape",     KEY_ESC },
1359         { "Left",       KEY_LEFT },
1360         { "Right",      KEY_RIGHT },
1361         { "Up",         KEY_UP },
1362         { "Down",       KEY_DOWN },
1363         { "Insert",     KEY_IC },
1364         { "Delete",     KEY_DC },
1365         { "Hash",       '#' },
1366         { "Home",       KEY_HOME },
1367         { "End",        KEY_END },
1368         { "PageUp",     KEY_PPAGE },
1369         { "PageDown",   KEY_NPAGE },
1370         { "F1",         KEY_F(1) },
1371         { "F2",         KEY_F(2) },
1372         { "F3",         KEY_F(3) },
1373         { "F4",         KEY_F(4) },
1374         { "F5",         KEY_F(5) },
1375         { "F6",         KEY_F(6) },
1376         { "F7",         KEY_F(7) },
1377         { "F8",         KEY_F(8) },
1378         { "F9",         KEY_F(9) },
1379         { "F10",        KEY_F(10) },
1380         { "F11",        KEY_F(11) },
1381         { "F12",        KEY_F(12) },
1382 };
1384 static int
1385 get_key_value(const char *name)
1387         int i;
1389         for (i = 0; i < ARRAY_SIZE(key_table); i++)
1390                 if (!strcasecmp(key_table[i].name, name))
1391                         return key_table[i].value;
1393         if (strlen(name) == 1 && isprint(*name))
1394                 return (int) *name;
1396         return ERR;
1399 static const char *
1400 get_key_name(int key_value)
1402         static char key_char[] = "'X'";
1403         const char *seq = NULL;
1404         int key;
1406         for (key = 0; key < ARRAY_SIZE(key_table); key++)
1407                 if (key_table[key].value == key_value)
1408                         seq = key_table[key].name;
1410         if (seq == NULL &&
1411             key_value < 127 &&
1412             isprint(key_value)) {
1413                 key_char[1] = (char) key_value;
1414                 seq = key_char;
1415         }
1417         return seq ? seq : "(no key)";
1420 static bool
1421 append_key(char *buf, size_t *pos, const struct keybinding *keybinding)
1423         const char *sep = *pos > 0 ? ", " : "";
1424         const char *keyname = get_key_name(keybinding->alias);
1426         return string_nformat(buf, BUFSIZ, pos, "%s%s", sep, keyname);
1429 static bool
1430 append_keymap_request_keys(char *buf, size_t *pos, enum request request,
1431                            enum keymap keymap, bool all)
1433         int i;
1435         for (i = 0; i < keybindings[keymap].size; i++) {
1436                 if (keybindings[keymap].data[i].request == request) {
1437                         if (!append_key(buf, pos, &keybindings[keymap].data[i]))
1438                                 return FALSE;
1439                         if (!all)
1440                                 break;
1441                 }
1442         }
1444         return TRUE;
1447 #define get_key(keymap, request) get_keys(keymap, request, FALSE)
1449 static const char *
1450 get_keys(enum keymap keymap, enum request request, bool all)
1452         static char buf[BUFSIZ];
1453         size_t pos = 0;
1454         int i;
1456         buf[pos] = 0;
1458         if (!append_keymap_request_keys(buf, &pos, request, keymap, all))
1459                 return "Too many keybindings!";
1460         if (pos > 0 && !all)
1461                 return buf;
1463         if (keymap != KEYMAP_GENERIC) {
1464                 /* Only the generic keymap includes the default keybindings when
1465                  * listing all keys. */
1466                 if (all)
1467                         return buf;
1469                 if (!append_keymap_request_keys(buf, &pos, request, KEYMAP_GENERIC, all))
1470                         return "Too many keybindings!";
1471                 if (pos)
1472                         return buf;
1473         }
1475         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1476                 if (default_keybindings[i].request == request) {
1477                         if (!append_key(buf, &pos, &default_keybindings[i]))
1478                                 return "Too many keybindings!";
1479                         if (!all)
1480                                 return buf;
1481                 }
1482         }
1484         return buf;
1487 struct run_request {
1488         enum keymap keymap;
1489         int key;
1490         const char *argv[SIZEOF_ARG];
1491 };
1493 static struct run_request *run_request;
1494 static size_t run_requests;
1496 DEFINE_ALLOCATOR(realloc_run_requests, struct run_request, 8)
1498 static enum request
1499 add_run_request(enum keymap keymap, int key, int argc, const char **argv)
1501         struct run_request *req;
1503         if (argc >= ARRAY_SIZE(req->argv) - 1)
1504                 return REQ_NONE;
1506         if (!realloc_run_requests(&run_request, run_requests, 1))
1507                 return REQ_NONE;
1509         req = &run_request[run_requests];
1510         req->keymap = keymap;
1511         req->key = key;
1512         req->argv[0] = NULL;
1514         if (!format_argv(req->argv, argv, FORMAT_NONE))
1515                 return REQ_NONE;
1517         return REQ_NONE + ++run_requests;
1520 static struct run_request *
1521 get_run_request(enum request request)
1523         if (request <= REQ_NONE)
1524                 return NULL;
1525         return &run_request[request - REQ_NONE - 1];
1528 static void
1529 add_builtin_run_requests(void)
1531         const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1532         const char *commit[] = { "git", "commit", NULL };
1533         const char *gc[] = { "git", "gc", NULL };
1534         struct {
1535                 enum keymap keymap;
1536                 int key;
1537                 int argc;
1538                 const char **argv;
1539         } reqs[] = {
1540                 { KEYMAP_MAIN,    'C', ARRAY_SIZE(cherry_pick) - 1, cherry_pick },
1541                 { KEYMAP_STATUS,  'C', ARRAY_SIZE(commit) - 1, commit },
1542                 { KEYMAP_GENERIC, 'G', ARRAY_SIZE(gc) - 1, gc },
1543         };
1544         int i;
1546         for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1547                 enum request req;
1549                 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argc, reqs[i].argv);
1550                 if (req != REQ_NONE)
1551                         add_keybinding(reqs[i].keymap, req, reqs[i].key);
1552         }
1555 /*
1556  * User config file handling.
1557  */
1559 static int   config_lineno;
1560 static bool  config_errors;
1561 static const char *config_msg;
1563 static const struct enum_map color_map[] = {
1564 #define COLOR_MAP(name) ENUM_MAP(#name, COLOR_##name)
1565         COLOR_MAP(DEFAULT),
1566         COLOR_MAP(BLACK),
1567         COLOR_MAP(BLUE),
1568         COLOR_MAP(CYAN),
1569         COLOR_MAP(GREEN),
1570         COLOR_MAP(MAGENTA),
1571         COLOR_MAP(RED),
1572         COLOR_MAP(WHITE),
1573         COLOR_MAP(YELLOW),
1574 };
1576 static const struct enum_map attr_map[] = {
1577 #define ATTR_MAP(name) ENUM_MAP(#name, A_##name)
1578         ATTR_MAP(NORMAL),
1579         ATTR_MAP(BLINK),
1580         ATTR_MAP(BOLD),
1581         ATTR_MAP(DIM),
1582         ATTR_MAP(REVERSE),
1583         ATTR_MAP(STANDOUT),
1584         ATTR_MAP(UNDERLINE),
1585 };
1587 #define set_attribute(attr, name)       map_enum(attr, attr_map, name)
1589 static int parse_step(double *opt, const char *arg)
1591         *opt = atoi(arg);
1592         if (!strchr(arg, '%'))
1593                 return OK;
1595         /* "Shift down" so 100% and 1 does not conflict. */
1596         *opt = (*opt - 1) / 100;
1597         if (*opt >= 1.0) {
1598                 *opt = 0.99;
1599                 config_msg = "Step value larger than 100%";
1600                 return ERR;
1601         }
1602         if (*opt < 0.0) {
1603                 *opt = 1;
1604                 config_msg = "Invalid step value";
1605                 return ERR;
1606         }
1607         return OK;
1610 static int
1611 parse_int(int *opt, const char *arg, int min, int max)
1613         int value = atoi(arg);
1615         if (min <= value && value <= max) {
1616                 *opt = value;
1617                 return OK;
1618         }
1620         config_msg = "Integer value out of bound";
1621         return ERR;
1624 static bool
1625 set_color(int *color, const char *name)
1627         if (map_enum(color, color_map, name))
1628                 return TRUE;
1629         if (!prefixcmp(name, "color"))
1630                 return parse_int(color, name + 5, 0, 255) == OK;
1631         return FALSE;
1634 /* Wants: object fgcolor bgcolor [attribute] */
1635 static int
1636 option_color_command(int argc, const char *argv[])
1638         struct line_info *info;
1640         if (argc < 3) {
1641                 config_msg = "Wrong number of arguments given to color command";
1642                 return ERR;
1643         }
1645         info = get_line_info(argv[0]);
1646         if (!info) {
1647                 static const struct enum_map obsolete[] = {
1648                         ENUM_MAP("main-delim",  LINE_DELIMITER),
1649                         ENUM_MAP("main-date",   LINE_DATE),
1650                         ENUM_MAP("main-author", LINE_AUTHOR),
1651                 };
1652                 int index;
1654                 if (!map_enum(&index, obsolete, argv[0])) {
1655                         config_msg = "Unknown color name";
1656                         return ERR;
1657                 }
1658                 info = &line_info[index];
1659         }
1661         if (!set_color(&info->fg, argv[1]) ||
1662             !set_color(&info->bg, argv[2])) {
1663                 config_msg = "Unknown color";
1664                 return ERR;
1665         }
1667         info->attr = 0;
1668         while (argc-- > 3) {
1669                 int attr;
1671                 if (!set_attribute(&attr, argv[argc])) {
1672                         config_msg = "Unknown attribute";
1673                         return ERR;
1674                 }
1675                 info->attr |= attr;
1676         }
1678         return OK;
1681 static int parse_bool(bool *opt, const char *arg)
1683         *opt = (!strcmp(arg, "1") || !strcmp(arg, "true") || !strcmp(arg, "yes"))
1684                 ? TRUE : FALSE;
1685         return OK;
1688 static int parse_enum_do(unsigned int *opt, const char *arg,
1689                          const struct enum_map *map, size_t map_size)
1691         bool is_true;
1693         assert(map_size > 1);
1695         if (map_enum_do(map, map_size, (int *) opt, arg))
1696                 return OK;
1698         if (parse_bool(&is_true, arg) != OK)
1699                 return ERR;
1701         *opt = is_true ? map[1].value : map[0].value;
1702         return OK;
1705 #define parse_enum(opt, arg, map) \
1706         parse_enum_do(opt, arg, map, ARRAY_SIZE(map))
1708 static int
1709 parse_string(char *opt, const char *arg, size_t optsize)
1711         int arglen = strlen(arg);
1713         switch (arg[0]) {
1714         case '\"':
1715         case '\'':
1716                 if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1717                         config_msg = "Unmatched quotation";
1718                         return ERR;
1719                 }
1720                 arg += 1; arglen -= 2;
1721         default:
1722                 string_ncopy_do(opt, optsize, arg, arglen);
1723                 return OK;
1724         }
1727 /* Wants: name = value */
1728 static int
1729 option_set_command(int argc, const char *argv[])
1731         if (argc != 3) {
1732                 config_msg = "Wrong number of arguments given to set command";
1733                 return ERR;
1734         }
1736         if (strcmp(argv[1], "=")) {
1737                 config_msg = "No value assigned";
1738                 return ERR;
1739         }
1741         if (!strcmp(argv[0], "show-author"))
1742                 return parse_bool(&opt_author, argv[2]);
1744         if (!strcmp(argv[0], "show-date"))
1745                 return parse_enum(&opt_date, argv[2], date_map);
1747         if (!strcmp(argv[0], "show-rev-graph"))
1748                 return parse_bool(&opt_rev_graph, argv[2]);
1750         if (!strcmp(argv[0], "show-refs"))
1751                 return parse_bool(&opt_show_refs, argv[2]);
1753         if (!strcmp(argv[0], "show-line-numbers"))
1754                 return parse_bool(&opt_line_number, argv[2]);
1756         if (!strcmp(argv[0], "line-graphics"))
1757                 return parse_bool(&opt_line_graphics, argv[2]);
1759         if (!strcmp(argv[0], "line-number-interval"))
1760                 return parse_int(&opt_num_interval, argv[2], 1, 1024);
1762         if (!strcmp(argv[0], "author-width"))
1763                 return parse_int(&opt_author_cols, argv[2], 0, 1024);
1765         if (!strcmp(argv[0], "horizontal-scroll"))
1766                 return parse_step(&opt_hscroll, argv[2]);
1768         if (!strcmp(argv[0], "split-view-height"))
1769                 return parse_step(&opt_scale_split_view, argv[2]);
1771         if (!strcmp(argv[0], "tab-size"))
1772                 return parse_int(&opt_tab_size, argv[2], 1, 1024);
1774         if (!strcmp(argv[0], "commit-encoding"))
1775                 return parse_string(opt_encoding, argv[2], sizeof(opt_encoding));
1777         config_msg = "Unknown variable name";
1778         return ERR;
1781 /* Wants: mode request key */
1782 static int
1783 option_bind_command(int argc, const char *argv[])
1785         enum request request;
1786         int keymap = -1;
1787         int key;
1789         if (argc < 3) {
1790                 config_msg = "Wrong number of arguments given to bind command";
1791                 return ERR;
1792         }
1794         if (set_keymap(&keymap, argv[0]) == ERR) {
1795                 config_msg = "Unknown key map";
1796                 return ERR;
1797         }
1799         key = get_key_value(argv[1]);
1800         if (key == ERR) {
1801                 config_msg = "Unknown key";
1802                 return ERR;
1803         }
1805         request = get_request(argv[2]);
1806         if (request == REQ_NONE) {
1807                 static const struct enum_map obsolete[] = {
1808                         ENUM_MAP("cherry-pick",         REQ_NONE),
1809                         ENUM_MAP("screen-resize",       REQ_NONE),
1810                         ENUM_MAP("tree-parent",         REQ_PARENT),
1811                 };
1812                 int alias;
1814                 if (map_enum(&alias, obsolete, argv[2])) {
1815                         if (alias != REQ_NONE)
1816                                 add_keybinding(keymap, alias, key);
1817                         config_msg = "Obsolete request name";
1818                         return ERR;
1819                 }
1820         }
1821         if (request == REQ_NONE && *argv[2]++ == '!')
1822                 request = add_run_request(keymap, key, argc - 2, argv + 2);
1823         if (request == REQ_NONE) {
1824                 config_msg = "Unknown request name";
1825                 return ERR;
1826         }
1828         add_keybinding(keymap, request, key);
1830         return OK;
1833 static int
1834 set_option(const char *opt, char *value)
1836         const char *argv[SIZEOF_ARG];
1837         int argc = 0;
1839         if (!argv_from_string(argv, &argc, value)) {
1840                 config_msg = "Too many option arguments";
1841                 return ERR;
1842         }
1844         if (!strcmp(opt, "color"))
1845                 return option_color_command(argc, argv);
1847         if (!strcmp(opt, "set"))
1848                 return option_set_command(argc, argv);
1850         if (!strcmp(opt, "bind"))
1851                 return option_bind_command(argc, argv);
1853         config_msg = "Unknown option command";
1854         return ERR;
1857 static int
1858 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
1860         int status = OK;
1862         config_lineno++;
1863         config_msg = "Internal error";
1865         /* Check for comment markers, since read_properties() will
1866          * only ensure opt and value are split at first " \t". */
1867         optlen = strcspn(opt, "#");
1868         if (optlen == 0)
1869                 return OK;
1871         if (opt[optlen] != 0) {
1872                 config_msg = "No option value";
1873                 status = ERR;
1875         }  else {
1876                 /* Look for comment endings in the value. */
1877                 size_t len = strcspn(value, "#");
1879                 if (len < valuelen) {
1880                         valuelen = len;
1881                         value[valuelen] = 0;
1882                 }
1884                 status = set_option(opt, value);
1885         }
1887         if (status == ERR) {
1888                 warn("Error on line %d, near '%.*s': %s",
1889                      config_lineno, (int) optlen, opt, config_msg);
1890                 config_errors = TRUE;
1891         }
1893         /* Always keep going if errors are encountered. */
1894         return OK;
1897 static void
1898 load_option_file(const char *path)
1900         struct io io = {};
1902         /* It's OK that the file doesn't exist. */
1903         if (!io_open(&io, "%s", path))
1904                 return;
1906         config_lineno = 0;
1907         config_errors = FALSE;
1909         if (io_load(&io, " \t", read_option) == ERR ||
1910             config_errors == TRUE)
1911                 warn("Errors while loading %s.", path);
1914 static int
1915 load_options(void)
1917         const char *home = getenv("HOME");
1918         const char *tigrc_user = getenv("TIGRC_USER");
1919         const char *tigrc_system = getenv("TIGRC_SYSTEM");
1920         char buf[SIZEOF_STR];
1922         add_builtin_run_requests();
1924         if (!tigrc_system)
1925                 tigrc_system = SYSCONFDIR "/tigrc";
1926         load_option_file(tigrc_system);
1928         if (!tigrc_user) {
1929                 if (!home || !string_format(buf, "%s/.tigrc", home))
1930                         return ERR;
1931                 tigrc_user = buf;
1932         }
1933         load_option_file(tigrc_user);
1935         return OK;
1939 /*
1940  * The viewer
1941  */
1943 struct view;
1944 struct view_ops;
1946 /* The display array of active views and the index of the current view. */
1947 static struct view *display[2];
1948 static unsigned int current_view;
1950 #define foreach_displayed_view(view, i) \
1951         for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1953 #define displayed_views()       (display[1] != NULL ? 2 : 1)
1955 /* Current head and commit ID */
1956 static char ref_blob[SIZEOF_REF]        = "";
1957 static char ref_commit[SIZEOF_REF]      = "HEAD";
1958 static char ref_head[SIZEOF_REF]        = "HEAD";
1960 struct view {
1961         const char *name;       /* View name */
1962         const char *cmd_env;    /* Command line set via environment */
1963         const char *id;         /* Points to either of ref_{head,commit,blob} */
1965         struct view_ops *ops;   /* View operations */
1967         enum keymap keymap;     /* What keymap does this view have */
1968         bool git_dir;           /* Whether the view requires a git directory. */
1970         char ref[SIZEOF_REF];   /* Hovered commit reference */
1971         char vid[SIZEOF_REF];   /* View ID. Set to id member when updating. */
1973         int height, width;      /* The width and height of the main window */
1974         WINDOW *win;            /* The main window */
1975         WINDOW *title;          /* The title window living below the main window */
1977         /* Navigation */
1978         unsigned long offset;   /* Offset of the window top */
1979         unsigned long yoffset;  /* Offset from the window side. */
1980         unsigned long lineno;   /* Current line number */
1981         unsigned long p_offset; /* Previous offset of the window top */
1982         unsigned long p_yoffset;/* Previous offset from the window side */
1983         unsigned long p_lineno; /* Previous current line number */
1984         bool p_restore;         /* Should the previous position be restored. */
1986         /* Searching */
1987         char grep[SIZEOF_STR];  /* Search string */
1988         regex_t *regex;         /* Pre-compiled regexp */
1990         /* If non-NULL, points to the view that opened this view. If this view
1991          * is closed tig will switch back to the parent view. */
1992         struct view *parent;
1994         /* Buffering */
1995         size_t lines;           /* Total number of lines */
1996         struct line *line;      /* Line index */
1997         unsigned int digits;    /* Number of digits in the lines member. */
1999         /* Drawing */
2000         struct line *curline;   /* Line currently being drawn. */
2001         enum line_type curtype; /* Attribute currently used for drawing. */
2002         unsigned long col;      /* Column when drawing. */
2003         bool has_scrolled;      /* View was scrolled. */
2005         /* Loading */
2006         struct io io;
2007         struct io *pipe;
2008         time_t start_time;
2009         time_t update_secs;
2010 };
2012 struct view_ops {
2013         /* What type of content being displayed. Used in the title bar. */
2014         const char *type;
2015         /* Default command arguments. */
2016         const char **argv;
2017         /* Open and reads in all view content. */
2018         bool (*open)(struct view *view);
2019         /* Read one line; updates view->line. */
2020         bool (*read)(struct view *view, char *data);
2021         /* Draw one line; @lineno must be < view->height. */
2022         bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
2023         /* Depending on view handle a special requests. */
2024         enum request (*request)(struct view *view, enum request request, struct line *line);
2025         /* Search for regexp in a line. */
2026         bool (*grep)(struct view *view, struct line *line);
2027         /* Select line */
2028         void (*select)(struct view *view, struct line *line);
2029         /* Prepare view for loading */
2030         bool (*prepare)(struct view *view);
2031 };
2033 static struct view_ops blame_ops;
2034 static struct view_ops blob_ops;
2035 static struct view_ops diff_ops;
2036 static struct view_ops help_ops;
2037 static struct view_ops log_ops;
2038 static struct view_ops main_ops;
2039 static struct view_ops pager_ops;
2040 static struct view_ops stage_ops;
2041 static struct view_ops status_ops;
2042 static struct view_ops tree_ops;
2043 static struct view_ops branch_ops;
2045 #define VIEW_STR(name, env, ref, ops, map, git) \
2046         { name, #env, ref, ops, map, git }
2048 #define VIEW_(id, name, ops, git, ref) \
2049         VIEW_STR(name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
2052 static struct view views[] = {
2053         VIEW_(MAIN,   "main",   &main_ops,   TRUE,  ref_head),
2054         VIEW_(DIFF,   "diff",   &diff_ops,   TRUE,  ref_commit),
2055         VIEW_(LOG,    "log",    &log_ops,    TRUE,  ref_head),
2056         VIEW_(TREE,   "tree",   &tree_ops,   TRUE,  ref_commit),
2057         VIEW_(BLOB,   "blob",   &blob_ops,   TRUE,  ref_blob),
2058         VIEW_(BLAME,  "blame",  &blame_ops,  TRUE,  ref_commit),
2059         VIEW_(BRANCH, "branch", &branch_ops, TRUE,  ref_head),
2060         VIEW_(HELP,   "help",   &help_ops,   FALSE, ""),
2061         VIEW_(PAGER,  "pager",  &pager_ops,  FALSE, "stdin"),
2062         VIEW_(STATUS, "status", &status_ops, TRUE,  ""),
2063         VIEW_(STAGE,  "stage",  &stage_ops,  TRUE,  ""),
2064 };
2066 #define VIEW(req)       (&views[(req) - REQ_OFFSET - 1])
2067 #define VIEW_REQ(view)  ((view) - views + REQ_OFFSET + 1)
2069 #define foreach_view(view, i) \
2070         for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
2072 #define view_is_displayed(view) \
2073         (view == display[0] || view == display[1])
2076 enum line_graphic {
2077         LINE_GRAPHIC_VLINE
2078 };
2080 static chtype line_graphics[] = {
2081         /* LINE_GRAPHIC_VLINE: */ '|'
2082 };
2084 static inline void
2085 set_view_attr(struct view *view, enum line_type type)
2087         if (!view->curline->selected && view->curtype != type) {
2088                 wattrset(view->win, get_line_attr(type));
2089                 wchgat(view->win, -1, 0, type, NULL);
2090                 view->curtype = type;
2091         }
2094 static int
2095 draw_chars(struct view *view, enum line_type type, const char *string,
2096            int max_len, bool use_tilde)
2098         static char out_buffer[BUFSIZ * 2];
2099         int len = 0;
2100         int col = 0;
2101         int trimmed = FALSE;
2102         size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2104         if (max_len <= 0)
2105                 return 0;
2107         len = utf8_length(&string, skip, &col, max_len, &trimmed, use_tilde);
2109         set_view_attr(view, type);
2110         if (len > 0) {
2111                 if (opt_iconv_out != ICONV_NONE) {
2112                         ICONV_CONST char *inbuf = (ICONV_CONST char *) string;
2113                         size_t inlen = len + 1;
2115                         char *outbuf = out_buffer;
2116                         size_t outlen = sizeof(out_buffer);
2118                         size_t ret;
2120                         ret = iconv(opt_iconv_out, &inbuf, &inlen, &outbuf, &outlen);
2121                         if (ret != (size_t) -1) {
2122                                 string = out_buffer;
2123                                 len = sizeof(out_buffer) - outlen;
2124                         }
2125                 }
2127                 waddnstr(view->win, string, len);
2128         }
2129         if (trimmed && use_tilde) {
2130                 set_view_attr(view, LINE_DELIMITER);
2131                 waddch(view->win, '~');
2132                 col++;
2133         }
2135         return col;
2138 static int
2139 draw_space(struct view *view, enum line_type type, int max, int spaces)
2141         static char space[] = "                    ";
2142         int col = 0;
2144         spaces = MIN(max, spaces);
2146         while (spaces > 0) {
2147                 int len = MIN(spaces, sizeof(space) - 1);
2149                 col += draw_chars(view, type, space, len, FALSE);
2150                 spaces -= len;
2151         }
2153         return col;
2156 static bool
2157 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
2159         view->col += draw_chars(view, type, string, view->width + view->yoffset - view->col, trim);
2160         return view->width + view->yoffset <= view->col;
2163 static bool
2164 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
2166         size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2167         int max = view->width + view->yoffset - view->col;
2168         int i;
2170         if (max < size)
2171                 size = max;
2173         set_view_attr(view, type);
2174         /* Using waddch() instead of waddnstr() ensures that
2175          * they'll be rendered correctly for the cursor line. */
2176         for (i = skip; i < size; i++)
2177                 waddch(view->win, graphic[i]);
2179         view->col += size;
2180         if (size < max && skip <= size)
2181                 waddch(view->win, ' ');
2182         view->col++;
2184         return view->width + view->yoffset <= view->col;
2187 static bool
2188 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
2190         int max = MIN(view->width + view->yoffset - view->col, len);
2191         int col;
2193         if (text)
2194                 col = draw_chars(view, type, text, max - 1, trim);
2195         else
2196                 col = draw_space(view, type, max - 1, max - 1);
2198         view->col += col;
2199         view->col += draw_space(view, LINE_DEFAULT, max - col, max - col);
2200         return view->width + view->yoffset <= view->col;
2203 static bool
2204 draw_date(struct view *view, time_t *time)
2206         const char *date = time ? mkdate(time) : "";
2207         int cols = opt_date == DATE_SHORT ? DATE_SHORT_COLS : DATE_COLS;
2209         return draw_field(view, LINE_DATE, date, cols, FALSE);
2212 static bool
2213 draw_author(struct view *view, const char *author)
2215         bool trim = opt_author_cols == 0 || opt_author_cols > 5 || !author;
2217         if (!trim) {
2218                 static char initials[10];
2219                 size_t pos;
2221 #define is_initial_sep(c) (isspace(c) || ispunct(c) || (c) == '@')
2223                 memset(initials, 0, sizeof(initials));
2224                 for (pos = 0; *author && pos < opt_author_cols - 1; author++, pos++) {
2225                         while (is_initial_sep(*author))
2226                                 author++;
2227                         strncpy(&initials[pos], author, sizeof(initials) - 1 - pos);
2228                         while (*author && !is_initial_sep(author[1]))
2229                                 author++;
2230                 }
2232                 author = initials;
2233         }
2235         return draw_field(view, LINE_AUTHOR, author, opt_author_cols, trim);
2238 static bool
2239 draw_mode(struct view *view, mode_t mode)
2241         const char *str;
2243         if (S_ISDIR(mode))
2244                 str = "drwxr-xr-x";
2245         else if (S_ISLNK(mode))
2246                 str = "lrwxrwxrwx";
2247         else if (S_ISGITLINK(mode))
2248                 str = "m---------";
2249         else if (S_ISREG(mode) && mode & S_IXUSR)
2250                 str = "-rwxr-xr-x";
2251         else if (S_ISREG(mode))
2252                 str = "-rw-r--r--";
2253         else
2254                 str = "----------";
2256         return draw_field(view, LINE_MODE, str, STRING_SIZE("-rw-r--r-- "), FALSE);
2259 static bool
2260 draw_lineno(struct view *view, unsigned int lineno)
2262         char number[10];
2263         int digits3 = view->digits < 3 ? 3 : view->digits;
2264         int max = MIN(view->width + view->yoffset - view->col, digits3);
2265         char *text = NULL;
2267         lineno += view->offset + 1;
2268         if (lineno == 1 || (lineno % opt_num_interval) == 0) {
2269                 static char fmt[] = "%1ld";
2271                 fmt[1] = '0' + (view->digits <= 9 ? digits3 : 1);
2272                 if (string_format(number, fmt, lineno))
2273                         text = number;
2274         }
2275         if (text)
2276                 view->col += draw_chars(view, LINE_LINE_NUMBER, text, max, TRUE);
2277         else
2278                 view->col += draw_space(view, LINE_LINE_NUMBER, max, digits3);
2279         return draw_graphic(view, LINE_DEFAULT, &line_graphics[LINE_GRAPHIC_VLINE], 1);
2282 static bool
2283 draw_view_line(struct view *view, unsigned int lineno)
2285         struct line *line;
2286         bool selected = (view->offset + lineno == view->lineno);
2288         assert(view_is_displayed(view));
2290         if (view->offset + lineno >= view->lines)
2291                 return FALSE;
2293         line = &view->line[view->offset + lineno];
2295         wmove(view->win, lineno, 0);
2296         if (line->cleareol)
2297                 wclrtoeol(view->win);
2298         view->col = 0;
2299         view->curline = line;
2300         view->curtype = LINE_NONE;
2301         line->selected = FALSE;
2302         line->dirty = line->cleareol = 0;
2304         if (selected) {
2305                 set_view_attr(view, LINE_CURSOR);
2306                 line->selected = TRUE;
2307                 view->ops->select(view, line);
2308         }
2310         return view->ops->draw(view, line, lineno);
2313 static void
2314 redraw_view_dirty(struct view *view)
2316         bool dirty = FALSE;
2317         int lineno;
2319         for (lineno = 0; lineno < view->height; lineno++) {
2320                 if (view->offset + lineno >= view->lines)
2321                         break;
2322                 if (!view->line[view->offset + lineno].dirty)
2323                         continue;
2324                 dirty = TRUE;
2325                 if (!draw_view_line(view, lineno))
2326                         break;
2327         }
2329         if (!dirty)
2330                 return;
2331         wnoutrefresh(view->win);
2334 static void
2335 redraw_view_from(struct view *view, int lineno)
2337         assert(0 <= lineno && lineno < view->height);
2339         for (; lineno < view->height; lineno++) {
2340                 if (!draw_view_line(view, lineno))
2341                         break;
2342         }
2344         wnoutrefresh(view->win);
2347 static void
2348 redraw_view(struct view *view)
2350         werase(view->win);
2351         redraw_view_from(view, 0);
2355 static void
2356 update_view_title(struct view *view)
2358         char buf[SIZEOF_STR];
2359         char state[SIZEOF_STR];
2360         size_t bufpos = 0, statelen = 0;
2362         assert(view_is_displayed(view));
2364         if (view != VIEW(REQ_VIEW_STATUS) && view->lines) {
2365                 unsigned int view_lines = view->offset + view->height;
2366                 unsigned int lines = view->lines
2367                                    ? MIN(view_lines, view->lines) * 100 / view->lines
2368                                    : 0;
2370                 string_format_from(state, &statelen, " - %s %d of %d (%d%%)",
2371                                    view->ops->type,
2372                                    view->lineno + 1,
2373                                    view->lines,
2374                                    lines);
2376         }
2378         if (view->pipe) {
2379                 time_t secs = time(NULL) - view->start_time;
2381                 /* Three git seconds are a long time ... */
2382                 if (secs > 2)
2383                         string_format_from(state, &statelen, " loading %lds", secs);
2384         }
2386         string_format_from(buf, &bufpos, "[%s]", view->name);
2387         if (*view->ref && bufpos < view->width) {
2388                 size_t refsize = strlen(view->ref);
2389                 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2391                 if (minsize < view->width)
2392                         refsize = view->width - minsize + 7;
2393                 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2394         }
2396         if (statelen && bufpos < view->width) {
2397                 string_format_from(buf, &bufpos, "%s", state);
2398         }
2400         if (view == display[current_view])
2401                 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2402         else
2403                 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2405         mvwaddnstr(view->title, 0, 0, buf, bufpos);
2406         wclrtoeol(view->title);
2407         wnoutrefresh(view->title);
2410 static int
2411 apply_step(double step, int value)
2413         if (step >= 1)
2414                 return (int) step;
2415         value *= step + 0.01;
2416         return value ? value : 1;
2419 static void
2420 resize_display(void)
2422         int offset, i;
2423         struct view *base = display[0];
2424         struct view *view = display[1] ? display[1] : display[0];
2426         /* Setup window dimensions */
2428         getmaxyx(stdscr, base->height, base->width);
2430         /* Make room for the status window. */
2431         base->height -= 1;
2433         if (view != base) {
2434                 /* Horizontal split. */
2435                 view->width   = base->width;
2436                 view->height  = apply_step(opt_scale_split_view, base->height);
2437                 view->height  = MAX(view->height, MIN_VIEW_HEIGHT);
2438                 view->height  = MIN(view->height, base->height - MIN_VIEW_HEIGHT);
2439                 base->height -= view->height;
2441                 /* Make room for the title bar. */
2442                 view->height -= 1;
2443         }
2445         /* Make room for the title bar. */
2446         base->height -= 1;
2448         offset = 0;
2450         foreach_displayed_view (view, i) {
2451                 if (!view->win) {
2452                         view->win = newwin(view->height, 0, offset, 0);
2453                         if (!view->win)
2454                                 die("Failed to create %s view", view->name);
2456                         scrollok(view->win, FALSE);
2458                         view->title = newwin(1, 0, offset + view->height, 0);
2459                         if (!view->title)
2460                                 die("Failed to create title window");
2462                 } else {
2463                         wresize(view->win, view->height, view->width);
2464                         mvwin(view->win,   offset, 0);
2465                         mvwin(view->title, offset + view->height, 0);
2466                 }
2468                 offset += view->height + 1;
2469         }
2472 static void
2473 redraw_display(bool clear)
2475         struct view *view;
2476         int i;
2478         foreach_displayed_view (view, i) {
2479                 if (clear)
2480                         wclear(view->win);
2481                 redraw_view(view);
2482                 update_view_title(view);
2483         }
2486 static void
2487 toggle_enum_option_do(unsigned int *opt, const char *help,
2488                       const struct enum_map *map, size_t size)
2490         *opt = (*opt + 1) % size;
2491         redraw_display(FALSE);
2492         report("Displaying %s %s", enum_name(map[*opt]), help);
2495 #define toggle_enum_option(opt, help, map) \
2496         toggle_enum_option_do(opt, help, map, ARRAY_SIZE(map))
2498 #define toggle_date() toggle_enum_option(&opt_date, "dates", date_map)
2500 static void
2501 toggle_view_option(bool *option, const char *help)
2503         *option = !*option;
2504         redraw_display(FALSE);
2505         report("%sabling %s", *option ? "En" : "Dis", help);
2508 static void
2509 open_option_menu(void)
2511         const struct menu_item menu[] = {
2512                 { '.', "line numbers", &opt_line_number },
2513                 { 'D', "date display", &opt_date },
2514                 { 'A', "author display", &opt_author },
2515                 { 'g', "revision graph display", &opt_rev_graph },
2516                 { 'F', "reference display", &opt_show_refs },
2517                 { 0 }
2518         };
2519         int selected = 0;
2521         if (prompt_menu("Toggle option", menu, &selected)) {
2522                 if (menu[selected].data == &opt_date)
2523                         toggle_date();
2524                 else
2525                         toggle_view_option(menu[selected].data, menu[selected].text);
2526         }
2529 static void
2530 maximize_view(struct view *view)
2532         memset(display, 0, sizeof(display));
2533         current_view = 0;
2534         display[current_view] = view;
2535         resize_display();
2536         redraw_display(FALSE);
2537         report("");
2541 /*
2542  * Navigation
2543  */
2545 static bool
2546 goto_view_line(struct view *view, unsigned long offset, unsigned long lineno)
2548         if (lineno >= view->lines)
2549                 lineno = view->lines > 0 ? view->lines - 1 : 0;
2551         if (offset > lineno || offset + view->height <= lineno) {
2552                 unsigned long half = view->height / 2;
2554                 if (lineno > half)
2555                         offset = lineno - half;
2556                 else
2557                         offset = 0;
2558         }
2560         if (offset != view->offset || lineno != view->lineno) {
2561                 view->offset = offset;
2562                 view->lineno = lineno;
2563                 return TRUE;
2564         }
2566         return FALSE;
2569 /* Scrolling backend */
2570 static void
2571 do_scroll_view(struct view *view, int lines)
2573         bool redraw_current_line = FALSE;
2575         /* The rendering expects the new offset. */
2576         view->offset += lines;
2578         assert(0 <= view->offset && view->offset < view->lines);
2579         assert(lines);
2581         /* Move current line into the view. */
2582         if (view->lineno < view->offset) {
2583                 view->lineno = view->offset;
2584                 redraw_current_line = TRUE;
2585         } else if (view->lineno >= view->offset + view->height) {
2586                 view->lineno = view->offset + view->height - 1;
2587                 redraw_current_line = TRUE;
2588         }
2590         assert(view->offset <= view->lineno && view->lineno < view->lines);
2592         /* Redraw the whole screen if scrolling is pointless. */
2593         if (view->height < ABS(lines)) {
2594                 redraw_view(view);
2596         } else {
2597                 int line = lines > 0 ? view->height - lines : 0;
2598                 int end = line + ABS(lines);
2600                 scrollok(view->win, TRUE);
2601                 wscrl(view->win, lines);
2602                 scrollok(view->win, FALSE);
2604                 while (line < end && draw_view_line(view, line))
2605                         line++;
2607                 if (redraw_current_line)
2608                         draw_view_line(view, view->lineno - view->offset);
2609                 wnoutrefresh(view->win);
2610         }
2612         view->has_scrolled = TRUE;
2613         report("");
2616 /* Scroll frontend */
2617 static void
2618 scroll_view(struct view *view, enum request request)
2620         int lines = 1;
2622         assert(view_is_displayed(view));
2624         switch (request) {
2625         case REQ_SCROLL_LEFT:
2626                 if (view->yoffset == 0) {
2627                         report("Cannot scroll beyond the first column");
2628                         return;
2629                 }
2630                 if (view->yoffset <= apply_step(opt_hscroll, view->width))
2631                         view->yoffset = 0;
2632                 else
2633                         view->yoffset -= apply_step(opt_hscroll, view->width);
2634                 redraw_view_from(view, 0);
2635                 report("");
2636                 return;
2637         case REQ_SCROLL_RIGHT:
2638                 view->yoffset += apply_step(opt_hscroll, view->width);
2639                 redraw_view(view);
2640                 report("");
2641                 return;
2642         case REQ_SCROLL_PAGE_DOWN:
2643                 lines = view->height;
2644         case REQ_SCROLL_LINE_DOWN:
2645                 if (view->offset + lines > view->lines)
2646                         lines = view->lines - view->offset;
2648                 if (lines == 0 || view->offset + view->height >= view->lines) {
2649                         report("Cannot scroll beyond the last line");
2650                         return;
2651                 }
2652                 break;
2654         case REQ_SCROLL_PAGE_UP:
2655                 lines = view->height;
2656         case REQ_SCROLL_LINE_UP:
2657                 if (lines > view->offset)
2658                         lines = view->offset;
2660                 if (lines == 0) {
2661                         report("Cannot scroll beyond the first line");
2662                         return;
2663                 }
2665                 lines = -lines;
2666                 break;
2668         default:
2669                 die("request %d not handled in switch", request);
2670         }
2672         do_scroll_view(view, lines);
2675 /* Cursor moving */
2676 static void
2677 move_view(struct view *view, enum request request)
2679         int scroll_steps = 0;
2680         int steps;
2682         switch (request) {
2683         case REQ_MOVE_FIRST_LINE:
2684                 steps = -view->lineno;
2685                 break;
2687         case REQ_MOVE_LAST_LINE:
2688                 steps = view->lines - view->lineno - 1;
2689                 break;
2691         case REQ_MOVE_PAGE_UP:
2692                 steps = view->height > view->lineno
2693                       ? -view->lineno : -view->height;
2694                 break;
2696         case REQ_MOVE_PAGE_DOWN:
2697                 steps = view->lineno + view->height >= view->lines
2698                       ? view->lines - view->lineno - 1 : view->height;
2699                 break;
2701         case REQ_MOVE_UP:
2702                 steps = -1;
2703                 break;
2705         case REQ_MOVE_DOWN:
2706                 steps = 1;
2707                 break;
2709         default:
2710                 die("request %d not handled in switch", request);
2711         }
2713         if (steps <= 0 && view->lineno == 0) {
2714                 report("Cannot move beyond the first line");
2715                 return;
2717         } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2718                 report("Cannot move beyond the last line");
2719                 return;
2720         }
2722         /* Move the current line */
2723         view->lineno += steps;
2724         assert(0 <= view->lineno && view->lineno < view->lines);
2726         /* Check whether the view needs to be scrolled */
2727         if (view->lineno < view->offset ||
2728             view->lineno >= view->offset + view->height) {
2729                 scroll_steps = steps;
2730                 if (steps < 0 && -steps > view->offset) {
2731                         scroll_steps = -view->offset;
2733                 } else if (steps > 0) {
2734                         if (view->lineno == view->lines - 1 &&
2735                             view->lines > view->height) {
2736                                 scroll_steps = view->lines - view->offset - 1;
2737                                 if (scroll_steps >= view->height)
2738                                         scroll_steps -= view->height - 1;
2739                         }
2740                 }
2741         }
2743         if (!view_is_displayed(view)) {
2744                 view->offset += scroll_steps;
2745                 assert(0 <= view->offset && view->offset < view->lines);
2746                 view->ops->select(view, &view->line[view->lineno]);
2747                 return;
2748         }
2750         /* Repaint the old "current" line if we be scrolling */
2751         if (ABS(steps) < view->height)
2752                 draw_view_line(view, view->lineno - steps - view->offset);
2754         if (scroll_steps) {
2755                 do_scroll_view(view, scroll_steps);
2756                 return;
2757         }
2759         /* Draw the current line */
2760         draw_view_line(view, view->lineno - view->offset);
2762         wnoutrefresh(view->win);
2763         report("");
2767 /*
2768  * Searching
2769  */
2771 static void search_view(struct view *view, enum request request);
2773 static bool
2774 grep_text(struct view *view, const char *text[])
2776         regmatch_t pmatch;
2777         size_t i;
2779         for (i = 0; text[i]; i++)
2780                 if (*text[i] &&
2781                     regexec(view->regex, text[i], 1, &pmatch, 0) != REG_NOMATCH)
2782                         return TRUE;
2783         return FALSE;
2786 static void
2787 select_view_line(struct view *view, unsigned long lineno)
2789         unsigned long old_lineno = view->lineno;
2790         unsigned long old_offset = view->offset;
2792         if (goto_view_line(view, view->offset, lineno)) {
2793                 if (view_is_displayed(view)) {
2794                         if (old_offset != view->offset) {
2795                                 redraw_view(view);
2796                         } else {
2797                                 draw_view_line(view, old_lineno - view->offset);
2798                                 draw_view_line(view, view->lineno - view->offset);
2799                                 wnoutrefresh(view->win);
2800                         }
2801                 } else {
2802                         view->ops->select(view, &view->line[view->lineno]);
2803                 }
2804         }
2807 static void
2808 find_next(struct view *view, enum request request)
2810         unsigned long lineno = view->lineno;
2811         int direction;
2813         if (!*view->grep) {
2814                 if (!*opt_search)
2815                         report("No previous search");
2816                 else
2817                         search_view(view, request);
2818                 return;
2819         }
2821         switch (request) {
2822         case REQ_SEARCH:
2823         case REQ_FIND_NEXT:
2824                 direction = 1;
2825                 break;
2827         case REQ_SEARCH_BACK:
2828         case REQ_FIND_PREV:
2829                 direction = -1;
2830                 break;
2832         default:
2833                 return;
2834         }
2836         if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
2837                 lineno += direction;
2839         /* Note, lineno is unsigned long so will wrap around in which case it
2840          * will become bigger than view->lines. */
2841         for (; lineno < view->lines; lineno += direction) {
2842                 if (view->ops->grep(view, &view->line[lineno])) {
2843                         select_view_line(view, lineno);
2844                         report("Line %ld matches '%s'", lineno + 1, view->grep);
2845                         return;
2846                 }
2847         }
2849         report("No match found for '%s'", view->grep);
2852 static void
2853 search_view(struct view *view, enum request request)
2855         int regex_err;
2857         if (view->regex) {
2858                 regfree(view->regex);
2859                 *view->grep = 0;
2860         } else {
2861                 view->regex = calloc(1, sizeof(*view->regex));
2862                 if (!view->regex)
2863                         return;
2864         }
2866         regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
2867         if (regex_err != 0) {
2868                 char buf[SIZEOF_STR] = "unknown error";
2870                 regerror(regex_err, view->regex, buf, sizeof(buf));
2871                 report("Search failed: %s", buf);
2872                 return;
2873         }
2875         string_copy(view->grep, opt_search);
2877         find_next(view, request);
2880 /*
2881  * Incremental updating
2882  */
2884 static void
2885 reset_view(struct view *view)
2887         int i;
2889         for (i = 0; i < view->lines; i++)
2890                 free(view->line[i].data);
2891         free(view->line);
2893         view->p_offset = view->offset;
2894         view->p_yoffset = view->yoffset;
2895         view->p_lineno = view->lineno;
2897         view->line = NULL;
2898         view->offset = 0;
2899         view->yoffset = 0;
2900         view->lines  = 0;
2901         view->lineno = 0;
2902         view->vid[0] = 0;
2903         view->update_secs = 0;
2906 static void
2907 free_argv(const char *argv[])
2909         int argc;
2911         for (argc = 0; argv[argc]; argc++)
2912                 free((void *) argv[argc]);
2915 static bool
2916 format_argv(const char *dst_argv[], const char *src_argv[], enum format_flags flags)
2918         char buf[SIZEOF_STR];
2919         int argc;
2920         bool noreplace = flags == FORMAT_NONE;
2922         free_argv(dst_argv);
2924         for (argc = 0; src_argv[argc]; argc++) {
2925                 const char *arg = src_argv[argc];
2926                 size_t bufpos = 0;
2928                 while (arg) {
2929                         char *next = strstr(arg, "%(");
2930                         int len = next - arg;
2931                         const char *value;
2933                         if (!next || noreplace) {
2934                                 if (flags == FORMAT_DASH && !strcmp(arg, "--"))
2935                                         noreplace = TRUE;
2936                                 len = strlen(arg);
2937                                 value = "";
2939                         } else if (!prefixcmp(next, "%(directory)")) {
2940                                 value = opt_path;
2942                         } else if (!prefixcmp(next, "%(file)")) {
2943                                 value = opt_file;
2945                         } else if (!prefixcmp(next, "%(ref)")) {
2946                                 value = *opt_ref ? opt_ref : "HEAD";
2948                         } else if (!prefixcmp(next, "%(head)")) {
2949                                 value = ref_head;
2951                         } else if (!prefixcmp(next, "%(commit)")) {
2952                                 value = ref_commit;
2954                         } else if (!prefixcmp(next, "%(blob)")) {
2955                                 value = ref_blob;
2957                         } else {
2958                                 report("Unknown replacement: `%s`", next);
2959                                 return FALSE;
2960                         }
2962                         if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
2963                                 return FALSE;
2965                         arg = next && !noreplace ? strchr(next, ')') + 1 : NULL;
2966                 }
2968                 dst_argv[argc] = strdup(buf);
2969                 if (!dst_argv[argc])
2970                         break;
2971         }
2973         dst_argv[argc] = NULL;
2975         return src_argv[argc] == NULL;
2978 static bool
2979 restore_view_position(struct view *view)
2981         if (!view->p_restore || (view->pipe && view->lines <= view->p_lineno))
2982                 return FALSE;
2984         /* Changing the view position cancels the restoring. */
2985         /* FIXME: Changing back to the first line is not detected. */
2986         if (view->offset != 0 || view->lineno != 0) {
2987                 view->p_restore = FALSE;
2988                 return FALSE;
2989         }
2991         if (goto_view_line(view, view->p_offset, view->p_lineno) &&
2992             view_is_displayed(view))
2993                 werase(view->win);
2995         view->yoffset = view->p_yoffset;
2996         view->p_restore = FALSE;
2998         return TRUE;
3001 static void
3002 end_update(struct view *view, bool force)
3004         if (!view->pipe)
3005                 return;
3006         while (!view->ops->read(view, NULL))
3007                 if (!force)
3008                         return;
3009         set_nonblocking_input(FALSE);
3010         if (force)
3011                 kill_io(view->pipe);
3012         done_io(view->pipe);
3013         view->pipe = NULL;
3016 static void
3017 setup_update(struct view *view, const char *vid)
3019         set_nonblocking_input(TRUE);
3020         reset_view(view);
3021         string_copy_rev(view->vid, vid);
3022         view->pipe = &view->io;
3023         view->start_time = time(NULL);
3026 static bool
3027 prepare_update(struct view *view, const char *argv[], const char *dir,
3028                enum format_flags flags)
3030         if (view->pipe)
3031                 end_update(view, TRUE);
3032         return init_io_rd(&view->io, argv, dir, flags);
3035 static bool
3036 prepare_update_file(struct view *view, const char *name)
3038         if (view->pipe)
3039                 end_update(view, TRUE);
3040         return io_open(&view->io, "%s", name);
3043 static bool
3044 begin_update(struct view *view, bool refresh)
3046         if (view->pipe)
3047                 end_update(view, TRUE);
3049         if (!refresh) {
3050                 if (view->ops->prepare) {
3051                         if (!view->ops->prepare(view))
3052                                 return FALSE;
3053                 } else if (!init_io_rd(&view->io, view->ops->argv, NULL, FORMAT_ALL)) {
3054                         return FALSE;
3055                 }
3057                 /* Put the current ref_* value to the view title ref
3058                  * member. This is needed by the blob view. Most other
3059                  * views sets it automatically after loading because the
3060                  * first line is a commit line. */
3061                 string_copy_rev(view->ref, view->id);
3062         }
3064         if (!start_io(&view->io))
3065                 return FALSE;
3067         setup_update(view, view->id);
3069         return TRUE;
3072 static bool
3073 update_view(struct view *view)
3075         char out_buffer[BUFSIZ * 2];
3076         char *line;
3077         /* Clear the view and redraw everything since the tree sorting
3078          * might have rearranged things. */
3079         bool redraw = view->lines == 0;
3080         bool can_read = TRUE;
3082         if (!view->pipe)
3083                 return TRUE;
3085         if (!io_can_read(view->pipe)) {
3086                 if (view->lines == 0 && view_is_displayed(view)) {
3087                         time_t secs = time(NULL) - view->start_time;
3089                         if (secs > 1 && secs > view->update_secs) {
3090                                 if (view->update_secs == 0)
3091                                         redraw_view(view);
3092                                 update_view_title(view);
3093                                 view->update_secs = secs;
3094                         }
3095                 }
3096                 return TRUE;
3097         }
3099         for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
3100                 if (opt_iconv_in != ICONV_NONE) {
3101                         ICONV_CONST char *inbuf = line;
3102                         size_t inlen = strlen(line) + 1;
3104                         char *outbuf = out_buffer;
3105                         size_t outlen = sizeof(out_buffer);
3107                         size_t ret;
3109                         ret = iconv(opt_iconv_in, &inbuf, &inlen, &outbuf, &outlen);
3110                         if (ret != (size_t) -1)
3111                                 line = out_buffer;
3112                 }
3114                 if (!view->ops->read(view, line)) {
3115                         report("Allocation failure");
3116                         end_update(view, TRUE);
3117                         return FALSE;
3118                 }
3119         }
3121         {
3122                 unsigned long lines = view->lines;
3123                 int digits;
3125                 for (digits = 0; lines; digits++)
3126                         lines /= 10;
3128                 /* Keep the displayed view in sync with line number scaling. */
3129                 if (digits != view->digits) {
3130                         view->digits = digits;
3131                         if (opt_line_number || view == VIEW(REQ_VIEW_BLAME))
3132                                 redraw = TRUE;
3133                 }
3134         }
3136         if (io_error(view->pipe)) {
3137                 report("Failed to read: %s", io_strerror(view->pipe));
3138                 end_update(view, TRUE);
3140         } else if (io_eof(view->pipe)) {
3141                 report("");
3142                 end_update(view, FALSE);
3143         }
3145         if (restore_view_position(view))
3146                 redraw = TRUE;
3148         if (!view_is_displayed(view))
3149                 return TRUE;
3151         if (redraw)
3152                 redraw_view_from(view, 0);
3153         else
3154                 redraw_view_dirty(view);
3156         /* Update the title _after_ the redraw so that if the redraw picks up a
3157          * commit reference in view->ref it'll be available here. */
3158         update_view_title(view);
3159         return TRUE;
3162 DEFINE_ALLOCATOR(realloc_lines, struct line, 256)
3164 static struct line *
3165 add_line_data(struct view *view, void *data, enum line_type type)
3167         struct line *line;
3169         if (!realloc_lines(&view->line, view->lines, 1))
3170                 return NULL;
3172         line = &view->line[view->lines++];
3173         memset(line, 0, sizeof(*line));
3174         line->type = type;
3175         line->data = data;
3176         line->dirty = 1;
3178         return line;
3181 static struct line *
3182 add_line_text(struct view *view, const char *text, enum line_type type)
3184         char *data = text ? strdup(text) : NULL;
3186         return data ? add_line_data(view, data, type) : NULL;
3189 static struct line *
3190 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
3192         char buf[SIZEOF_STR];
3193         va_list args;
3195         va_start(args, fmt);
3196         if (vsnprintf(buf, sizeof(buf), fmt, args) >= sizeof(buf))
3197                 buf[0] = 0;
3198         va_end(args);
3200         return buf[0] ? add_line_text(view, buf, type) : NULL;
3203 /*
3204  * View opening
3205  */
3207 enum open_flags {
3208         OPEN_DEFAULT = 0,       /* Use default view switching. */
3209         OPEN_SPLIT = 1,         /* Split current view. */
3210         OPEN_RELOAD = 4,        /* Reload view even if it is the current. */
3211         OPEN_REFRESH = 16,      /* Refresh view using previous command. */
3212         OPEN_PREPARED = 32,     /* Open already prepared command. */
3213 };
3215 static void
3216 open_view(struct view *prev, enum request request, enum open_flags flags)
3218         bool split = !!(flags & OPEN_SPLIT);
3219         bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
3220         bool nomaximize = !!(flags & OPEN_REFRESH);
3221         struct view *view = VIEW(request);
3222         int nviews = displayed_views();
3223         struct view *base_view = display[0];
3225         if (view == prev && nviews == 1 && !reload) {
3226                 report("Already in %s view", view->name);
3227                 return;
3228         }
3230         if (view->git_dir && !opt_git_dir[0]) {
3231                 report("The %s view is disabled in pager view", view->name);
3232                 return;
3233         }
3235         if (split) {
3236                 display[1] = view;
3237                 current_view = 1;
3238         } else if (!nomaximize) {
3239                 /* Maximize the current view. */
3240                 memset(display, 0, sizeof(display));
3241                 current_view = 0;
3242                 display[current_view] = view;
3243         }
3245         /* No parent signals that this is the first loaded view. */
3246         if (prev && view != prev) {
3247                 view->parent = prev;
3248         }
3250         /* Resize the view when switching between split- and full-screen,
3251          * or when switching between two different full-screen views. */
3252         if (nviews != displayed_views() ||
3253             (nviews == 1 && base_view != display[0]))
3254                 resize_display();
3256         if (view->ops->open) {
3257                 if (view->pipe)
3258                         end_update(view, TRUE);
3259                 if (!view->ops->open(view)) {
3260                         report("Failed to load %s view", view->name);
3261                         return;
3262                 }
3263                 restore_view_position(view);
3265         } else if ((reload || strcmp(view->vid, view->id)) &&
3266                    !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
3267                 report("Failed to load %s view", view->name);
3268                 return;
3269         }
3271         if (split && prev->lineno - prev->offset >= prev->height) {
3272                 /* Take the title line into account. */
3273                 int lines = prev->lineno - prev->offset - prev->height + 1;
3275                 /* Scroll the view that was split if the current line is
3276                  * outside the new limited view. */
3277                 do_scroll_view(prev, lines);
3278         }
3280         if (prev && view != prev && split && view_is_displayed(prev)) {
3281                 /* "Blur" the previous view. */
3282                 update_view_title(prev);
3283         }
3285         if (view->pipe && view->lines == 0) {
3286                 /* Clear the old view and let the incremental updating refill
3287                  * the screen. */
3288                 werase(view->win);
3289                 view->p_restore = flags & (OPEN_RELOAD | OPEN_REFRESH);
3290                 report("");
3291         } else if (view_is_displayed(view)) {
3292                 redraw_view(view);
3293                 report("");
3294         }
3297 static void
3298 open_external_viewer(const char *argv[], const char *dir)
3300         def_prog_mode();           /* save current tty modes */
3301         endwin();                  /* restore original tty modes */
3302         run_io_fg(argv, dir);
3303         fprintf(stderr, "Press Enter to continue");
3304         getc(opt_tty);
3305         reset_prog_mode();
3306         redraw_display(TRUE);
3309 static void
3310 open_mergetool(const char *file)
3312         const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
3314         open_external_viewer(mergetool_argv, opt_cdup);
3317 static void
3318 open_editor(bool from_root, const char *file)
3320         const char *editor_argv[] = { "vi", file, NULL };
3321         const char *editor;
3323         editor = getenv("GIT_EDITOR");
3324         if (!editor && *opt_editor)
3325                 editor = opt_editor;
3326         if (!editor)
3327                 editor = getenv("VISUAL");
3328         if (!editor)
3329                 editor = getenv("EDITOR");
3330         if (!editor)
3331                 editor = "vi";
3333         editor_argv[0] = editor;
3334         open_external_viewer(editor_argv, from_root ? opt_cdup : NULL);
3337 static void
3338 open_run_request(enum request request)
3340         struct run_request *req = get_run_request(request);
3341         const char *argv[ARRAY_SIZE(req->argv)] = { NULL };
3343         if (!req) {
3344                 report("Unknown run request");
3345                 return;
3346         }
3348         if (format_argv(argv, req->argv, FORMAT_ALL))
3349                 open_external_viewer(argv, NULL);
3350         free_argv(argv);
3353 /*
3354  * User request switch noodle
3355  */
3357 static int
3358 view_driver(struct view *view, enum request request)
3360         int i;
3362         if (request == REQ_NONE)
3363                 return TRUE;
3365         if (request > REQ_NONE) {
3366                 open_run_request(request);
3367                 /* FIXME: When all views can refresh always do this. */
3368                 if (view == VIEW(REQ_VIEW_STATUS) ||
3369                     view == VIEW(REQ_VIEW_MAIN) ||
3370                     view == VIEW(REQ_VIEW_LOG) ||
3371                     view == VIEW(REQ_VIEW_BRANCH) ||
3372                     view == VIEW(REQ_VIEW_STAGE))
3373                         request = REQ_REFRESH;
3374                 else
3375                         return TRUE;
3376         }
3378         if (view && view->lines) {
3379                 request = view->ops->request(view, request, &view->line[view->lineno]);
3380                 if (request == REQ_NONE)
3381                         return TRUE;
3382         }
3384         switch (request) {
3385         case REQ_MOVE_UP:
3386         case REQ_MOVE_DOWN:
3387         case REQ_MOVE_PAGE_UP:
3388         case REQ_MOVE_PAGE_DOWN:
3389         case REQ_MOVE_FIRST_LINE:
3390         case REQ_MOVE_LAST_LINE:
3391                 move_view(view, request);
3392                 break;
3394         case REQ_SCROLL_LEFT:
3395         case REQ_SCROLL_RIGHT:
3396         case REQ_SCROLL_LINE_DOWN:
3397         case REQ_SCROLL_LINE_UP:
3398         case REQ_SCROLL_PAGE_DOWN:
3399         case REQ_SCROLL_PAGE_UP:
3400                 scroll_view(view, request);
3401                 break;
3403         case REQ_VIEW_BLAME:
3404                 if (!opt_file[0]) {
3405                         report("No file chosen, press %s to open tree view",
3406                                get_key(view->keymap, REQ_VIEW_TREE));
3407                         break;
3408                 }
3409                 open_view(view, request, OPEN_DEFAULT);
3410                 break;
3412         case REQ_VIEW_BLOB:
3413                 if (!ref_blob[0]) {
3414                         report("No file chosen, press %s to open tree view",
3415                                get_key(view->keymap, REQ_VIEW_TREE));
3416                         break;
3417                 }
3418                 open_view(view, request, OPEN_DEFAULT);
3419                 break;
3421         case REQ_VIEW_PAGER:
3422                 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3423                         report("No pager content, press %s to run command from prompt",
3424                                get_key(view->keymap, REQ_PROMPT));
3425                         break;
3426                 }
3427                 open_view(view, request, OPEN_DEFAULT);
3428                 break;
3430         case REQ_VIEW_STAGE:
3431                 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3432                         report("No stage content, press %s to open the status view and choose file",
3433                                get_key(view->keymap, REQ_VIEW_STATUS));
3434                         break;
3435                 }
3436                 open_view(view, request, OPEN_DEFAULT);
3437                 break;
3439         case REQ_VIEW_STATUS:
3440                 if (opt_is_inside_work_tree == FALSE) {
3441                         report("The status view requires a working tree");
3442                         break;
3443                 }
3444                 open_view(view, request, OPEN_DEFAULT);
3445                 break;
3447         case REQ_VIEW_MAIN:
3448         case REQ_VIEW_DIFF:
3449         case REQ_VIEW_LOG:
3450         case REQ_VIEW_TREE:
3451         case REQ_VIEW_HELP:
3452         case REQ_VIEW_BRANCH:
3453                 open_view(view, request, OPEN_DEFAULT);
3454                 break;
3456         case REQ_NEXT:
3457         case REQ_PREVIOUS:
3458                 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3460                 if ((view == VIEW(REQ_VIEW_DIFF) &&
3461                      view->parent == VIEW(REQ_VIEW_MAIN)) ||
3462                    (view == VIEW(REQ_VIEW_DIFF) &&
3463                      view->parent == VIEW(REQ_VIEW_BLAME)) ||
3464                    (view == VIEW(REQ_VIEW_STAGE) &&
3465                      view->parent == VIEW(REQ_VIEW_STATUS)) ||
3466                    (view == VIEW(REQ_VIEW_BLOB) &&
3467                      view->parent == VIEW(REQ_VIEW_TREE)) ||
3468                    (view == VIEW(REQ_VIEW_MAIN) &&
3469                      view->parent == VIEW(REQ_VIEW_BRANCH))) {
3470                         int line;
3472                         view = view->parent;
3473                         line = view->lineno;
3474                         move_view(view, request);
3475                         if (view_is_displayed(view))
3476                                 update_view_title(view);
3477                         if (line != view->lineno)
3478                                 view->ops->request(view, REQ_ENTER,
3479                                                    &view->line[view->lineno]);
3481                 } else {
3482                         move_view(view, request);
3483                 }
3484                 break;
3486         case REQ_VIEW_NEXT:
3487         {
3488                 int nviews = displayed_views();
3489                 int next_view = (current_view + 1) % nviews;
3491                 if (next_view == current_view) {
3492                         report("Only one view is displayed");
3493                         break;
3494                 }
3496                 current_view = next_view;
3497                 /* Blur out the title of the previous view. */
3498                 update_view_title(view);
3499                 report("");
3500                 break;
3501         }
3502         case REQ_REFRESH:
3503                 report("Refreshing is not yet supported for the %s view", view->name);
3504                 break;
3506         case REQ_MAXIMIZE:
3507                 if (displayed_views() == 2)
3508                         maximize_view(view);
3509                 break;
3511         case REQ_OPTIONS:
3512                 open_option_menu();
3513                 break;
3515         case REQ_TOGGLE_LINENO:
3516                 toggle_view_option(&opt_line_number, "line numbers");
3517                 break;
3519         case REQ_TOGGLE_DATE:
3520                 toggle_date();
3521                 break;
3523         case REQ_TOGGLE_AUTHOR:
3524                 toggle_view_option(&opt_author, "author display");
3525                 break;
3527         case REQ_TOGGLE_REV_GRAPH:
3528                 toggle_view_option(&opt_rev_graph, "revision graph display");
3529                 break;
3531         case REQ_TOGGLE_REFS:
3532                 toggle_view_option(&opt_show_refs, "reference display");
3533                 break;
3535         case REQ_TOGGLE_SORT_FIELD:
3536         case REQ_TOGGLE_SORT_ORDER:
3537                 report("Sorting is not yet supported for the %s view", view->name);
3538                 break;
3540         case REQ_SEARCH:
3541         case REQ_SEARCH_BACK:
3542                 search_view(view, request);
3543                 break;
3545         case REQ_FIND_NEXT:
3546         case REQ_FIND_PREV:
3547                 find_next(view, request);
3548                 break;
3550         case REQ_STOP_LOADING:
3551                 for (i = 0; i < ARRAY_SIZE(views); i++) {
3552                         view = &views[i];
3553                         if (view->pipe)
3554                                 report("Stopped loading the %s view", view->name),
3555                         end_update(view, TRUE);
3556                 }
3557                 break;
3559         case REQ_SHOW_VERSION:
3560                 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3561                 return TRUE;
3563         case REQ_SCREEN_REDRAW:
3564                 redraw_display(TRUE);
3565                 break;
3567         case REQ_EDIT:
3568                 report("Nothing to edit");
3569                 break;
3571         case REQ_ENTER:
3572                 report("Nothing to enter");
3573                 break;
3575         case REQ_VIEW_CLOSE:
3576                 /* XXX: Mark closed views by letting view->parent point to the
3577                  * view itself. Parents to closed view should never be
3578                  * followed. */
3579                 if (view->parent &&
3580                     view->parent->parent != view->parent) {
3581                         maximize_view(view->parent);
3582                         view->parent = view;
3583                         break;
3584                 }
3585                 /* Fall-through */
3586         case REQ_QUIT:
3587                 return FALSE;
3589         default:
3590                 report("Unknown key, press %s for help",
3591                        get_key(view->keymap, REQ_VIEW_HELP));
3592                 return TRUE;
3593         }
3595         return TRUE;
3599 /*
3600  * View backend utilities
3601  */
3603 enum sort_field {
3604         ORDERBY_NAME,
3605         ORDERBY_DATE,
3606         ORDERBY_AUTHOR,
3607 };
3609 struct sort_state {
3610         const enum sort_field *fields;
3611         size_t size, current;
3612         bool reverse;
3613 };
3615 #define SORT_STATE(fields) { fields, ARRAY_SIZE(fields), 0 }
3616 #define get_sort_field(state) ((state).fields[(state).current])
3617 #define sort_order(state, result) ((state).reverse ? -(result) : (result))
3619 static void
3620 sort_view(struct view *view, enum request request, struct sort_state *state,
3621           int (*compare)(const void *, const void *))
3623         switch (request) {
3624         case REQ_TOGGLE_SORT_FIELD:
3625                 state->current = (state->current + 1) % state->size;
3626                 break;
3628         case REQ_TOGGLE_SORT_ORDER:
3629                 state->reverse = !state->reverse;
3630                 break;
3631         default:
3632                 die("Not a sort request");
3633         }
3635         qsort(view->line, view->lines, sizeof(*view->line), compare);
3636         redraw_view(view);
3639 DEFINE_ALLOCATOR(realloc_authors, const char *, 256)
3641 /* Small author cache to reduce memory consumption. It uses binary
3642  * search to lookup or find place to position new entries. No entries
3643  * are ever freed. */
3644 static const char *
3645 get_author(const char *name)
3647         static const char **authors;
3648         static size_t authors_size;
3649         int from = 0, to = authors_size - 1;
3651         while (from <= to) {
3652                 size_t pos = (to + from) / 2;
3653                 int cmp = strcmp(name, authors[pos]);
3655                 if (!cmp)
3656                         return authors[pos];
3658                 if (cmp < 0)
3659                         to = pos - 1;
3660                 else
3661                         from = pos + 1;
3662         }
3664         if (!realloc_authors(&authors, authors_size, 1))
3665                 return NULL;
3666         name = strdup(name);
3667         if (!name)
3668                 return NULL;
3670         memmove(authors + from + 1, authors + from, (authors_size - from) * sizeof(*authors));
3671         authors[from] = name;
3672         authors_size++;
3674         return name;
3677 static void
3678 parse_timezone(time_t *time, const char *zone)
3680         long tz;
3682         tz  = ('0' - zone[1]) * 60 * 60 * 10;
3683         tz += ('0' - zone[2]) * 60 * 60;
3684         tz += ('0' - zone[3]) * 60;
3685         tz += ('0' - zone[4]);
3687         if (zone[0] == '-')
3688                 tz = -tz;
3690         *time -= tz;
3693 /* Parse author lines where the name may be empty:
3694  *      author  <email@address.tld> 1138474660 +0100
3695  */
3696 static void
3697 parse_author_line(char *ident, const char **author, time_t *time)
3699         char *nameend = strchr(ident, '<');
3700         char *emailend = strchr(ident, '>');
3702         if (nameend && emailend)
3703                 *nameend = *emailend = 0;
3704         ident = chomp_string(ident);
3705         if (!*ident) {
3706                 if (nameend)
3707                         ident = chomp_string(nameend + 1);
3708                 if (!*ident)
3709                         ident = "Unknown";
3710         }
3712         *author = get_author(ident);
3714         /* Parse epoch and timezone */
3715         if (emailend && emailend[1] == ' ') {
3716                 char *secs = emailend + 2;
3717                 char *zone = strchr(secs, ' ');
3719                 *time = (time_t) atol(secs);
3721                 if (zone && strlen(zone) == STRING_SIZE(" +0700"))
3722                         parse_timezone(time, zone + 1);
3723         }
3726 static bool
3727 open_commit_parent_menu(char buf[SIZEOF_STR], int *parents)
3729         char rev[SIZEOF_REV];
3730         const char *revlist_argv[] = {
3731                 "git", "log", "--no-color", "-1", "--pretty=format:%s", rev, NULL
3732         };
3733         struct menu_item *items;
3734         char text[SIZEOF_STR];
3735         bool ok = TRUE;
3736         int i;
3738         items = calloc(*parents + 1, sizeof(*items));
3739         if (!items)
3740                 return FALSE;
3742         for (i = 0; i < *parents; i++) {
3743                 string_copy_rev(rev, &buf[SIZEOF_REV * i]);
3744                 if (!run_io_buf(revlist_argv, text, sizeof(text)) ||
3745                     !(items[i].text = strdup(text))) {
3746                         ok = FALSE;
3747                         break;
3748                 }
3749         }
3751         if (ok) {
3752                 *parents = 0;
3753                 ok = prompt_menu("Select parent", items, parents);
3754         }
3755         for (i = 0; items[i].text; i++)
3756                 free((char *) items[i].text);
3757         free(items);
3758         return ok;
3761 static bool
3762 select_commit_parent(const char *id, char rev[SIZEOF_REV], const char *path)
3764         char buf[SIZEOF_STR * 4];
3765         const char *revlist_argv[] = {
3766                 "git", "log", "--no-color", "-1",
3767                         "--pretty=format:%P", id, "--", path, NULL
3768         };
3769         int parents;
3771         if (!run_io_buf(revlist_argv, buf, sizeof(buf)) ||
3772             (parents = strlen(buf) / 40) < 0) {
3773                 report("Failed to get parent information");
3774                 return FALSE;
3776         } else if (parents == 0) {
3777                 if (path)
3778                         report("Path '%s' does not exist in the parent", path);
3779                 else
3780                         report("The selected commit has no parents");
3781                 return FALSE;
3782         }
3784         if (parents > 1 && !open_commit_parent_menu(buf, &parents))
3785                 return FALSE;
3787         string_copy_rev(rev, &buf[41 * parents]);
3788         return TRUE;
3791 /*
3792  * Pager backend
3793  */
3795 static bool
3796 pager_draw(struct view *view, struct line *line, unsigned int lineno)
3798         char text[SIZEOF_STR];
3800         if (opt_line_number && draw_lineno(view, lineno))
3801                 return TRUE;
3803         string_expand(text, sizeof(text), line->data, opt_tab_size);
3804         draw_text(view, line->type, text, TRUE);
3805         return TRUE;
3808 static bool
3809 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
3811         const char *describe_argv[] = { "git", "describe", commit_id, NULL };
3812         char ref[SIZEOF_STR];
3814         if (!run_io_buf(describe_argv, ref, sizeof(ref)) || !*ref)
3815                 return TRUE;
3817         /* This is the only fatal call, since it can "corrupt" the buffer. */
3818         if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
3819                 return FALSE;
3821         return TRUE;
3824 static void
3825 add_pager_refs(struct view *view, struct line *line)
3827         char buf[SIZEOF_STR];
3828         char *commit_id = (char *)line->data + STRING_SIZE("commit ");
3829         struct ref_list *list;
3830         size_t bufpos = 0, i;
3831         const char *sep = "Refs: ";
3832         bool is_tag = FALSE;
3834         assert(line->type == LINE_COMMIT);
3836         list = get_ref_list(commit_id);
3837         if (!list) {
3838                 if (view == VIEW(REQ_VIEW_DIFF))
3839                         goto try_add_describe_ref;
3840                 return;
3841         }
3843         for (i = 0; i < list->size; i++) {
3844                 struct ref *ref = list->refs[i];
3845                 const char *fmt = ref->tag    ? "%s[%s]" :
3846                                   ref->remote ? "%s<%s>" : "%s%s";
3848                 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
3849                         return;
3850                 sep = ", ";
3851                 if (ref->tag)
3852                         is_tag = TRUE;
3853         }
3855         if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
3856 try_add_describe_ref:
3857                 /* Add <tag>-g<commit_id> "fake" reference. */
3858                 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
3859                         return;
3860         }
3862         if (bufpos == 0)
3863                 return;
3865         add_line_text(view, buf, LINE_PP_REFS);
3868 static bool
3869 pager_read(struct view *view, char *data)
3871         struct line *line;
3873         if (!data)
3874                 return TRUE;
3876         line = add_line_text(view, data, get_line_type(data));
3877         if (!line)
3878                 return FALSE;
3880         if (line->type == LINE_COMMIT &&
3881             (view == VIEW(REQ_VIEW_DIFF) ||
3882              view == VIEW(REQ_VIEW_LOG)))
3883                 add_pager_refs(view, line);
3885         return TRUE;
3888 static enum request
3889 pager_request(struct view *view, enum request request, struct line *line)
3891         int split = 0;
3893         if (request != REQ_ENTER)
3894                 return request;
3896         if (line->type == LINE_COMMIT &&
3897            (view == VIEW(REQ_VIEW_LOG) ||
3898             view == VIEW(REQ_VIEW_PAGER))) {
3899                 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
3900                 split = 1;
3901         }
3903         /* Always scroll the view even if it was split. That way
3904          * you can use Enter to scroll through the log view and
3905          * split open each commit diff. */
3906         scroll_view(view, REQ_SCROLL_LINE_DOWN);
3908         /* FIXME: A minor workaround. Scrolling the view will call report("")
3909          * but if we are scrolling a non-current view this won't properly
3910          * update the view title. */
3911         if (split)
3912                 update_view_title(view);
3914         return REQ_NONE;
3917 static bool
3918 pager_grep(struct view *view, struct line *line)
3920         const char *text[] = { line->data, NULL };
3922         return grep_text(view, text);
3925 static void
3926 pager_select(struct view *view, struct line *line)
3928         if (line->type == LINE_COMMIT) {
3929                 char *text = (char *)line->data + STRING_SIZE("commit ");
3931                 if (view != VIEW(REQ_VIEW_PAGER))
3932                         string_copy_rev(view->ref, text);
3933                 string_copy_rev(ref_commit, text);
3934         }
3937 static struct view_ops pager_ops = {
3938         "line",
3939         NULL,
3940         NULL,
3941         pager_read,
3942         pager_draw,
3943         pager_request,
3944         pager_grep,
3945         pager_select,
3946 };
3948 static const char *log_argv[SIZEOF_ARG] = {
3949         "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
3950 };
3952 static enum request
3953 log_request(struct view *view, enum request request, struct line *line)
3955         switch (request) {
3956         case REQ_REFRESH:
3957                 load_refs();
3958                 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
3959                 return REQ_NONE;
3960         default:
3961                 return pager_request(view, request, line);
3962         }
3965 static struct view_ops log_ops = {
3966         "line",
3967         log_argv,
3968         NULL,
3969         pager_read,
3970         pager_draw,
3971         log_request,
3972         pager_grep,
3973         pager_select,
3974 };
3976 static const char *diff_argv[SIZEOF_ARG] = {
3977         "git", "show", "--pretty=fuller", "--no-color", "--root",
3978                 "--patch-with-stat", "--find-copies-harder", "-C", "%(commit)", NULL
3979 };
3981 static struct view_ops diff_ops = {
3982         "line",
3983         diff_argv,
3984         NULL,
3985         pager_read,
3986         pager_draw,
3987         pager_request,
3988         pager_grep,
3989         pager_select,
3990 };
3992 /*
3993  * Help backend
3994  */
3996 static bool help_keymap_hidden[ARRAY_SIZE(keymap_table)];
3998 static bool
3999 help_open_keymap_title(struct view *view, enum keymap keymap)
4001         struct line *line;
4003         line = add_line_format(view, LINE_HELP_KEYMAP, "[%c] %s bindings",
4004                                help_keymap_hidden[keymap] ? '+' : '-',
4005                                enum_name(keymap_table[keymap]));
4006         if (line)
4007                 line->other = keymap;
4009         return help_keymap_hidden[keymap];
4012 static void
4013 help_open_keymap(struct view *view, enum keymap keymap)
4015         const char *group = NULL;
4016         char buf[SIZEOF_STR];
4017         size_t bufpos;
4018         bool add_title = TRUE;
4019         int i;
4021         for (i = 0; i < ARRAY_SIZE(req_info); i++) {
4022                 const char *key = NULL;
4024                 if (req_info[i].request == REQ_NONE)
4025                         continue;
4027                 if (!req_info[i].request) {
4028                         group = req_info[i].help;
4029                         continue;
4030                 }
4032                 key = get_keys(keymap, req_info[i].request, TRUE);
4033                 if (!key || !*key)
4034                         continue;
4036                 if (add_title && help_open_keymap_title(view, keymap))
4037                         return;
4038                 add_title = false;
4040                 if (group) {
4041                         add_line_text(view, group, LINE_HELP_GROUP);
4042                         group = NULL;
4043                 }
4045                 add_line_format(view, LINE_DEFAULT, "    %-25s %-20s %s", key,
4046                                 enum_name(req_info[i]), req_info[i].help);
4047         }
4049         group = "External commands:";
4051         for (i = 0; i < run_requests; i++) {
4052                 struct run_request *req = get_run_request(REQ_NONE + i + 1);
4053                 const char *key;
4054                 int argc;
4056                 if (!req || req->keymap != keymap)
4057                         continue;
4059                 key = get_key_name(req->key);
4060                 if (!*key)
4061                         key = "(no key defined)";
4063                 if (add_title && help_open_keymap_title(view, keymap))
4064                         return;
4065                 if (group) {
4066                         add_line_text(view, group, LINE_HELP_GROUP);
4067                         group = NULL;
4068                 }
4070                 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
4071                         if (!string_format_from(buf, &bufpos, "%s%s",
4072                                                 argc ? " " : "", req->argv[argc]))
4073                                 return;
4075                 add_line_format(view, LINE_DEFAULT, "    %-25s `%s`", key, buf);
4076         }
4079 static bool
4080 help_open(struct view *view)
4082         enum keymap keymap;
4084         reset_view(view);
4085         add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
4086         add_line_text(view, "", LINE_DEFAULT);
4088         for (keymap = 0; keymap < ARRAY_SIZE(keymap_table); keymap++)
4089                 help_open_keymap(view, keymap);
4091         return TRUE;
4094 static enum request
4095 help_request(struct view *view, enum request request, struct line *line)
4097         switch (request) {
4098         case REQ_ENTER:
4099                 if (line->type == LINE_HELP_KEYMAP) {
4100                         help_keymap_hidden[line->other] =
4101                                 !help_keymap_hidden[line->other];
4102                         view->p_restore = TRUE;
4103                         open_view(view, REQ_VIEW_HELP, OPEN_REFRESH);
4104                 }
4106                 return REQ_NONE;
4107         default:
4108                 return pager_request(view, request, line);
4109         }
4112 static struct view_ops help_ops = {
4113         "line",
4114         NULL,
4115         help_open,
4116         NULL,
4117         pager_draw,
4118         help_request,
4119         pager_grep,
4120         pager_select,
4121 };
4124 /*
4125  * Tree backend
4126  */
4128 struct tree_stack_entry {
4129         struct tree_stack_entry *prev;  /* Entry below this in the stack */
4130         unsigned long lineno;           /* Line number to restore */
4131         char *name;                     /* Position of name in opt_path */
4132 };
4134 /* The top of the path stack. */
4135 static struct tree_stack_entry *tree_stack = NULL;
4136 unsigned long tree_lineno = 0;
4138 static void
4139 pop_tree_stack_entry(void)
4141         struct tree_stack_entry *entry = tree_stack;
4143         tree_lineno = entry->lineno;
4144         entry->name[0] = 0;
4145         tree_stack = entry->prev;
4146         free(entry);
4149 static void
4150 push_tree_stack_entry(const char *name, unsigned long lineno)
4152         struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
4153         size_t pathlen = strlen(opt_path);
4155         if (!entry)
4156                 return;
4158         entry->prev = tree_stack;
4159         entry->name = opt_path + pathlen;
4160         tree_stack = entry;
4162         if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
4163                 pop_tree_stack_entry();
4164                 return;
4165         }
4167         /* Move the current line to the first tree entry. */
4168         tree_lineno = 1;
4169         entry->lineno = lineno;
4172 /* Parse output from git-ls-tree(1):
4173  *
4174  * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
4175  */
4177 #define SIZEOF_TREE_ATTR \
4178         STRING_SIZE("100644 blob f931e1d229c3e185caad4449bf5b66ed72462657\t")
4180 #define SIZEOF_TREE_MODE \
4181         STRING_SIZE("100644 ")
4183 #define TREE_ID_OFFSET \
4184         STRING_SIZE("100644 blob ")
4186 struct tree_entry {
4187         char id[SIZEOF_REV];
4188         mode_t mode;
4189         time_t time;                    /* Date from the author ident. */
4190         const char *author;             /* Author of the commit. */
4191         char name[1];
4192 };
4194 static const char *
4195 tree_path(const struct line *line)
4197         return ((struct tree_entry *) line->data)->name;
4200 static int
4201 tree_compare_entry(const struct line *line1, const struct line *line2)
4203         if (line1->type != line2->type)
4204                 return line1->type == LINE_TREE_DIR ? -1 : 1;
4205         return strcmp(tree_path(line1), tree_path(line2));
4208 static const enum sort_field tree_sort_fields[] = {
4209         ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
4210 };
4211 static struct sort_state tree_sort_state = SORT_STATE(tree_sort_fields);
4213 static int
4214 tree_compare(const void *l1, const void *l2)
4216         const struct line *line1 = (const struct line *) l1;
4217         const struct line *line2 = (const struct line *) l2;
4218         const struct tree_entry *entry1 = ((const struct line *) l1)->data;
4219         const struct tree_entry *entry2 = ((const struct line *) l2)->data;
4221         if (line1->type == LINE_TREE_HEAD)
4222                 return -1;
4223         if (line2->type == LINE_TREE_HEAD)
4224                 return 1;
4226         switch (get_sort_field(tree_sort_state)) {
4227         case ORDERBY_DATE:
4228                 return sort_order(tree_sort_state, entry1->time - entry2->time);
4230         case ORDERBY_AUTHOR:
4231                 return sort_order(tree_sort_state, strcmp(entry1->author, entry2->author));
4233         case ORDERBY_NAME:
4234         default:
4235                 return sort_order(tree_sort_state, tree_compare_entry(line1, line2));
4236         }
4240 static struct line *
4241 tree_entry(struct view *view, enum line_type type, const char *path,
4242            const char *mode, const char *id)
4244         struct tree_entry *entry = calloc(1, sizeof(*entry) + strlen(path));
4245         struct line *line = entry ? add_line_data(view, entry, type) : NULL;
4247         if (!entry || !line) {
4248                 free(entry);
4249                 return NULL;
4250         }
4252         strncpy(entry->name, path, strlen(path));
4253         if (mode)
4254                 entry->mode = strtoul(mode, NULL, 8);
4255         if (id)
4256                 string_copy_rev(entry->id, id);
4258         return line;
4261 static bool
4262 tree_read_date(struct view *view, char *text, bool *read_date)
4264         static const char *author_name;
4265         static time_t author_time;
4267         if (!text && *read_date) {
4268                 *read_date = FALSE;
4269                 return TRUE;
4271         } else if (!text) {
4272                 char *path = *opt_path ? opt_path : ".";
4273                 /* Find next entry to process */
4274                 const char *log_file[] = {
4275                         "git", "log", "--no-color", "--pretty=raw",
4276                                 "--cc", "--raw", view->id, "--", path, NULL
4277                 };
4278                 struct io io = {};
4280                 if (!view->lines) {
4281                         tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL);
4282                         report("Tree is empty");
4283                         return TRUE;
4284                 }
4286                 if (!run_io_rd(&io, log_file, opt_cdup, FORMAT_NONE)) {
4287                         report("Failed to load tree data");
4288                         return TRUE;
4289                 }
4291                 done_io(view->pipe);
4292                 view->io = io;
4293                 *read_date = TRUE;
4294                 return FALSE;
4296         } else if (*text == 'a' && get_line_type(text) == LINE_AUTHOR) {
4297                 parse_author_line(text + STRING_SIZE("author "),
4298                                   &author_name, &author_time);
4300         } else if (*text == ':') {
4301                 char *pos;
4302                 size_t annotated = 1;
4303                 size_t i;
4305                 pos = strchr(text, '\t');
4306                 if (!pos)
4307                         return TRUE;
4308                 text = pos + 1;
4309                 if (*opt_path && !strncmp(text, opt_path, strlen(opt_path)))
4310                         text += strlen(opt_path);
4311                 pos = strchr(text, '/');
4312                 if (pos)
4313                         *pos = 0;
4315                 for (i = 1; i < view->lines; i++) {
4316                         struct line *line = &view->line[i];
4317                         struct tree_entry *entry = line->data;
4319                         annotated += !!entry->author;
4320                         if (entry->author || strcmp(entry->name, text))
4321                                 continue;
4323                         entry->author = author_name;
4324                         entry->time = author_time;
4325                         line->dirty = 1;
4326                         break;
4327                 }
4329                 if (annotated == view->lines)
4330                         kill_io(view->pipe);
4331         }
4332         return TRUE;
4335 static bool
4336 tree_read(struct view *view, char *text)
4338         static bool read_date = FALSE;
4339         struct tree_entry *data;
4340         struct line *entry, *line;
4341         enum line_type type;
4342         size_t textlen = text ? strlen(text) : 0;
4343         char *path = text + SIZEOF_TREE_ATTR;
4345         if (read_date || !text)
4346                 return tree_read_date(view, text, &read_date);
4348         if (textlen <= SIZEOF_TREE_ATTR)
4349                 return FALSE;
4350         if (view->lines == 0 &&
4351             !tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL))
4352                 return FALSE;
4354         /* Strip the path part ... */
4355         if (*opt_path) {
4356                 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
4357                 size_t striplen = strlen(opt_path);
4359                 if (pathlen > striplen)
4360                         memmove(path, path + striplen,
4361                                 pathlen - striplen + 1);
4363                 /* Insert "link" to parent directory. */
4364                 if (view->lines == 1 &&
4365                     !tree_entry(view, LINE_TREE_DIR, "..", "040000", view->ref))
4366                         return FALSE;
4367         }
4369         type = text[SIZEOF_TREE_MODE] == 't' ? LINE_TREE_DIR : LINE_TREE_FILE;
4370         entry = tree_entry(view, type, path, text, text + TREE_ID_OFFSET);
4371         if (!entry)
4372                 return FALSE;
4373         data = entry->data;
4375         /* Skip "Directory ..." and ".." line. */
4376         for (line = &view->line[1 + !!*opt_path]; line < entry; line++) {
4377                 if (tree_compare_entry(line, entry) <= 0)
4378                         continue;
4380                 memmove(line + 1, line, (entry - line) * sizeof(*entry));
4382                 line->data = data;
4383                 line->type = type;
4384                 for (; line <= entry; line++)
4385                         line->dirty = line->cleareol = 1;
4386                 return TRUE;
4387         }
4389         if (tree_lineno > view->lineno) {
4390                 view->lineno = tree_lineno;
4391                 tree_lineno = 0;
4392         }
4394         return TRUE;
4397 static bool
4398 tree_draw(struct view *view, struct line *line, unsigned int lineno)
4400         struct tree_entry *entry = line->data;
4402         if (line->type == LINE_TREE_HEAD) {
4403                 if (draw_text(view, line->type, "Directory path /", TRUE))
4404                         return TRUE;
4405         } else {
4406                 if (draw_mode(view, entry->mode))
4407                         return TRUE;
4409                 if (opt_author && draw_author(view, entry->author))
4410                         return TRUE;
4412                 if (opt_date && draw_date(view, entry->author ? &entry->time : NULL))
4413                         return TRUE;
4414         }
4415         if (draw_text(view, line->type, entry->name, TRUE))
4416                 return TRUE;
4417         return TRUE;
4420 static void
4421 open_blob_editor()
4423         char file[SIZEOF_STR] = "/tmp/tigblob.XXXXXX";
4424         int fd = mkstemp(file);
4426         if (fd == -1)
4427                 report("Failed to create temporary file");
4428         else if (!run_io_append(blob_ops.argv, FORMAT_ALL, fd))
4429                 report("Failed to save blob data to file");
4430         else
4431                 open_editor(FALSE, file);
4432         if (fd != -1)
4433                 unlink(file);
4436 static enum request
4437 tree_request(struct view *view, enum request request, struct line *line)
4439         enum open_flags flags;
4441         switch (request) {
4442         case REQ_VIEW_BLAME:
4443                 if (line->type != LINE_TREE_FILE) {
4444                         report("Blame only supported for files");
4445                         return REQ_NONE;
4446                 }
4448                 string_copy(opt_ref, view->vid);
4449                 return request;
4451         case REQ_EDIT:
4452                 if (line->type != LINE_TREE_FILE) {
4453                         report("Edit only supported for files");
4454                 } else if (!is_head_commit(view->vid)) {
4455                         open_blob_editor();
4456                 } else {
4457                         open_editor(TRUE, opt_file);
4458                 }
4459                 return REQ_NONE;
4461         case REQ_TOGGLE_SORT_FIELD:
4462         case REQ_TOGGLE_SORT_ORDER:
4463                 sort_view(view, request, &tree_sort_state, tree_compare);
4464                 return REQ_NONE;
4466         case REQ_PARENT:
4467                 if (!*opt_path) {
4468                         /* quit view if at top of tree */
4469                         return REQ_VIEW_CLOSE;
4470                 }
4471                 /* fake 'cd  ..' */
4472                 line = &view->line[1];
4473                 break;
4475         case REQ_ENTER:
4476                 break;
4478         default:
4479                 return request;
4480         }
4482         /* Cleanup the stack if the tree view is at a different tree. */
4483         while (!*opt_path && tree_stack)
4484                 pop_tree_stack_entry();
4486         switch (line->type) {
4487         case LINE_TREE_DIR:
4488                 /* Depending on whether it is a subdirectory or parent link
4489                  * mangle the path buffer. */
4490                 if (line == &view->line[1] && *opt_path) {
4491                         pop_tree_stack_entry();
4493                 } else {
4494                         const char *basename = tree_path(line);
4496                         push_tree_stack_entry(basename, view->lineno);
4497                 }
4499                 /* Trees and subtrees share the same ID, so they are not not
4500                  * unique like blobs. */
4501                 flags = OPEN_RELOAD;
4502                 request = REQ_VIEW_TREE;
4503                 break;
4505         case LINE_TREE_FILE:
4506                 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4507                 request = REQ_VIEW_BLOB;
4508                 break;
4510         default:
4511                 return REQ_NONE;
4512         }
4514         open_view(view, request, flags);
4515         if (request == REQ_VIEW_TREE)
4516                 view->lineno = tree_lineno;
4518         return REQ_NONE;
4521 static bool
4522 tree_grep(struct view *view, struct line *line)
4524         struct tree_entry *entry = line->data;
4525         const char *text[] = {
4526                 entry->name,
4527                 opt_author ? entry->author : "",
4528                 opt_date ? mkdate(&entry->time) : "",
4529                 NULL
4530         };
4532         return grep_text(view, text);
4535 static void
4536 tree_select(struct view *view, struct line *line)
4538         struct tree_entry *entry = line->data;
4540         if (line->type == LINE_TREE_FILE) {
4541                 string_copy_rev(ref_blob, entry->id);
4542                 string_format(opt_file, "%s%s", opt_path, tree_path(line));
4544         } else if (line->type != LINE_TREE_DIR) {
4545                 return;
4546         }
4548         string_copy_rev(view->ref, entry->id);
4551 static bool
4552 tree_prepare(struct view *view)
4554         if (view->lines == 0 && opt_prefix[0]) {
4555                 char *pos = opt_prefix;
4557                 while (pos && *pos) {
4558                         char *end = strchr(pos, '/');
4560                         if (end)
4561                                 *end = 0;
4562                         push_tree_stack_entry(pos, 0);
4563                         pos = end;
4564                         if (end) {
4565                                 *end = '/';
4566                                 pos++;
4567                         }
4568                 }
4570         } else if (strcmp(view->vid, view->id)) {
4571                 opt_path[0] = 0;
4572         }
4574         return init_io_rd(&view->io, view->ops->argv, opt_cdup, FORMAT_ALL);
4577 static const char *tree_argv[SIZEOF_ARG] = {
4578         "git", "ls-tree", "%(commit)", "%(directory)", NULL
4579 };
4581 static struct view_ops tree_ops = {
4582         "file",
4583         tree_argv,
4584         NULL,
4585         tree_read,
4586         tree_draw,
4587         tree_request,
4588         tree_grep,
4589         tree_select,
4590         tree_prepare,
4591 };
4593 static bool
4594 blob_read(struct view *view, char *line)
4596         if (!line)
4597                 return TRUE;
4598         return add_line_text(view, line, LINE_DEFAULT) != NULL;
4601 static enum request
4602 blob_request(struct view *view, enum request request, struct line *line)
4604         switch (request) {
4605         case REQ_EDIT:
4606                 open_blob_editor();
4607                 return REQ_NONE;
4608         default:
4609                 return pager_request(view, request, line);
4610         }
4613 static const char *blob_argv[SIZEOF_ARG] = {
4614         "git", "cat-file", "blob", "%(blob)", NULL
4615 };
4617 static struct view_ops blob_ops = {
4618         "line",
4619         blob_argv,
4620         NULL,
4621         blob_read,
4622         pager_draw,
4623         blob_request,
4624         pager_grep,
4625         pager_select,
4626 };
4628 /*
4629  * Blame backend
4630  *
4631  * Loading the blame view is a two phase job:
4632  *
4633  *  1. File content is read either using opt_file from the
4634  *     filesystem or using git-cat-file.
4635  *  2. Then blame information is incrementally added by
4636  *     reading output from git-blame.
4637  */
4639 static const char *blame_head_argv[] = {
4640         "git", "blame", "--incremental", "--", "%(file)", NULL
4641 };
4643 static const char *blame_ref_argv[] = {
4644         "git", "blame", "--incremental", "%(ref)", "--", "%(file)", NULL
4645 };
4647 static const char *blame_cat_file_argv[] = {
4648         "git", "cat-file", "blob", "%(ref):%(file)", NULL
4649 };
4651 struct blame_commit {
4652         char id[SIZEOF_REV];            /* SHA1 ID. */
4653         char title[128];                /* First line of the commit message. */
4654         const char *author;             /* Author of the commit. */
4655         time_t time;                    /* Date from the author ident. */
4656         char filename[128];             /* Name of file. */
4657         bool has_previous;              /* Was a "previous" line detected. */
4658 };
4660 struct blame {
4661         struct blame_commit *commit;
4662         unsigned long lineno;
4663         char text[1];
4664 };
4666 static bool
4667 blame_open(struct view *view)
4669         char path[SIZEOF_STR];
4671         if (!view->parent && *opt_prefix) {
4672                 string_copy(path, opt_file);
4673                 if (!string_format(opt_file, "%s%s", opt_prefix, path))
4674                         return FALSE;
4675         }
4677         if (*opt_ref || !io_open(&view->io, "%s%s", opt_cdup, opt_file)) {
4678                 if (!run_io_rd(&view->io, blame_cat_file_argv, opt_cdup, FORMAT_ALL))
4679                         return FALSE;
4680         }
4682         setup_update(view, opt_file);
4683         string_format(view->ref, "%s ...", opt_file);
4685         return TRUE;
4688 static struct blame_commit *
4689 get_blame_commit(struct view *view, const char *id)
4691         size_t i;
4693         for (i = 0; i < view->lines; i++) {
4694                 struct blame *blame = view->line[i].data;
4696                 if (!blame->commit)
4697                         continue;
4699                 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
4700                         return blame->commit;
4701         }
4703         {
4704                 struct blame_commit *commit = calloc(1, sizeof(*commit));
4706                 if (commit)
4707                         string_ncopy(commit->id, id, SIZEOF_REV);
4708                 return commit;
4709         }
4712 static bool
4713 parse_number(const char **posref, size_t *number, size_t min, size_t max)
4715         const char *pos = *posref;
4717         *posref = NULL;
4718         pos = strchr(pos + 1, ' ');
4719         if (!pos || !isdigit(pos[1]))
4720                 return FALSE;
4721         *number = atoi(pos + 1);
4722         if (*number < min || *number > max)
4723                 return FALSE;
4725         *posref = pos;
4726         return TRUE;
4729 static struct blame_commit *
4730 parse_blame_commit(struct view *view, const char *text, int *blamed)
4732         struct blame_commit *commit;
4733         struct blame *blame;
4734         const char *pos = text + SIZEOF_REV - 2;
4735         size_t orig_lineno = 0;
4736         size_t lineno;
4737         size_t group;
4739         if (strlen(text) <= SIZEOF_REV || pos[1] != ' ')
4740                 return NULL;
4742         if (!parse_number(&pos, &orig_lineno, 1, 9999999) ||
4743             !parse_number(&pos, &lineno, 1, view->lines) ||
4744             !parse_number(&pos, &group, 1, view->lines - lineno + 1))
4745                 return NULL;
4747         commit = get_blame_commit(view, text);
4748         if (!commit)
4749                 return NULL;
4751         *blamed += group;
4752         while (group--) {
4753                 struct line *line = &view->line[lineno + group - 1];
4755                 blame = line->data;
4756                 blame->commit = commit;
4757                 blame->lineno = orig_lineno + group - 1;
4758                 line->dirty = 1;
4759         }
4761         return commit;
4764 static bool
4765 blame_read_file(struct view *view, const char *line, bool *read_file)
4767         if (!line) {
4768                 const char **argv = *opt_ref ? blame_ref_argv : blame_head_argv;
4769                 struct io io = {};
4771                 if (view->lines == 0 && !view->parent)
4772                         die("No blame exist for %s", view->vid);
4774                 if (view->lines == 0 || !run_io_rd(&io, argv, opt_cdup, FORMAT_ALL)) {
4775                         report("Failed to load blame data");
4776                         return TRUE;
4777                 }
4779                 done_io(view->pipe);
4780                 view->io = io;
4781                 *read_file = FALSE;
4782                 return FALSE;
4784         } else {
4785                 size_t linelen = strlen(line);
4786                 struct blame *blame = malloc(sizeof(*blame) + linelen);
4788                 if (!blame)
4789                         return FALSE;
4791                 blame->commit = NULL;
4792                 strncpy(blame->text, line, linelen);
4793                 blame->text[linelen] = 0;
4794                 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
4795         }
4798 static bool
4799 match_blame_header(const char *name, char **line)
4801         size_t namelen = strlen(name);
4802         bool matched = !strncmp(name, *line, namelen);
4804         if (matched)
4805                 *line += namelen;
4807         return matched;
4810 static bool
4811 blame_read(struct view *view, char *line)
4813         static struct blame_commit *commit = NULL;
4814         static int blamed = 0;
4815         static bool read_file = TRUE;
4817         if (read_file)
4818                 return blame_read_file(view, line, &read_file);
4820         if (!line) {
4821                 /* Reset all! */
4822                 commit = NULL;
4823                 blamed = 0;
4824                 read_file = TRUE;
4825                 string_format(view->ref, "%s", view->vid);
4826                 if (view_is_displayed(view)) {
4827                         update_view_title(view);
4828                         redraw_view_from(view, 0);
4829                 }
4830                 return TRUE;
4831         }
4833         if (!commit) {
4834                 commit = parse_blame_commit(view, line, &blamed);
4835                 string_format(view->ref, "%s %2d%%", view->vid,
4836                               view->lines ? blamed * 100 / view->lines : 0);
4838         } else if (match_blame_header("author ", &line)) {
4839                 commit->author = get_author(line);
4841         } else if (match_blame_header("author-time ", &line)) {
4842                 commit->time = (time_t) atol(line);
4844         } else if (match_blame_header("author-tz ", &line)) {
4845                 parse_timezone(&commit->time, line);
4847         } else if (match_blame_header("summary ", &line)) {
4848                 string_ncopy(commit->title, line, strlen(line));
4850         } else if (match_blame_header("previous ", &line)) {
4851                 commit->has_previous = TRUE;
4853         } else if (match_blame_header("filename ", &line)) {
4854                 string_ncopy(commit->filename, line, strlen(line));
4855                 commit = NULL;
4856         }
4858         return TRUE;
4861 static bool
4862 blame_draw(struct view *view, struct line *line, unsigned int lineno)
4864         struct blame *blame = line->data;
4865         time_t *time = NULL;
4866         const char *id = NULL, *author = NULL;
4867         char text[SIZEOF_STR];
4869         if (blame->commit && *blame->commit->filename) {
4870                 id = blame->commit->id;
4871                 author = blame->commit->author;
4872                 time = &blame->commit->time;
4873         }
4875         if (opt_date && draw_date(view, time))
4876                 return TRUE;
4878         if (opt_author && draw_author(view, author))
4879                 return TRUE;
4881         if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
4882                 return TRUE;
4884         if (draw_lineno(view, lineno))
4885                 return TRUE;
4887         string_expand(text, sizeof(text), blame->text, opt_tab_size);
4888         draw_text(view, LINE_DEFAULT, text, TRUE);
4889         return TRUE;
4892 static bool
4893 check_blame_commit(struct blame *blame, bool check_null_id)
4895         if (!blame->commit)
4896                 report("Commit data not loaded yet");
4897         else if (check_null_id && !strcmp(blame->commit->id, NULL_ID))
4898                 report("No commit exist for the selected line");
4899         else
4900                 return TRUE;
4901         return FALSE;
4904 static void
4905 setup_blame_parent_line(struct view *view, struct blame *blame)
4907         const char *diff_tree_argv[] = {
4908                 "git", "diff-tree", "-U0", blame->commit->id,
4909                         "--", blame->commit->filename, NULL
4910         };
4911         struct io io = {};
4912         int parent_lineno = -1;
4913         int blamed_lineno = -1;
4914         char *line;
4916         if (!run_io(&io, diff_tree_argv, NULL, IO_RD))
4917                 return;
4919         while ((line = io_get(&io, '\n', TRUE))) {
4920                 if (*line == '@') {
4921                         char *pos = strchr(line, '+');
4923                         parent_lineno = atoi(line + 4);
4924                         if (pos)
4925                                 blamed_lineno = atoi(pos + 1);
4927                 } else if (*line == '+' && parent_lineno != -1) {
4928                         if (blame->lineno == blamed_lineno - 1 &&
4929                             !strcmp(blame->text, line + 1)) {
4930                                 view->lineno = parent_lineno ? parent_lineno - 1 : 0;
4931                                 break;
4932                         }
4933                         blamed_lineno++;
4934                 }
4935         }
4937         done_io(&io);
4940 static enum request
4941 blame_request(struct view *view, enum request request, struct line *line)
4943         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4944         struct blame *blame = line->data;
4946         switch (request) {
4947         case REQ_VIEW_BLAME:
4948                 if (check_blame_commit(blame, TRUE)) {
4949                         string_copy(opt_ref, blame->commit->id);
4950                         string_copy(opt_file, blame->commit->filename);
4951                         if (blame->lineno)
4952                                 view->lineno = blame->lineno;
4953                         open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4954                 }
4955                 break;
4957         case REQ_PARENT:
4958                 if (check_blame_commit(blame, TRUE) &&
4959                     select_commit_parent(blame->commit->id, opt_ref,
4960                                          blame->commit->filename)) {
4961                         string_copy(opt_file, blame->commit->filename);
4962                         setup_blame_parent_line(view, blame);
4963                         open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4964                 }
4965                 break;
4967         case REQ_ENTER:
4968                 if (!check_blame_commit(blame, FALSE))
4969                         break;
4971                 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
4972                     !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
4973                         break;
4975                 if (!strcmp(blame->commit->id, NULL_ID)) {
4976                         struct view *diff = VIEW(REQ_VIEW_DIFF);
4977                         const char *diff_index_argv[] = {
4978                                 "git", "diff-index", "--root", "--patch-with-stat",
4979                                         "-C", "-M", "HEAD", "--", view->vid, NULL
4980                         };
4982                         if (!blame->commit->has_previous) {
4983                                 diff_index_argv[1] = "diff";
4984                                 diff_index_argv[2] = "--no-color";
4985                                 diff_index_argv[6] = "--";
4986                                 diff_index_argv[7] = "/dev/null";
4987                         }
4989                         if (!prepare_update(diff, diff_index_argv, NULL, FORMAT_DASH)) {
4990                                 report("Failed to allocate diff command");
4991                                 break;
4992                         }
4993                         flags |= OPEN_PREPARED;
4994                 }
4996                 open_view(view, REQ_VIEW_DIFF, flags);
4997                 if (VIEW(REQ_VIEW_DIFF)->pipe && !strcmp(blame->commit->id, NULL_ID))
4998                         string_copy_rev(VIEW(REQ_VIEW_DIFF)->ref, NULL_ID);
4999                 break;
5001         default:
5002                 return request;
5003         }
5005         return REQ_NONE;
5008 static bool
5009 blame_grep(struct view *view, struct line *line)
5011         struct blame *blame = line->data;
5012         struct blame_commit *commit = blame->commit;
5013         const char *text[] = {
5014                 blame->text,
5015                 commit ? commit->title : "",
5016                 commit ? commit->id : "",
5017                 commit && opt_author ? commit->author : "",
5018                 commit && opt_date ? mkdate(&commit->time) : "",
5019                 NULL
5020         };
5022         return grep_text(view, text);
5025 static void
5026 blame_select(struct view *view, struct line *line)
5028         struct blame *blame = line->data;
5029         struct blame_commit *commit = blame->commit;
5031         if (!commit)
5032                 return;
5034         if (!strcmp(commit->id, NULL_ID))
5035                 string_ncopy(ref_commit, "HEAD", 4);
5036         else
5037                 string_copy_rev(ref_commit, commit->id);
5040 static struct view_ops blame_ops = {
5041         "line",
5042         NULL,
5043         blame_open,
5044         blame_read,
5045         blame_draw,
5046         blame_request,
5047         blame_grep,
5048         blame_select,
5049 };
5051 /*
5052  * Branch backend
5053  */
5055 struct branch {
5056         const char *author;             /* Author of the last commit. */
5057         time_t time;                    /* Date of the last activity. */
5058         const struct ref *ref;          /* Name and commit ID information. */
5059 };
5061 static const struct ref branch_all;
5063 static const enum sort_field branch_sort_fields[] = {
5064         ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
5065 };
5066 static struct sort_state branch_sort_state = SORT_STATE(branch_sort_fields);
5068 static int
5069 branch_compare(const void *l1, const void *l2)
5071         const struct branch *branch1 = ((const struct line *) l1)->data;
5072         const struct branch *branch2 = ((const struct line *) l2)->data;
5074         switch (get_sort_field(branch_sort_state)) {
5075         case ORDERBY_DATE:
5076                 return sort_order(branch_sort_state, branch1->time - branch2->time);
5078         case ORDERBY_AUTHOR:
5079                 return sort_order(branch_sort_state, strcmp(branch1->author, branch2->author));
5081         case ORDERBY_NAME:
5082         default:
5083                 return sort_order(branch_sort_state, strcmp(branch1->ref->name, branch2->ref->name));
5084         }
5087 static bool
5088 branch_draw(struct view *view, struct line *line, unsigned int lineno)
5090         struct branch *branch = line->data;
5091         enum line_type type = branch->ref->head ? LINE_MAIN_HEAD : LINE_DEFAULT;
5093         if (opt_date && draw_date(view, branch->author ? &branch->time : NULL))
5094                 return TRUE;
5096         if (opt_author && draw_author(view, branch->author))
5097                 return TRUE;
5099         draw_text(view, type, branch->ref == &branch_all ? "All branches" : branch->ref->name, TRUE);
5100         return TRUE;
5103 static enum request
5104 branch_request(struct view *view, enum request request, struct line *line)
5106         struct branch *branch = line->data;
5108         switch (request) {
5109         case REQ_REFRESH:
5110                 load_refs();
5111                 open_view(view, REQ_VIEW_BRANCH, OPEN_REFRESH);
5112                 return REQ_NONE;
5114         case REQ_TOGGLE_SORT_FIELD:
5115         case REQ_TOGGLE_SORT_ORDER:
5116                 sort_view(view, request, &branch_sort_state, branch_compare);
5117                 return REQ_NONE;
5119         case REQ_ENTER:
5120                 if (branch->ref == &branch_all) {
5121                         const char *all_branches_argv[] = {
5122                                 "git", "log", "--no-color", "--pretty=raw", "--parents",
5123                                       "--topo-order", "--all", NULL
5124                         };
5125                         struct view *main_view = VIEW(REQ_VIEW_MAIN);
5127                         if (!prepare_update(main_view, all_branches_argv, NULL, FORMAT_NONE)) {
5128                                 report("Failed to load view of all branches");
5129                                 return REQ_NONE;
5130                         }
5131                         open_view(view, REQ_VIEW_MAIN, OPEN_PREPARED | OPEN_SPLIT);
5132                 } else {
5133                         open_view(view, REQ_VIEW_MAIN, OPEN_SPLIT);
5134                 }
5135                 return REQ_NONE;
5137         default:
5138                 return request;
5139         }
5142 static bool
5143 branch_read(struct view *view, char *line)
5145         static char id[SIZEOF_REV];
5146         struct branch *reference;
5147         size_t i;
5149         if (!line)
5150                 return TRUE;
5152         switch (get_line_type(line)) {
5153         case LINE_COMMIT:
5154                 string_copy_rev(id, line + STRING_SIZE("commit "));
5155                 return TRUE;
5157         case LINE_AUTHOR:
5158                 for (i = 0, reference = NULL; i < view->lines; i++) {
5159                         struct branch *branch = view->line[i].data;
5161                         if (strcmp(branch->ref->id, id))
5162                                 continue;
5164                         view->line[i].dirty = TRUE;
5165                         if (reference) {
5166                                 branch->author = reference->author;
5167                                 branch->time = reference->time;
5168                                 continue;
5169                         }
5171                         parse_author_line(line + STRING_SIZE("author "),
5172                                           &branch->author, &branch->time);
5173                         reference = branch;
5174                 }
5175                 return TRUE;
5177         default:
5178                 return TRUE;
5179         }
5183 static bool
5184 branch_open_visitor(void *data, const struct ref *ref)
5186         struct view *view = data;
5187         struct branch *branch;
5189         if (ref->tag || ref->ltag || ref->remote)
5190                 return TRUE;
5192         branch = calloc(1, sizeof(*branch));
5193         if (!branch)
5194                 return FALSE;
5196         branch->ref = ref;
5197         return !!add_line_data(view, branch, LINE_DEFAULT);
5200 static bool
5201 branch_open(struct view *view)
5203         const char *branch_log[] = {
5204                 "git", "log", "--no-color", "--pretty=raw",
5205                         "--simplify-by-decoration", "--all", NULL
5206         };
5208         if (!run_io_rd(&view->io, branch_log, NULL, FORMAT_NONE)) {
5209                 report("Failed to load branch data");
5210                 return TRUE;
5211         }
5213         setup_update(view, view->id);
5214         branch_open_visitor(view, &branch_all);
5215         foreach_ref(branch_open_visitor, view);
5216         view->p_restore = TRUE;
5218         return TRUE;
5221 static bool
5222 branch_grep(struct view *view, struct line *line)
5224         struct branch *branch = line->data;
5225         const char *text[] = {
5226                 branch->ref->name,
5227                 branch->author,
5228                 NULL
5229         };
5231         return grep_text(view, text);
5234 static void
5235 branch_select(struct view *view, struct line *line)
5237         struct branch *branch = line->data;
5239         string_copy_rev(view->ref, branch->ref->id);
5240         string_copy_rev(ref_commit, branch->ref->id);
5241         string_copy_rev(ref_head, branch->ref->id);
5244 static struct view_ops branch_ops = {
5245         "branch",
5246         NULL,
5247         branch_open,
5248         branch_read,
5249         branch_draw,
5250         branch_request,
5251         branch_grep,
5252         branch_select,
5253 };
5255 /*
5256  * Status backend
5257  */
5259 struct status {
5260         char status;
5261         struct {
5262                 mode_t mode;
5263                 char rev[SIZEOF_REV];
5264                 char name[SIZEOF_STR];
5265         } old;
5266         struct {
5267                 mode_t mode;
5268                 char rev[SIZEOF_REV];
5269                 char name[SIZEOF_STR];
5270         } new;
5271 };
5273 static char status_onbranch[SIZEOF_STR];
5274 static struct status stage_status;
5275 static enum line_type stage_line_type;
5276 static size_t stage_chunks;
5277 static int *stage_chunk;
5279 DEFINE_ALLOCATOR(realloc_ints, int, 32)
5281 /* This should work even for the "On branch" line. */
5282 static inline bool
5283 status_has_none(struct view *view, struct line *line)
5285         return line < view->line + view->lines && !line[1].data;
5288 /* Get fields from the diff line:
5289  * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
5290  */
5291 static inline bool
5292 status_get_diff(struct status *file, const char *buf, size_t bufsize)
5294         const char *old_mode = buf +  1;
5295         const char *new_mode = buf +  8;
5296         const char *old_rev  = buf + 15;
5297         const char *new_rev  = buf + 56;
5298         const char *status   = buf + 97;
5300         if (bufsize < 98 ||
5301             old_mode[-1] != ':' ||
5302             new_mode[-1] != ' ' ||
5303             old_rev[-1]  != ' ' ||
5304             new_rev[-1]  != ' ' ||
5305             status[-1]   != ' ')
5306                 return FALSE;
5308         file->status = *status;
5310         string_copy_rev(file->old.rev, old_rev);
5311         string_copy_rev(file->new.rev, new_rev);
5313         file->old.mode = strtoul(old_mode, NULL, 8);
5314         file->new.mode = strtoul(new_mode, NULL, 8);
5316         file->old.name[0] = file->new.name[0] = 0;
5318         return TRUE;
5321 static bool
5322 status_run(struct view *view, const char *argv[], char status, enum line_type type)
5324         struct status *unmerged = NULL;
5325         char *buf;
5326         struct io io = {};
5328         if (!run_io(&io, argv, opt_cdup, IO_RD))
5329                 return FALSE;
5331         add_line_data(view, NULL, type);
5333         while ((buf = io_get(&io, 0, TRUE))) {
5334                 struct status *file = unmerged;
5336                 if (!file) {
5337                         file = calloc(1, sizeof(*file));
5338                         if (!file || !add_line_data(view, file, type))
5339                                 goto error_out;
5340                 }
5342                 /* Parse diff info part. */
5343                 if (status) {
5344                         file->status = status;
5345                         if (status == 'A')
5346                                 string_copy(file->old.rev, NULL_ID);
5348                 } else if (!file->status || file == unmerged) {
5349                         if (!status_get_diff(file, buf, strlen(buf)))
5350                                 goto error_out;
5352                         buf = io_get(&io, 0, TRUE);
5353                         if (!buf)
5354                                 break;
5356                         /* Collapse all modified entries that follow an
5357                          * associated unmerged entry. */
5358                         if (unmerged == file) {
5359                                 unmerged->status = 'U';
5360                                 unmerged = NULL;
5361                         } else if (file->status == 'U') {
5362                                 unmerged = file;
5363                         }
5364                 }
5366                 /* Grab the old name for rename/copy. */
5367                 if (!*file->old.name &&
5368                     (file->status == 'R' || file->status == 'C')) {
5369                         string_ncopy(file->old.name, buf, strlen(buf));
5371                         buf = io_get(&io, 0, TRUE);
5372                         if (!buf)
5373                                 break;
5374                 }
5376                 /* git-ls-files just delivers a NUL separated list of
5377                  * file names similar to the second half of the
5378                  * git-diff-* output. */
5379                 string_ncopy(file->new.name, buf, strlen(buf));
5380                 if (!*file->old.name)
5381                         string_copy(file->old.name, file->new.name);
5382                 file = NULL;
5383         }
5385         if (io_error(&io)) {
5386 error_out:
5387                 done_io(&io);
5388                 return FALSE;
5389         }
5391         if (!view->line[view->lines - 1].data)
5392                 add_line_data(view, NULL, LINE_STAT_NONE);
5394         done_io(&io);
5395         return TRUE;
5398 /* Don't show unmerged entries in the staged section. */
5399 static const char *status_diff_index_argv[] = {
5400         "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
5401                              "--cached", "-M", "HEAD", NULL
5402 };
5404 static const char *status_diff_files_argv[] = {
5405         "git", "diff-files", "-z", NULL
5406 };
5408 static const char *status_list_other_argv[] = {
5409         "git", "ls-files", "-z", "--others", "--exclude-standard", NULL
5410 };
5412 static const char *status_list_no_head_argv[] = {
5413         "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
5414 };
5416 static const char *update_index_argv[] = {
5417         "git", "update-index", "-q", "--unmerged", "--refresh", NULL
5418 };
5420 /* Restore the previous line number to stay in the context or select a
5421  * line with something that can be updated. */
5422 static void
5423 status_restore(struct view *view)
5425         if (view->p_lineno >= view->lines)
5426                 view->p_lineno = view->lines - 1;
5427         while (view->p_lineno < view->lines && !view->line[view->p_lineno].data)
5428                 view->p_lineno++;
5429         while (view->p_lineno > 0 && !view->line[view->p_lineno].data)
5430                 view->p_lineno--;
5432         /* If the above fails, always skip the "On branch" line. */
5433         if (view->p_lineno < view->lines)
5434                 view->lineno = view->p_lineno;
5435         else
5436                 view->lineno = 1;
5438         if (view->lineno < view->offset)
5439                 view->offset = view->lineno;
5440         else if (view->offset + view->height <= view->lineno)
5441                 view->offset = view->lineno - view->height + 1;
5443         view->p_restore = FALSE;
5446 static void
5447 status_update_onbranch(void)
5449         static const char *paths[][2] = {
5450                 { "rebase-apply/rebasing",      "Rebasing" },
5451                 { "rebase-apply/applying",      "Applying mailbox" },
5452                 { "rebase-apply/",              "Rebasing mailbox" },
5453                 { "rebase-merge/interactive",   "Interactive rebase" },
5454                 { "rebase-merge/",              "Rebase merge" },
5455                 { "MERGE_HEAD",                 "Merging" },
5456                 { "BISECT_LOG",                 "Bisecting" },
5457                 { "HEAD",                       "On branch" },
5458         };
5459         char buf[SIZEOF_STR];
5460         struct stat stat;
5461         int i;
5463         if (is_initial_commit()) {
5464                 string_copy(status_onbranch, "Initial commit");
5465                 return;
5466         }
5468         for (i = 0; i < ARRAY_SIZE(paths); i++) {
5469                 char *head = opt_head;
5471                 if (!string_format(buf, "%s/%s", opt_git_dir, paths[i][0]) ||
5472                     lstat(buf, &stat) < 0)
5473                         continue;
5475                 if (!*opt_head) {
5476                         struct io io = {};
5478                         if (io_open(&io, "%s/rebase-merge/head-name", opt_git_dir) &&
5479                             io_read_buf(&io, buf, sizeof(buf))) {
5480                                 head = buf;
5481                                 if (!prefixcmp(head, "refs/heads/"))
5482                                         head += STRING_SIZE("refs/heads/");
5483                         }
5484                 }
5486                 if (!string_format(status_onbranch, "%s %s", paths[i][1], head))
5487                         string_copy(status_onbranch, opt_head);
5488                 return;
5489         }
5491         string_copy(status_onbranch, "Not currently on any branch");
5494 /* First parse staged info using git-diff-index(1), then parse unstaged
5495  * info using git-diff-files(1), and finally untracked files using
5496  * git-ls-files(1). */
5497 static bool
5498 status_open(struct view *view)
5500         reset_view(view);
5502         add_line_data(view, NULL, LINE_STAT_HEAD);
5503         status_update_onbranch();
5505         run_io_bg(update_index_argv);
5507         if (is_initial_commit()) {
5508                 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
5509                         return FALSE;
5510         } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
5511                 return FALSE;
5512         }
5514         if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
5515             !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
5516                 return FALSE;
5518         /* Restore the exact position or use the specialized restore
5519          * mode? */
5520         if (!view->p_restore)
5521                 status_restore(view);
5522         return TRUE;
5525 static bool
5526 status_draw(struct view *view, struct line *line, unsigned int lineno)
5528         struct status *status = line->data;
5529         enum line_type type;
5530         const char *text;
5532         if (!status) {
5533                 switch (line->type) {
5534                 case LINE_STAT_STAGED:
5535                         type = LINE_STAT_SECTION;
5536                         text = "Changes to be committed:";
5537                         break;
5539                 case LINE_STAT_UNSTAGED:
5540                         type = LINE_STAT_SECTION;
5541                         text = "Changed but not updated:";
5542                         break;
5544                 case LINE_STAT_UNTRACKED:
5545                         type = LINE_STAT_SECTION;
5546                         text = "Untracked files:";
5547                         break;
5549                 case LINE_STAT_NONE:
5550                         type = LINE_DEFAULT;
5551                         text = "  (no files)";
5552                         break;
5554                 case LINE_STAT_HEAD:
5555                         type = LINE_STAT_HEAD;
5556                         text = status_onbranch;
5557                         break;
5559                 default:
5560                         return FALSE;
5561                 }
5562         } else {
5563                 static char buf[] = { '?', ' ', ' ', ' ', 0 };
5565                 buf[0] = status->status;
5566                 if (draw_text(view, line->type, buf, TRUE))
5567                         return TRUE;
5568                 type = LINE_DEFAULT;
5569                 text = status->new.name;
5570         }
5572         draw_text(view, type, text, TRUE);
5573         return TRUE;
5576 static enum request
5577 status_load_error(struct view *view, struct view *stage, const char *path)
5579         if (displayed_views() == 2 || display[current_view] != view)
5580                 maximize_view(view);
5581         report("Failed to load '%s': %s", path, io_strerror(&stage->io));
5582         return REQ_NONE;
5585 static enum request
5586 status_enter(struct view *view, struct line *line)
5588         struct status *status = line->data;
5589         const char *oldpath = status ? status->old.name : NULL;
5590         /* Diffs for unmerged entries are empty when passing the new
5591          * path, so leave it empty. */
5592         const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
5593         const char *info;
5594         enum open_flags split;
5595         struct view *stage = VIEW(REQ_VIEW_STAGE);
5597         if (line->type == LINE_STAT_NONE ||
5598             (!status && line[1].type == LINE_STAT_NONE)) {
5599                 report("No file to diff");
5600                 return REQ_NONE;
5601         }
5603         switch (line->type) {
5604         case LINE_STAT_STAGED:
5605                 if (is_initial_commit()) {
5606                         const char *no_head_diff_argv[] = {
5607                                 "git", "diff", "--no-color", "--patch-with-stat",
5608                                         "--", "/dev/null", newpath, NULL
5609                         };
5611                         if (!prepare_update(stage, no_head_diff_argv, opt_cdup, FORMAT_DASH))
5612                                 return status_load_error(view, stage, newpath);
5613                 } else {
5614                         const char *index_show_argv[] = {
5615                                 "git", "diff-index", "--root", "--patch-with-stat",
5616                                         "-C", "-M", "--cached", "HEAD", "--",
5617                                         oldpath, newpath, NULL
5618                         };
5620                         if (!prepare_update(stage, index_show_argv, opt_cdup, FORMAT_DASH))
5621                                 return status_load_error(view, stage, newpath);
5622                 }
5624                 if (status)
5625                         info = "Staged changes to %s";
5626                 else
5627                         info = "Staged changes";
5628                 break;
5630         case LINE_STAT_UNSTAGED:
5631         {
5632                 const char *files_show_argv[] = {
5633                         "git", "diff-files", "--root", "--patch-with-stat",
5634                                 "-C", "-M", "--", oldpath, newpath, NULL
5635                 };
5637                 if (!prepare_update(stage, files_show_argv, opt_cdup, FORMAT_DASH))
5638                         return status_load_error(view, stage, newpath);
5639                 if (status)
5640                         info = "Unstaged changes to %s";
5641                 else
5642                         info = "Unstaged changes";
5643                 break;
5644         }
5645         case LINE_STAT_UNTRACKED:
5646                 if (!newpath) {
5647                         report("No file to show");
5648                         return REQ_NONE;
5649                 }
5651                 if (!suffixcmp(status->new.name, -1, "/")) {
5652                         report("Cannot display a directory");
5653                         return REQ_NONE;
5654                 }
5656                 if (!prepare_update_file(stage, newpath))
5657                         return status_load_error(view, stage, newpath);
5658                 info = "Untracked file %s";
5659                 break;
5661         case LINE_STAT_HEAD:
5662                 return REQ_NONE;
5664         default:
5665                 die("line type %d not handled in switch", line->type);
5666         }
5668         split = view_is_displayed(view) ? OPEN_SPLIT : 0;
5669         open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split);
5670         if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
5671                 if (status) {
5672                         stage_status = *status;
5673                 } else {
5674                         memset(&stage_status, 0, sizeof(stage_status));
5675                 }
5677                 stage_line_type = line->type;
5678                 stage_chunks = 0;
5679                 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
5680         }
5682         return REQ_NONE;
5685 static bool
5686 status_exists(struct status *status, enum line_type type)
5688         struct view *view = VIEW(REQ_VIEW_STATUS);
5689         unsigned long lineno;
5691         for (lineno = 0; lineno < view->lines; lineno++) {
5692                 struct line *line = &view->line[lineno];
5693                 struct status *pos = line->data;
5695                 if (line->type != type)
5696                         continue;
5697                 if (!pos && (!status || !status->status) && line[1].data) {
5698                         select_view_line(view, lineno);
5699                         return TRUE;
5700                 }
5701                 if (pos && !strcmp(status->new.name, pos->new.name)) {
5702                         select_view_line(view, lineno);
5703                         return TRUE;
5704                 }
5705         }
5707         return FALSE;
5711 static bool
5712 status_update_prepare(struct io *io, enum line_type type)
5714         const char *staged_argv[] = {
5715                 "git", "update-index", "-z", "--index-info", NULL
5716         };
5717         const char *others_argv[] = {
5718                 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
5719         };
5721         switch (type) {
5722         case LINE_STAT_STAGED:
5723                 return run_io(io, staged_argv, opt_cdup, IO_WR);
5725         case LINE_STAT_UNSTAGED:
5726         case LINE_STAT_UNTRACKED:
5727                 return run_io(io, others_argv, opt_cdup, IO_WR);
5729         default:
5730                 die("line type %d not handled in switch", type);
5731                 return FALSE;
5732         }
5735 static bool
5736 status_update_write(struct io *io, struct status *status, enum line_type type)
5738         char buf[SIZEOF_STR];
5739         size_t bufsize = 0;
5741         switch (type) {
5742         case LINE_STAT_STAGED:
5743                 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
5744                                         status->old.mode,
5745                                         status->old.rev,
5746                                         status->old.name, 0))
5747                         return FALSE;
5748                 break;
5750         case LINE_STAT_UNSTAGED:
5751         case LINE_STAT_UNTRACKED:
5752                 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
5753                         return FALSE;
5754                 break;
5756         default:
5757                 die("line type %d not handled in switch", type);
5758         }
5760         return io_write(io, buf, bufsize);
5763 static bool
5764 status_update_file(struct status *status, enum line_type type)
5766         struct io io = {};
5767         bool result;
5769         if (!status_update_prepare(&io, type))
5770                 return FALSE;
5772         result = status_update_write(&io, status, type);
5773         return done_io(&io) && result;
5776 static bool
5777 status_update_files(struct view *view, struct line *line)
5779         char buf[sizeof(view->ref)];
5780         struct io io = {};
5781         bool result = TRUE;
5782         struct line *pos = view->line + view->lines;
5783         int files = 0;
5784         int file, done;
5785         int cursor_y = -1, cursor_x = -1;
5787         if (!status_update_prepare(&io, line->type))
5788                 return FALSE;
5790         for (pos = line; pos < view->line + view->lines && pos->data; pos++)
5791                 files++;
5793         string_copy(buf, view->ref);
5794         getsyx(cursor_y, cursor_x);
5795         for (file = 0, done = 5; result && file < files; line++, file++) {
5796                 int almost_done = file * 100 / files;
5798                 if (almost_done > done) {
5799                         done = almost_done;
5800                         string_format(view->ref, "updating file %u of %u (%d%% done)",
5801                                       file, files, done);
5802                         update_view_title(view);
5803                         setsyx(cursor_y, cursor_x);
5804                         doupdate();
5805                 }
5806                 result = status_update_write(&io, line->data, line->type);
5807         }
5808         string_copy(view->ref, buf);
5810         return done_io(&io) && result;
5813 static bool
5814 status_update(struct view *view)
5816         struct line *line = &view->line[view->lineno];
5818         assert(view->lines);
5820         if (!line->data) {
5821                 /* This should work even for the "On branch" line. */
5822                 if (line < view->line + view->lines && !line[1].data) {
5823                         report("Nothing to update");
5824                         return FALSE;
5825                 }
5827                 if (!status_update_files(view, line + 1)) {
5828                         report("Failed to update file status");
5829                         return FALSE;
5830                 }
5832         } else if (!status_update_file(line->data, line->type)) {
5833                 report("Failed to update file status");
5834                 return FALSE;
5835         }
5837         return TRUE;
5840 static bool
5841 status_revert(struct status *status, enum line_type type, bool has_none)
5843         if (!status || type != LINE_STAT_UNSTAGED) {
5844                 if (type == LINE_STAT_STAGED) {
5845                         report("Cannot revert changes to staged files");
5846                 } else if (type == LINE_STAT_UNTRACKED) {
5847                         report("Cannot revert changes to untracked files");
5848                 } else if (has_none) {
5849                         report("Nothing to revert");
5850                 } else {
5851                         report("Cannot revert changes to multiple files");
5852                 }
5854         } else if (prompt_yesno("Are you sure you want to revert changes?")) {
5855                 char mode[10] = "100644";
5856                 const char *reset_argv[] = {
5857                         "git", "update-index", "--cacheinfo", mode,
5858                                 status->old.rev, status->old.name, NULL
5859                 };
5860                 const char *checkout_argv[] = {
5861                         "git", "checkout", "--", status->old.name, NULL
5862                 };
5864                 if (status->status == 'U') {
5865                         string_format(mode, "%5o", status->old.mode);
5867                         if (status->old.mode == 0 && status->new.mode == 0) {
5868                                 reset_argv[2] = "--force-remove";
5869                                 reset_argv[3] = status->old.name;
5870                                 reset_argv[4] = NULL;
5871                         }
5873                         if (!run_io_fg(reset_argv, opt_cdup))
5874                                 return FALSE;
5875                         if (status->old.mode == 0 && status->new.mode == 0)
5876                                 return TRUE;
5877                 }
5879                 return run_io_fg(checkout_argv, opt_cdup);
5880         }
5882         return FALSE;
5885 static enum request
5886 status_request(struct view *view, enum request request, struct line *line)
5888         struct status *status = line->data;
5890         switch (request) {
5891         case REQ_STATUS_UPDATE:
5892                 if (!status_update(view))
5893                         return REQ_NONE;
5894                 break;
5896         case REQ_STATUS_REVERT:
5897                 if (!status_revert(status, line->type, status_has_none(view, line)))
5898                         return REQ_NONE;
5899                 break;
5901         case REQ_STATUS_MERGE:
5902                 if (!status || status->status != 'U') {
5903                         report("Merging only possible for files with unmerged status ('U').");
5904                         return REQ_NONE;
5905                 }
5906                 open_mergetool(status->new.name);
5907                 break;
5909         case REQ_EDIT:
5910                 if (!status)
5911                         return request;
5912                 if (status->status == 'D') {
5913                         report("File has been deleted.");
5914                         return REQ_NONE;
5915                 }
5917                 open_editor(status->status != '?', status->new.name);
5918                 break;
5920         case REQ_VIEW_BLAME:
5921                 if (status)
5922                         opt_ref[0] = 0;
5923                 return request;
5925         case REQ_ENTER:
5926                 /* After returning the status view has been split to
5927                  * show the stage view. No further reloading is
5928                  * necessary. */
5929                 return status_enter(view, line);
5931         case REQ_REFRESH:
5932                 /* Simply reload the view. */
5933                 break;
5935         default:
5936                 return request;
5937         }
5939         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
5941         return REQ_NONE;
5944 static void
5945 status_select(struct view *view, struct line *line)
5947         struct status *status = line->data;
5948         char file[SIZEOF_STR] = "all files";
5949         const char *text;
5950         const char *key;
5952         if (status && !string_format(file, "'%s'", status->new.name))
5953                 return;
5955         if (!status && line[1].type == LINE_STAT_NONE)
5956                 line++;
5958         switch (line->type) {
5959         case LINE_STAT_STAGED:
5960                 text = "Press %s to unstage %s for commit";
5961                 break;
5963         case LINE_STAT_UNSTAGED:
5964                 text = "Press %s to stage %s for commit";
5965                 break;
5967         case LINE_STAT_UNTRACKED:
5968                 text = "Press %s to stage %s for addition";
5969                 break;
5971         case LINE_STAT_HEAD:
5972         case LINE_STAT_NONE:
5973                 text = "Nothing to update";
5974                 break;
5976         default:
5977                 die("line type %d not handled in switch", line->type);
5978         }
5980         if (status && status->status == 'U') {
5981                 text = "Press %s to resolve conflict in %s";
5982                 key = get_key(KEYMAP_STATUS, REQ_STATUS_MERGE);
5984         } else {
5985                 key = get_key(KEYMAP_STATUS, REQ_STATUS_UPDATE);
5986         }
5988         string_format(view->ref, text, key, file);
5989         if (status)
5990                 string_copy(opt_file, status->new.name);
5993 static bool
5994 status_grep(struct view *view, struct line *line)
5996         struct status *status = line->data;
5998         if (status) {
5999                 const char buf[2] = { status->status, 0 };
6000                 const char *text[] = { status->new.name, buf, NULL };
6002                 return grep_text(view, text);
6003         }
6005         return FALSE;
6008 static struct view_ops status_ops = {
6009         "file",
6010         NULL,
6011         status_open,
6012         NULL,
6013         status_draw,
6014         status_request,
6015         status_grep,
6016         status_select,
6017 };
6020 static bool
6021 stage_diff_write(struct io *io, struct line *line, struct line *end)
6023         while (line < end) {
6024                 if (!io_write(io, line->data, strlen(line->data)) ||
6025                     !io_write(io, "\n", 1))
6026                         return FALSE;
6027                 line++;
6028                 if (line->type == LINE_DIFF_CHUNK ||
6029                     line->type == LINE_DIFF_HEADER)
6030                         break;
6031         }
6033         return TRUE;
6036 static struct line *
6037 stage_diff_find(struct view *view, struct line *line, enum line_type type)
6039         for (; view->line < line; line--)
6040                 if (line->type == type)
6041                         return line;
6043         return NULL;
6046 static bool
6047 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
6049         const char *apply_argv[SIZEOF_ARG] = {
6050                 "git", "apply", "--whitespace=nowarn", NULL
6051         };
6052         struct line *diff_hdr;
6053         struct io io = {};
6054         int argc = 3;
6056         diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
6057         if (!diff_hdr)
6058                 return FALSE;
6060         if (!revert)
6061                 apply_argv[argc++] = "--cached";
6062         if (revert || stage_line_type == LINE_STAT_STAGED)
6063                 apply_argv[argc++] = "-R";
6064         apply_argv[argc++] = "-";
6065         apply_argv[argc++] = NULL;
6066         if (!run_io(&io, apply_argv, opt_cdup, IO_WR))
6067                 return FALSE;
6069         if (!stage_diff_write(&io, diff_hdr, chunk) ||
6070             !stage_diff_write(&io, chunk, view->line + view->lines))
6071                 chunk = NULL;
6073         done_io(&io);
6074         run_io_bg(update_index_argv);
6076         return chunk ? TRUE : FALSE;
6079 static bool
6080 stage_update(struct view *view, struct line *line)
6082         struct line *chunk = NULL;
6084         if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
6085                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6087         if (chunk) {
6088                 if (!stage_apply_chunk(view, chunk, FALSE)) {
6089                         report("Failed to apply chunk");
6090                         return FALSE;
6091                 }
6093         } else if (!stage_status.status) {
6094                 view = VIEW(REQ_VIEW_STATUS);
6096                 for (line = view->line; line < view->line + view->lines; line++)
6097                         if (line->type == stage_line_type)
6098                                 break;
6100                 if (!status_update_files(view, line + 1)) {
6101                         report("Failed to update files");
6102                         return FALSE;
6103                 }
6105         } else if (!status_update_file(&stage_status, stage_line_type)) {
6106                 report("Failed to update file");
6107                 return FALSE;
6108         }
6110         return TRUE;
6113 static bool
6114 stage_revert(struct view *view, struct line *line)
6116         struct line *chunk = NULL;
6118         if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
6119                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6121         if (chunk) {
6122                 if (!prompt_yesno("Are you sure you want to revert changes?"))
6123                         return FALSE;
6125                 if (!stage_apply_chunk(view, chunk, TRUE)) {
6126                         report("Failed to revert chunk");
6127                         return FALSE;
6128                 }
6129                 return TRUE;
6131         } else {
6132                 return status_revert(stage_status.status ? &stage_status : NULL,
6133                                      stage_line_type, FALSE);
6134         }
6138 static void
6139 stage_next(struct view *view, struct line *line)
6141         int i;
6143         if (!stage_chunks) {
6144                 for (line = view->line; line < view->line + view->lines; line++) {
6145                         if (line->type != LINE_DIFF_CHUNK)
6146                                 continue;
6148                         if (!realloc_ints(&stage_chunk, stage_chunks, 1)) {
6149                                 report("Allocation failure");
6150                                 return;
6151                         }
6153                         stage_chunk[stage_chunks++] = line - view->line;
6154                 }
6155         }
6157         for (i = 0; i < stage_chunks; i++) {
6158                 if (stage_chunk[i] > view->lineno) {
6159                         do_scroll_view(view, stage_chunk[i] - view->lineno);
6160                         report("Chunk %d of %d", i + 1, stage_chunks);
6161                         return;
6162                 }
6163         }
6165         report("No next chunk found");
6168 static enum request
6169 stage_request(struct view *view, enum request request, struct line *line)
6171         switch (request) {
6172         case REQ_STATUS_UPDATE:
6173                 if (!stage_update(view, line))
6174                         return REQ_NONE;
6175                 break;
6177         case REQ_STATUS_REVERT:
6178                 if (!stage_revert(view, line))
6179                         return REQ_NONE;
6180                 break;
6182         case REQ_STAGE_NEXT:
6183                 if (stage_line_type == LINE_STAT_UNTRACKED) {
6184                         report("File is untracked; press %s to add",
6185                                get_key(KEYMAP_STAGE, REQ_STATUS_UPDATE));
6186                         return REQ_NONE;
6187                 }
6188                 stage_next(view, line);
6189                 return REQ_NONE;
6191         case REQ_EDIT:
6192                 if (!stage_status.new.name[0])
6193                         return request;
6194                 if (stage_status.status == 'D') {
6195                         report("File has been deleted.");
6196                         return REQ_NONE;
6197                 }
6199                 open_editor(stage_status.status != '?', stage_status.new.name);
6200                 break;
6202         case REQ_REFRESH:
6203                 /* Reload everything ... */
6204                 break;
6206         case REQ_VIEW_BLAME:
6207                 if (stage_status.new.name[0]) {
6208                         string_copy(opt_file, stage_status.new.name);
6209                         opt_ref[0] = 0;
6210                 }
6211                 return request;
6213         case REQ_ENTER:
6214                 return pager_request(view, request, line);
6216         default:
6217                 return request;
6218         }
6220         VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
6221         open_view(view, REQ_VIEW_STATUS, OPEN_REFRESH);
6223         /* Check whether the staged entry still exists, and close the
6224          * stage view if it doesn't. */
6225         if (!status_exists(&stage_status, stage_line_type)) {
6226                 status_restore(VIEW(REQ_VIEW_STATUS));
6227                 return REQ_VIEW_CLOSE;
6228         }
6230         if (stage_line_type == LINE_STAT_UNTRACKED) {
6231                 if (!suffixcmp(stage_status.new.name, -1, "/")) {
6232                         report("Cannot display a directory");
6233                         return REQ_NONE;
6234                 }
6236                 if (!prepare_update_file(view, stage_status.new.name)) {
6237                         report("Failed to open file: %s", strerror(errno));
6238                         return REQ_NONE;
6239                 }
6240         }
6241         open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
6243         return REQ_NONE;
6246 static struct view_ops stage_ops = {
6247         "line",
6248         NULL,
6249         NULL,
6250         pager_read,
6251         pager_draw,
6252         stage_request,
6253         pager_grep,
6254         pager_select,
6255 };
6258 /*
6259  * Revision graph
6260  */
6262 struct commit {
6263         char id[SIZEOF_REV];            /* SHA1 ID. */
6264         char title[128];                /* First line of the commit message. */
6265         const char *author;             /* Author of the commit. */
6266         time_t time;                    /* Date from the author ident. */
6267         struct ref_list *refs;          /* Repository references. */
6268         chtype graph[SIZEOF_REVGRAPH];  /* Ancestry chain graphics. */
6269         size_t graph_size;              /* The width of the graph array. */
6270         bool has_parents;               /* Rewritten --parents seen. */
6271 };
6273 /* Size of rev graph with no  "padding" columns */
6274 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
6276 struct rev_graph {
6277         struct rev_graph *prev, *next, *parents;
6278         char rev[SIZEOF_REVITEMS][SIZEOF_REV];
6279         size_t size;
6280         struct commit *commit;
6281         size_t pos;
6282         unsigned int boundary:1;
6283 };
6285 /* Parents of the commit being visualized. */
6286 static struct rev_graph graph_parents[4];
6288 /* The current stack of revisions on the graph. */
6289 static struct rev_graph graph_stacks[4] = {
6290         { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
6291         { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
6292         { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
6293         { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
6294 };
6296 static inline bool
6297 graph_parent_is_merge(struct rev_graph *graph)
6299         return graph->parents->size > 1;
6302 static inline void
6303 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
6305         struct commit *commit = graph->commit;
6307         if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
6308                 commit->graph[commit->graph_size++] = symbol;
6311 static void
6312 clear_rev_graph(struct rev_graph *graph)
6314         graph->boundary = 0;
6315         graph->size = graph->pos = 0;
6316         graph->commit = NULL;
6317         memset(graph->parents, 0, sizeof(*graph->parents));
6320 static void
6321 done_rev_graph(struct rev_graph *graph)
6323         if (graph_parent_is_merge(graph) &&
6324             graph->pos < graph->size - 1 &&
6325             graph->next->size == graph->size + graph->parents->size - 1) {
6326                 size_t i = graph->pos + graph->parents->size - 1;
6328                 graph->commit->graph_size = i * 2;
6329                 while (i < graph->next->size - 1) {
6330                         append_to_rev_graph(graph, ' ');
6331                         append_to_rev_graph(graph, '\\');
6332                         i++;
6333                 }
6334         }
6336         clear_rev_graph(graph);
6339 static void
6340 push_rev_graph(struct rev_graph *graph, const char *parent)
6342         int i;
6344         /* "Collapse" duplicate parents lines.
6345          *
6346          * FIXME: This needs to also update update the drawn graph but
6347          * for now it just serves as a method for pruning graph lines. */
6348         for (i = 0; i < graph->size; i++)
6349                 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
6350                         return;
6352         if (graph->size < SIZEOF_REVITEMS) {
6353                 string_copy_rev(graph->rev[graph->size++], parent);
6354         }
6357 static chtype
6358 get_rev_graph_symbol(struct rev_graph *graph)
6360         chtype symbol;
6362         if (graph->boundary)
6363                 symbol = REVGRAPH_BOUND;
6364         else if (graph->parents->size == 0)
6365                 symbol = REVGRAPH_INIT;
6366         else if (graph_parent_is_merge(graph))
6367                 symbol = REVGRAPH_MERGE;
6368         else if (graph->pos >= graph->size)
6369                 symbol = REVGRAPH_BRANCH;
6370         else
6371                 symbol = REVGRAPH_COMMIT;
6373         return symbol;
6376 static void
6377 draw_rev_graph(struct rev_graph *graph)
6379         struct rev_filler {
6380                 chtype separator, line;
6381         };
6382         enum { DEFAULT, RSHARP, RDIAG, LDIAG };
6383         static struct rev_filler fillers[] = {
6384                 { ' ',  '|' },
6385                 { '`',  '.' },
6386                 { '\'', ' ' },
6387                 { '/',  ' ' },
6388         };
6389         chtype symbol = get_rev_graph_symbol(graph);
6390         struct rev_filler *filler;
6391         size_t i;
6393         if (opt_line_graphics)
6394                 fillers[DEFAULT].line = line_graphics[LINE_GRAPHIC_VLINE];
6396         filler = &fillers[DEFAULT];
6398         for (i = 0; i < graph->pos; i++) {
6399                 append_to_rev_graph(graph, filler->line);
6400                 if (graph_parent_is_merge(graph->prev) &&
6401                     graph->prev->pos == i)
6402                         filler = &fillers[RSHARP];
6404                 append_to_rev_graph(graph, filler->separator);
6405         }
6407         /* Place the symbol for this revision. */
6408         append_to_rev_graph(graph, symbol);
6410         if (graph->prev->size > graph->size)
6411                 filler = &fillers[RDIAG];
6412         else
6413                 filler = &fillers[DEFAULT];
6415         i++;
6417         for (; i < graph->size; i++) {
6418                 append_to_rev_graph(graph, filler->separator);
6419                 append_to_rev_graph(graph, filler->line);
6420                 if (graph_parent_is_merge(graph->prev) &&
6421                     i < graph->prev->pos + graph->parents->size)
6422                         filler = &fillers[RSHARP];
6423                 if (graph->prev->size > graph->size)
6424                         filler = &fillers[LDIAG];
6425         }
6427         if (graph->prev->size > graph->size) {
6428                 append_to_rev_graph(graph, filler->separator);
6429                 if (filler->line != ' ')
6430                         append_to_rev_graph(graph, filler->line);
6431         }
6434 /* Prepare the next rev graph */
6435 static void
6436 prepare_rev_graph(struct rev_graph *graph)
6438         size_t i;
6440         /* First, traverse all lines of revisions up to the active one. */
6441         for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
6442                 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
6443                         break;
6445                 push_rev_graph(graph->next, graph->rev[graph->pos]);
6446         }
6448         /* Interleave the new revision parent(s). */
6449         for (i = 0; !graph->boundary && i < graph->parents->size; i++)
6450                 push_rev_graph(graph->next, graph->parents->rev[i]);
6452         /* Lastly, put any remaining revisions. */
6453         for (i = graph->pos + 1; i < graph->size; i++)
6454                 push_rev_graph(graph->next, graph->rev[i]);
6457 static void
6458 update_rev_graph(struct view *view, struct rev_graph *graph)
6460         /* If this is the finalizing update ... */
6461         if (graph->commit)
6462                 prepare_rev_graph(graph);
6464         /* Graph visualization needs a one rev look-ahead,
6465          * so the first update doesn't visualize anything. */
6466         if (!graph->prev->commit)
6467                 return;
6469         if (view->lines > 2)
6470                 view->line[view->lines - 3].dirty = 1;
6471         if (view->lines > 1)
6472                 view->line[view->lines - 2].dirty = 1;
6473         draw_rev_graph(graph->prev);
6474         done_rev_graph(graph->prev->prev);
6478 /*
6479  * Main view backend
6480  */
6482 static const char *main_argv[SIZEOF_ARG] = {
6483         "git", "log", "--no-color", "--pretty=raw", "--parents",
6484                       "--topo-order", "%(head)", NULL
6485 };
6487 static bool
6488 main_draw(struct view *view, struct line *line, unsigned int lineno)
6490         struct commit *commit = line->data;
6492         if (!commit->author)
6493                 return FALSE;
6495         if (opt_date && draw_date(view, &commit->time))
6496                 return TRUE;
6498         if (opt_author && draw_author(view, commit->author))
6499                 return TRUE;
6501         if (opt_rev_graph && commit->graph_size &&
6502             draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
6503                 return TRUE;
6505         if (opt_show_refs && commit->refs) {
6506                 size_t i;
6508                 for (i = 0; i < commit->refs->size; i++) {
6509                         struct ref *ref = commit->refs->refs[i];
6510                         enum line_type type;
6512                         if (ref->head)
6513                                 type = LINE_MAIN_HEAD;
6514                         else if (ref->ltag)
6515                                 type = LINE_MAIN_LOCAL_TAG;
6516                         else if (ref->tag)
6517                                 type = LINE_MAIN_TAG;
6518                         else if (ref->tracked)
6519                                 type = LINE_MAIN_TRACKED;
6520                         else if (ref->remote)
6521                                 type = LINE_MAIN_REMOTE;
6522                         else
6523                                 type = LINE_MAIN_REF;
6525                         if (draw_text(view, type, "[", TRUE) ||
6526                             draw_text(view, type, ref->name, TRUE) ||
6527                             draw_text(view, type, "]", TRUE))
6528                                 return TRUE;
6530                         if (draw_text(view, LINE_DEFAULT, " ", TRUE))
6531                                 return TRUE;
6532                 }
6533         }
6535         draw_text(view, LINE_DEFAULT, commit->title, TRUE);
6536         return TRUE;
6539 /* Reads git log --pretty=raw output and parses it into the commit struct. */
6540 static bool
6541 main_read(struct view *view, char *line)
6543         static struct rev_graph *graph = graph_stacks;
6544         enum line_type type;
6545         struct commit *commit;
6547         if (!line) {
6548                 int i;
6550                 if (!view->lines && !view->parent)
6551                         die("No revisions match the given arguments.");
6552                 if (view->lines > 0) {
6553                         commit = view->line[view->lines - 1].data;
6554                         view->line[view->lines - 1].dirty = 1;
6555                         if (!commit->author) {
6556                                 view->lines--;
6557                                 free(commit);
6558                                 graph->commit = NULL;
6559                         }
6560                 }
6561                 update_rev_graph(view, graph);
6563                 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
6564                         clear_rev_graph(&graph_stacks[i]);
6565                 return TRUE;
6566         }
6568         type = get_line_type(line);
6569         if (type == LINE_COMMIT) {
6570                 commit = calloc(1, sizeof(struct commit));
6571                 if (!commit)
6572                         return FALSE;
6574                 line += STRING_SIZE("commit ");
6575                 if (*line == '-') {
6576                         graph->boundary = 1;
6577                         line++;
6578                 }
6580                 string_copy_rev(commit->id, line);
6581                 commit->refs = get_ref_list(commit->id);
6582                 graph->commit = commit;
6583                 add_line_data(view, commit, LINE_MAIN_COMMIT);
6585                 while ((line = strchr(line, ' '))) {
6586                         line++;
6587                         push_rev_graph(graph->parents, line);
6588                         commit->has_parents = TRUE;
6589                 }
6590                 return TRUE;
6591         }
6593         if (!view->lines)
6594                 return TRUE;
6595         commit = view->line[view->lines - 1].data;
6597         switch (type) {
6598         case LINE_PARENT:
6599                 if (commit->has_parents)
6600                         break;
6601                 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
6602                 break;
6604         case LINE_AUTHOR:
6605                 parse_author_line(line + STRING_SIZE("author "),
6606                                   &commit->author, &commit->time);
6607                 update_rev_graph(view, graph);
6608                 graph = graph->next;
6609                 break;
6611         default:
6612                 /* Fill in the commit title if it has not already been set. */
6613                 if (commit->title[0])
6614                         break;
6616                 /* Require titles to start with a non-space character at the
6617                  * offset used by git log. */
6618                 if (strncmp(line, "    ", 4))
6619                         break;
6620                 line += 4;
6621                 /* Well, if the title starts with a whitespace character,
6622                  * try to be forgiving.  Otherwise we end up with no title. */
6623                 while (isspace(*line))
6624                         line++;
6625                 if (*line == '\0')
6626                         break;
6627                 /* FIXME: More graceful handling of titles; append "..." to
6628                  * shortened titles, etc. */
6630                 string_expand(commit->title, sizeof(commit->title), line, 1);
6631                 view->line[view->lines - 1].dirty = 1;
6632         }
6634         return TRUE;
6637 static enum request
6638 main_request(struct view *view, enum request request, struct line *line)
6640         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
6642         switch (request) {
6643         case REQ_ENTER:
6644                 open_view(view, REQ_VIEW_DIFF, flags);
6645                 break;
6646         case REQ_REFRESH:
6647                 load_refs();
6648                 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
6649                 break;
6650         default:
6651                 return request;
6652         }
6654         return REQ_NONE;
6657 static bool
6658 grep_refs(struct ref_list *list, regex_t *regex)
6660         regmatch_t pmatch;
6661         size_t i;
6663         if (!opt_show_refs || !list)
6664                 return FALSE;
6666         for (i = 0; i < list->size; i++) {
6667                 if (regexec(regex, list->refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
6668                         return TRUE;
6669         }
6671         return FALSE;
6674 static bool
6675 main_grep(struct view *view, struct line *line)
6677         struct commit *commit = line->data;
6678         const char *text[] = {
6679                 commit->title,
6680                 opt_author ? commit->author : "",
6681                 opt_date ? mkdate(&commit->time) : "",
6682                 NULL
6683         };
6685         return grep_text(view, text) || grep_refs(commit->refs, view->regex);
6688 static void
6689 main_select(struct view *view, struct line *line)
6691         struct commit *commit = line->data;
6693         string_copy_rev(view->ref, commit->id);
6694         string_copy_rev(ref_commit, view->ref);
6697 static struct view_ops main_ops = {
6698         "commit",
6699         main_argv,
6700         NULL,
6701         main_read,
6702         main_draw,
6703         main_request,
6704         main_grep,
6705         main_select,
6706 };
6709 /*
6710  * Unicode / UTF-8 handling
6711  *
6712  * NOTE: Much of the following code for dealing with Unicode is derived from
6713  * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
6714  * src/intl/charset.c from the UTF-8 branch commit elinks-0.11.0-g31f2c28.
6715  */
6717 static inline int
6718 unicode_width(unsigned long c)
6720         if (c >= 0x1100 &&
6721            (c <= 0x115f                         /* Hangul Jamo */
6722             || c == 0x2329
6723             || c == 0x232a
6724             || (c >= 0x2e80  && c <= 0xa4cf && c != 0x303f)
6725                                                 /* CJK ... Yi */
6726             || (c >= 0xac00  && c <= 0xd7a3)    /* Hangul Syllables */
6727             || (c >= 0xf900  && c <= 0xfaff)    /* CJK Compatibility Ideographs */
6728             || (c >= 0xfe30  && c <= 0xfe6f)    /* CJK Compatibility Forms */
6729             || (c >= 0xff00  && c <= 0xff60)    /* Fullwidth Forms */
6730             || (c >= 0xffe0  && c <= 0xffe6)
6731             || (c >= 0x20000 && c <= 0x2fffd)
6732             || (c >= 0x30000 && c <= 0x3fffd)))
6733                 return 2;
6735         if (c == '\t')
6736                 return opt_tab_size;
6738         return 1;
6741 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
6742  * Illegal bytes are set one. */
6743 static const unsigned char utf8_bytes[256] = {
6744         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,
6745         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,
6746         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,
6747         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,
6748         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,
6749         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,
6750         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,
6751         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,
6752 };
6754 /* Decode UTF-8 multi-byte representation into a Unicode character. */
6755 static inline unsigned long
6756 utf8_to_unicode(const char *string, size_t length)
6758         unsigned long unicode;
6760         switch (length) {
6761         case 1:
6762                 unicode  =   string[0];
6763                 break;
6764         case 2:
6765                 unicode  =  (string[0] & 0x1f) << 6;
6766                 unicode +=  (string[1] & 0x3f);
6767                 break;
6768         case 3:
6769                 unicode  =  (string[0] & 0x0f) << 12;
6770                 unicode += ((string[1] & 0x3f) << 6);
6771                 unicode +=  (string[2] & 0x3f);
6772                 break;
6773         case 4:
6774                 unicode  =  (string[0] & 0x0f) << 18;
6775                 unicode += ((string[1] & 0x3f) << 12);
6776                 unicode += ((string[2] & 0x3f) << 6);
6777                 unicode +=  (string[3] & 0x3f);
6778                 break;
6779         case 5:
6780                 unicode  =  (string[0] & 0x0f) << 24;
6781                 unicode += ((string[1] & 0x3f) << 18);
6782                 unicode += ((string[2] & 0x3f) << 12);
6783                 unicode += ((string[3] & 0x3f) << 6);
6784                 unicode +=  (string[4] & 0x3f);
6785                 break;
6786         case 6:
6787                 unicode  =  (string[0] & 0x01) << 30;
6788                 unicode += ((string[1] & 0x3f) << 24);
6789                 unicode += ((string[2] & 0x3f) << 18);
6790                 unicode += ((string[3] & 0x3f) << 12);
6791                 unicode += ((string[4] & 0x3f) << 6);
6792                 unicode +=  (string[5] & 0x3f);
6793                 break;
6794         default:
6795                 die("Invalid Unicode length");
6796         }
6798         /* Invalid characters could return the special 0xfffd value but NUL
6799          * should be just as good. */
6800         return unicode > 0xffff ? 0 : unicode;
6803 /* Calculates how much of string can be shown within the given maximum width
6804  * and sets trimmed parameter to non-zero value if all of string could not be
6805  * shown. If the reserve flag is TRUE, it will reserve at least one
6806  * trailing character, which can be useful when drawing a delimiter.
6807  *
6808  * Returns the number of bytes to output from string to satisfy max_width. */
6809 static size_t
6810 utf8_length(const char **start, size_t skip, int *width, size_t max_width, int *trimmed, bool reserve)
6812         const char *string = *start;
6813         const char *end = strchr(string, '\0');
6814         unsigned char last_bytes = 0;
6815         size_t last_ucwidth = 0;
6817         *width = 0;
6818         *trimmed = 0;
6820         while (string < end) {
6821                 int c = *(unsigned char *) string;
6822                 unsigned char bytes = utf8_bytes[c];
6823                 size_t ucwidth;
6824                 unsigned long unicode;
6826                 if (string + bytes > end)
6827                         break;
6829                 /* Change representation to figure out whether
6830                  * it is a single- or double-width character. */
6832                 unicode = utf8_to_unicode(string, bytes);
6833                 /* FIXME: Graceful handling of invalid Unicode character. */
6834                 if (!unicode)
6835                         break;
6837                 ucwidth = unicode_width(unicode);
6838                 if (skip > 0) {
6839                         skip -= ucwidth <= skip ? ucwidth : skip;
6840                         *start += bytes;
6841                 }
6842                 *width  += ucwidth;
6843                 if (*width > max_width) {
6844                         *trimmed = 1;
6845                         *width -= ucwidth;
6846                         if (reserve && *width == max_width) {
6847                                 string -= last_bytes;
6848                                 *width -= last_ucwidth;
6849                         }
6850                         break;
6851                 }
6853                 string  += bytes;
6854                 last_bytes = ucwidth ? bytes : 0;
6855                 last_ucwidth = ucwidth;
6856         }
6858         return string - *start;
6862 /*
6863  * Status management
6864  */
6866 /* Whether or not the curses interface has been initialized. */
6867 static bool cursed = FALSE;
6869 /* Terminal hacks and workarounds. */
6870 static bool use_scroll_redrawwin;
6871 static bool use_scroll_status_wclear;
6873 /* The status window is used for polling keystrokes. */
6874 static WINDOW *status_win;
6876 /* Reading from the prompt? */
6877 static bool input_mode = FALSE;
6879 static bool status_empty = FALSE;
6881 /* Update status and title window. */
6882 static void
6883 report(const char *msg, ...)
6885         struct view *view = display[current_view];
6887         if (input_mode)
6888                 return;
6890         if (!view) {
6891                 char buf[SIZEOF_STR];
6892                 va_list args;
6894                 va_start(args, msg);
6895                 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
6896                         buf[sizeof(buf) - 1] = 0;
6897                         buf[sizeof(buf) - 2] = '.';
6898                         buf[sizeof(buf) - 3] = '.';
6899                         buf[sizeof(buf) - 4] = '.';
6900                 }
6901                 va_end(args);
6902                 die("%s", buf);
6903         }
6905         if (!status_empty || *msg) {
6906                 va_list args;
6908                 va_start(args, msg);
6910                 wmove(status_win, 0, 0);
6911                 if (view->has_scrolled && use_scroll_status_wclear)
6912                         wclear(status_win);
6913                 if (*msg) {
6914                         vwprintw(status_win, msg, args);
6915                         status_empty = FALSE;
6916                 } else {
6917                         status_empty = TRUE;
6918                 }
6919                 wclrtoeol(status_win);
6920                 wnoutrefresh(status_win);
6922                 va_end(args);
6923         }
6925         update_view_title(view);
6928 /* Controls when nodelay should be in effect when polling user input. */
6929 static void
6930 set_nonblocking_input(bool loading)
6932         static unsigned int loading_views;
6934         if ((loading == FALSE && loading_views-- == 1) ||
6935             (loading == TRUE  && loading_views++ == 0))
6936                 nodelay(status_win, loading);
6939 static void
6940 init_display(void)
6942         const char *term;
6943         int x, y;
6945         /* Initialize the curses library */
6946         if (isatty(STDIN_FILENO)) {
6947                 cursed = !!initscr();
6948                 opt_tty = stdin;
6949         } else {
6950                 /* Leave stdin and stdout alone when acting as a pager. */
6951                 opt_tty = fopen("/dev/tty", "r+");
6952                 if (!opt_tty)
6953                         die("Failed to open /dev/tty");
6954                 cursed = !!newterm(NULL, opt_tty, opt_tty);
6955         }
6957         if (!cursed)
6958                 die("Failed to initialize curses");
6960         nonl();         /* Disable conversion and detect newlines from input. */
6961         cbreak();       /* Take input chars one at a time, no wait for \n */
6962         noecho();       /* Don't echo input */
6963         leaveok(stdscr, FALSE);
6965         if (has_colors())
6966                 init_colors();
6968         getmaxyx(stdscr, y, x);
6969         status_win = newwin(1, 0, y - 1, 0);
6970         if (!status_win)
6971                 die("Failed to create status window");
6973         /* Enable keyboard mapping */
6974         keypad(status_win, TRUE);
6975         wbkgdset(status_win, get_line_attr(LINE_STATUS));
6977         TABSIZE = opt_tab_size;
6978         if (opt_line_graphics) {
6979                 line_graphics[LINE_GRAPHIC_VLINE] = ACS_VLINE;
6980         }
6982         term = getenv("XTERM_VERSION") ? NULL : getenv("COLORTERM");
6983         if (term && !strcmp(term, "gnome-terminal")) {
6984                 /* In the gnome-terminal-emulator, the message from
6985                  * scrolling up one line when impossible followed by
6986                  * scrolling down one line causes corruption of the
6987                  * status line. This is fixed by calling wclear. */
6988                 use_scroll_status_wclear = TRUE;
6989                 use_scroll_redrawwin = FALSE;
6991         } else if (term && !strcmp(term, "xrvt-xpm")) {
6992                 /* No problems with full optimizations in xrvt-(unicode)
6993                  * and aterm. */
6994                 use_scroll_status_wclear = use_scroll_redrawwin = FALSE;
6996         } else {
6997                 /* When scrolling in (u)xterm the last line in the
6998                  * scrolling direction will update slowly. */
6999                 use_scroll_redrawwin = TRUE;
7000                 use_scroll_status_wclear = FALSE;
7001         }
7004 static int
7005 get_input(int prompt_position)
7007         struct view *view;
7008         int i, key, cursor_y, cursor_x;
7010         if (prompt_position)
7011                 input_mode = TRUE;
7013         while (TRUE) {
7014                 foreach_view (view, i) {
7015                         update_view(view);
7016                         if (view_is_displayed(view) && view->has_scrolled &&
7017                             use_scroll_redrawwin)
7018                                 redrawwin(view->win);
7019                         view->has_scrolled = FALSE;
7020                 }
7022                 /* Update the cursor position. */
7023                 if (prompt_position) {
7024                         getbegyx(status_win, cursor_y, cursor_x);
7025                         cursor_x = prompt_position;
7026                 } else {
7027                         view = display[current_view];
7028                         getbegyx(view->win, cursor_y, cursor_x);
7029                         cursor_x = view->width - 1;
7030                         cursor_y += view->lineno - view->offset;
7031                 }
7032                 setsyx(cursor_y, cursor_x);
7034                 /* Refresh, accept single keystroke of input */
7035                 doupdate();
7036                 key = wgetch(status_win);
7038                 /* wgetch() with nodelay() enabled returns ERR when
7039                  * there's no input. */
7040                 if (key == ERR) {
7042                 } else if (key == KEY_RESIZE) {
7043                         int height, width;
7045                         getmaxyx(stdscr, height, width);
7047                         wresize(status_win, 1, width);
7048                         mvwin(status_win, height - 1, 0);
7049                         wnoutrefresh(status_win);
7050                         resize_display();
7051                         redraw_display(TRUE);
7053                 } else {
7054                         input_mode = FALSE;
7055                         return key;
7056                 }
7057         }
7060 static char *
7061 prompt_input(const char *prompt, input_handler handler, void *data)
7063         enum input_status status = INPUT_OK;
7064         static char buf[SIZEOF_STR];
7065         size_t pos = 0;
7067         buf[pos] = 0;
7069         while (status == INPUT_OK || status == INPUT_SKIP) {
7070                 int key;
7072                 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
7073                 wclrtoeol(status_win);
7075                 key = get_input(pos + 1);
7076                 switch (key) {
7077                 case KEY_RETURN:
7078                 case KEY_ENTER:
7079                 case '\n':
7080                         status = pos ? INPUT_STOP : INPUT_CANCEL;
7081                         break;
7083                 case KEY_BACKSPACE:
7084                         if (pos > 0)
7085                                 buf[--pos] = 0;
7086                         else
7087                                 status = INPUT_CANCEL;
7088                         break;
7090                 case KEY_ESC:
7091                         status = INPUT_CANCEL;
7092                         break;
7094                 default:
7095                         if (pos >= sizeof(buf)) {
7096                                 report("Input string too long");
7097                                 return NULL;
7098                         }
7100                         status = handler(data, buf, key);
7101                         if (status == INPUT_OK)
7102                                 buf[pos++] = (char) key;
7103                 }
7104         }
7106         /* Clear the status window */
7107         status_empty = FALSE;
7108         report("");
7110         if (status == INPUT_CANCEL)
7111                 return NULL;
7113         buf[pos++] = 0;
7115         return buf;
7118 static enum input_status
7119 prompt_yesno_handler(void *data, char *buf, int c)
7121         if (c == 'y' || c == 'Y')
7122                 return INPUT_STOP;
7123         if (c == 'n' || c == 'N')
7124                 return INPUT_CANCEL;
7125         return INPUT_SKIP;
7128 static bool
7129 prompt_yesno(const char *prompt)
7131         char prompt2[SIZEOF_STR];
7133         if (!string_format(prompt2, "%s [Yy/Nn]", prompt))
7134                 return FALSE;
7136         return !!prompt_input(prompt2, prompt_yesno_handler, NULL);
7139 static enum input_status
7140 read_prompt_handler(void *data, char *buf, int c)
7142         return isprint(c) ? INPUT_OK : INPUT_SKIP;
7145 static char *
7146 read_prompt(const char *prompt)
7148         return prompt_input(prompt, read_prompt_handler, NULL);
7151 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected)
7153         enum input_status status = INPUT_OK;
7154         int size = 0;
7156         while (items[size].text)
7157                 size++;
7159         while (status == INPUT_OK) {
7160                 const struct menu_item *item = &items[*selected];
7161                 int key;
7162                 int i;
7164                 mvwprintw(status_win, 0, 0, "%s (%d of %d) ",
7165                           prompt, *selected + 1, size);
7166                 if (item->hotkey)
7167                         wprintw(status_win, "[%c] ", (char) item->hotkey);
7168                 wprintw(status_win, "%s", item->text);
7169                 wclrtoeol(status_win);
7171                 key = get_input(COLS - 1);
7172                 switch (key) {
7173                 case KEY_RETURN:
7174                 case KEY_ENTER:
7175                 case '\n':
7176                         status = INPUT_STOP;
7177                         break;
7179                 case KEY_LEFT:
7180                 case KEY_UP:
7181                         *selected = *selected - 1;
7182                         if (*selected < 0)
7183                                 *selected = size - 1;
7184                         break;
7186                 case KEY_RIGHT:
7187                 case KEY_DOWN:
7188                         *selected = (*selected + 1) % size;
7189                         break;
7191                 case KEY_ESC:
7192                         status = INPUT_CANCEL;
7193                         break;
7195                 default:
7196                         for (i = 0; items[i].text; i++)
7197                                 if (items[i].hotkey == key) {
7198                                         *selected = i;
7199                                         status = INPUT_STOP;
7200                                         break;
7201                                 }
7202                 }
7203         }
7205         /* Clear the status window */
7206         status_empty = FALSE;
7207         report("");
7209         return status != INPUT_CANCEL;
7212 /*
7213  * Repository properties
7214  */
7216 static struct ref **refs = NULL;
7217 static size_t refs_size = 0;
7219 static struct ref_list **ref_lists = NULL;
7220 static size_t ref_lists_size = 0;
7222 DEFINE_ALLOCATOR(realloc_refs, struct ref *, 256)
7223 DEFINE_ALLOCATOR(realloc_refs_list, struct ref *, 8)
7224 DEFINE_ALLOCATOR(realloc_ref_lists, struct ref_list *, 8)
7226 static int
7227 compare_refs(const void *ref1_, const void *ref2_)
7229         const struct ref *ref1 = *(const struct ref **)ref1_;
7230         const struct ref *ref2 = *(const struct ref **)ref2_;
7232         if (ref1->tag != ref2->tag)
7233                 return ref2->tag - ref1->tag;
7234         if (ref1->ltag != ref2->ltag)
7235                 return ref2->ltag - ref2->ltag;
7236         if (ref1->head != ref2->head)
7237                 return ref2->head - ref1->head;
7238         if (ref1->tracked != ref2->tracked)
7239                 return ref2->tracked - ref1->tracked;
7240         if (ref1->remote != ref2->remote)
7241                 return ref2->remote - ref1->remote;
7242         return strcmp(ref1->name, ref2->name);
7245 static void
7246 foreach_ref(bool (*visitor)(void *data, const struct ref *ref), void *data)
7248         size_t i;
7250         for (i = 0; i < refs_size; i++)
7251                 if (!visitor(data, refs[i]))
7252                         break;
7255 static struct ref_list *
7256 get_ref_list(const char *id)
7258         struct ref_list *list;
7259         size_t i;
7261         for (i = 0; i < ref_lists_size; i++)
7262                 if (!strcmp(id, ref_lists[i]->id))
7263                         return ref_lists[i];
7265         if (!realloc_ref_lists(&ref_lists, ref_lists_size, 1))
7266                 return NULL;
7267         list = calloc(1, sizeof(*list));
7268         if (!list)
7269                 return NULL;
7271         for (i = 0; i < refs_size; i++) {
7272                 if (!strcmp(id, refs[i]->id) &&
7273                     realloc_refs_list(&list->refs, list->size, 1))
7274                         list->refs[list->size++] = refs[i];
7275         }
7277         if (!list->refs) {
7278                 free(list);
7279                 return NULL;
7280         }
7282         qsort(list->refs, list->size, sizeof(*list->refs), compare_refs);
7283         ref_lists[ref_lists_size++] = list;
7284         return list;
7287 static int
7288 read_ref(char *id, size_t idlen, char *name, size_t namelen)
7290         struct ref *ref = NULL;
7291         bool tag = FALSE;
7292         bool ltag = FALSE;
7293         bool remote = FALSE;
7294         bool tracked = FALSE;
7295         bool head = FALSE;
7296         int from = 0, to = refs_size - 1;
7298         if (!prefixcmp(name, "refs/tags/")) {
7299                 if (!suffixcmp(name, namelen, "^{}")) {
7300                         namelen -= 3;
7301                         name[namelen] = 0;
7302                 } else {
7303                         ltag = TRUE;
7304                 }
7306                 tag = TRUE;
7307                 namelen -= STRING_SIZE("refs/tags/");
7308                 name    += STRING_SIZE("refs/tags/");
7310         } else if (!prefixcmp(name, "refs/remotes/")) {
7311                 remote = TRUE;
7312                 namelen -= STRING_SIZE("refs/remotes/");
7313                 name    += STRING_SIZE("refs/remotes/");
7314                 tracked  = !strcmp(opt_remote, name);
7316         } else if (!prefixcmp(name, "refs/heads/")) {
7317                 namelen -= STRING_SIZE("refs/heads/");
7318                 name    += STRING_SIZE("refs/heads/");
7319                 head     = !strncmp(opt_head, name, namelen);
7321         } else if (!strcmp(name, "HEAD")) {
7322                 string_ncopy(opt_head_rev, id, idlen);
7323                 return OK;
7324         }
7326         /* If we are reloading or it's an annotated tag, replace the
7327          * previous SHA1 with the resolved commit id; relies on the fact
7328          * git-ls-remote lists the commit id of an annotated tag right
7329          * before the commit id it points to. */
7330         while (from <= to) {
7331                 size_t pos = (to + from) / 2;
7332                 int cmp = strcmp(name, refs[pos]->name);
7334                 if (!cmp) {
7335                         ref = refs[pos];
7336                         break;
7337                 }
7339                 if (cmp < 0)
7340                         to = pos - 1;
7341                 else
7342                         from = pos + 1;
7343         }
7345         if (!ref) {
7346                 if (!realloc_refs(&refs, refs_size, 1))
7347                         return ERR;
7348                 ref = calloc(1, sizeof(*ref) + namelen);
7349                 if (!ref)
7350                         return ERR;
7351                 memmove(refs + from + 1, refs + from,
7352                         (refs_size - from) * sizeof(*refs));
7353                 refs[from] = ref;
7354                 strncpy(ref->name, name, namelen);
7355                 refs_size++;
7356         }
7358         ref->head = head;
7359         ref->tag = tag;
7360         ref->ltag = ltag;
7361         ref->remote = remote;
7362         ref->tracked = tracked;
7363         string_copy_rev(ref->id, id);
7365         return OK;
7368 static int
7369 load_refs(void)
7371         const char *head_argv[] = {
7372                 "git", "symbolic-ref", "HEAD", NULL
7373         };
7374         static const char *ls_remote_argv[SIZEOF_ARG] = {
7375                 "git", "ls-remote", opt_git_dir, NULL
7376         };
7377         static bool init = FALSE;
7378         size_t i;
7380         if (!init) {
7381                 argv_from_env(ls_remote_argv, "TIG_LS_REMOTE");
7382                 init = TRUE;
7383         }
7385         if (!*opt_git_dir)
7386                 return OK;
7388         if (run_io_buf(head_argv, opt_head, sizeof(opt_head)) &&
7389             !prefixcmp(opt_head, "refs/heads/")) {
7390                 char *offset = opt_head + STRING_SIZE("refs/heads/");
7392                 memmove(opt_head, offset, strlen(offset) + 1);
7393         }
7395         for (i = 0; i < refs_size; i++)
7396                 refs[i]->id[0] = 0;
7398         if (run_io_load(ls_remote_argv, "\t", read_ref) == ERR)
7399                 return ERR;
7401         /* Update the ref lists to reflect changes. */
7402         for (i = 0; i < ref_lists_size; i++) {
7403                 struct ref_list *list = ref_lists[i];
7404                 size_t old, new;
7406                 for (old = new = 0; old < list->size; old++)
7407                         if (!strcmp(list->id, list->refs[old]->id))
7408                                 list->refs[new++] = list->refs[old];
7409                 list->size = new;
7410         }
7412         return OK;
7415 static void
7416 set_remote_branch(const char *name, const char *value, size_t valuelen)
7418         if (!strcmp(name, ".remote")) {
7419                 string_ncopy(opt_remote, value, valuelen);
7421         } else if (*opt_remote && !strcmp(name, ".merge")) {
7422                 size_t from = strlen(opt_remote);
7424                 if (!prefixcmp(value, "refs/heads/"))
7425                         value += STRING_SIZE("refs/heads/");
7427                 if (!string_format_from(opt_remote, &from, "/%s", value))
7428                         opt_remote[0] = 0;
7429         }
7432 static void
7433 set_repo_config_option(char *name, char *value, int (*cmd)(int, const char **))
7435         const char *argv[SIZEOF_ARG] = { name, "=" };
7436         int argc = 1 + (cmd == option_set_command);
7437         int error = ERR;
7439         if (!argv_from_string(argv, &argc, value))
7440                 config_msg = "Too many option arguments";
7441         else
7442                 error = cmd(argc, argv);
7444         if (error == ERR)
7445                 warn("Option 'tig.%s': %s", name, config_msg);
7448 static bool
7449 set_environment_variable(const char *name, const char *value)
7451         size_t len = strlen(name) + 1 + strlen(value) + 1;
7452         char *env = malloc(len);
7454         if (env &&
7455             string_nformat(env, len, NULL, "%s=%s", name, value) &&
7456             putenv(env) == 0)
7457                 return TRUE;
7458         free(env);
7459         return FALSE;
7462 static void
7463 set_work_tree(const char *value)
7465         char cwd[SIZEOF_STR];
7467         if (!getcwd(cwd, sizeof(cwd)))
7468                 die("Failed to get cwd path: %s", strerror(errno));
7469         if (chdir(opt_git_dir) < 0)
7470                 die("Failed to chdir(%s): %s", strerror(errno));
7471         if (!getcwd(opt_git_dir, sizeof(opt_git_dir)))
7472                 die("Failed to get git path: %s", strerror(errno));
7473         if (chdir(cwd) < 0)
7474                 die("Failed to chdir(%s): %s", cwd, strerror(errno));
7475         if (chdir(value) < 0)
7476                 die("Failed to chdir(%s): %s", value, strerror(errno));
7477         if (!getcwd(cwd, sizeof(cwd)))
7478                 die("Failed to get cwd path: %s", strerror(errno));
7479         if (!set_environment_variable("GIT_WORK_TREE", cwd))
7480                 die("Failed to set GIT_WORK_TREE to '%s'", cwd);
7481         if (!set_environment_variable("GIT_DIR", opt_git_dir))
7482                 die("Failed to set GIT_DIR to '%s'", opt_git_dir);
7483         opt_is_inside_work_tree = TRUE;
7486 static int
7487 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
7489         if (!strcmp(name, "i18n.commitencoding"))
7490                 string_ncopy(opt_encoding, value, valuelen);
7492         else if (!strcmp(name, "core.editor"))
7493                 string_ncopy(opt_editor, value, valuelen);
7495         else if (!strcmp(name, "core.worktree"))
7496                 set_work_tree(value);
7498         else if (!prefixcmp(name, "tig.color."))
7499                 set_repo_config_option(name + 10, value, option_color_command);
7501         else if (!prefixcmp(name, "tig.bind."))
7502                 set_repo_config_option(name + 9, value, option_bind_command);
7504         else if (!prefixcmp(name, "tig."))
7505                 set_repo_config_option(name + 4, value, option_set_command);
7507         else if (*opt_head && !prefixcmp(name, "branch.") &&
7508                  !strncmp(name + 7, opt_head, strlen(opt_head)))
7509                 set_remote_branch(name + 7 + strlen(opt_head), value, valuelen);
7511         return OK;
7514 static int
7515 load_git_config(void)
7517         const char *config_list_argv[] = { "git", "config", "--list", NULL };
7519         return run_io_load(config_list_argv, "=", read_repo_config_option);
7522 static int
7523 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
7525         if (!opt_git_dir[0]) {
7526                 string_ncopy(opt_git_dir, name, namelen);
7528         } else if (opt_is_inside_work_tree == -1) {
7529                 /* This can be 3 different values depending on the
7530                  * version of git being used. If git-rev-parse does not
7531                  * understand --is-inside-work-tree it will simply echo
7532                  * the option else either "true" or "false" is printed.
7533                  * Default to true for the unknown case. */
7534                 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
7536         } else if (*name == '.') {
7537                 string_ncopy(opt_cdup, name, namelen);
7539         } else {
7540                 string_ncopy(opt_prefix, name, namelen);
7541         }
7543         return OK;
7546 static int
7547 load_repo_info(void)
7549         const char *rev_parse_argv[] = {
7550                 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
7551                         "--show-cdup", "--show-prefix", NULL
7552         };
7554         return run_io_load(rev_parse_argv, "=", read_repo_info);
7558 /*
7559  * Main
7560  */
7562 static const char usage[] =
7563 "tig " TIG_VERSION " (" __DATE__ ")\n"
7564 "\n"
7565 "Usage: tig        [options] [revs] [--] [paths]\n"
7566 "   or: tig show   [options] [revs] [--] [paths]\n"
7567 "   or: tig blame  [rev] path\n"
7568 "   or: tig status\n"
7569 "   or: tig <      [git command output]\n"
7570 "\n"
7571 "Options:\n"
7572 "  -v, --version   Show version and exit\n"
7573 "  -h, --help      Show help message and exit";
7575 static void __NORETURN
7576 quit(int sig)
7578         /* XXX: Restore tty modes and let the OS cleanup the rest! */
7579         if (cursed)
7580                 endwin();
7581         exit(0);
7584 static void __NORETURN
7585 die(const char *err, ...)
7587         va_list args;
7589         endwin();
7591         va_start(args, err);
7592         fputs("tig: ", stderr);
7593         vfprintf(stderr, err, args);
7594         fputs("\n", stderr);
7595         va_end(args);
7597         exit(1);
7600 static void
7601 warn(const char *msg, ...)
7603         va_list args;
7605         va_start(args, msg);
7606         fputs("tig warning: ", stderr);
7607         vfprintf(stderr, msg, args);
7608         fputs("\n", stderr);
7609         va_end(args);
7612 static enum request
7613 parse_options(int argc, const char *argv[])
7615         enum request request = REQ_VIEW_MAIN;
7616         const char *subcommand;
7617         bool seen_dashdash = FALSE;
7618         /* XXX: This is vulnerable to the user overriding options
7619          * required for the main view parser. */
7620         const char *custom_argv[SIZEOF_ARG] = {
7621                 "git", "log", "--no-color", "--pretty=raw", "--parents",
7622                         "--topo-order", NULL
7623         };
7624         int i, j = 6;
7626         if (!isatty(STDIN_FILENO)) {
7627                 io_open(&VIEW(REQ_VIEW_PAGER)->io, "");
7628                 return REQ_VIEW_PAGER;
7629         }
7631         if (argc <= 1)
7632                 return REQ_NONE;
7634         subcommand = argv[1];
7635         if (!strcmp(subcommand, "status")) {
7636                 if (argc > 2)
7637                         warn("ignoring arguments after `%s'", subcommand);
7638                 return REQ_VIEW_STATUS;
7640         } else if (!strcmp(subcommand, "blame")) {
7641                 if (argc <= 2 || argc > 4)
7642                         die("invalid number of options to blame\n\n%s", usage);
7644                 i = 2;
7645                 if (argc == 4) {
7646                         string_ncopy(opt_ref, argv[i], strlen(argv[i]));
7647                         i++;
7648                 }
7650                 string_ncopy(opt_file, argv[i], strlen(argv[i]));
7651                 return REQ_VIEW_BLAME;
7653         } else if (!strcmp(subcommand, "show")) {
7654                 request = REQ_VIEW_DIFF;
7656         } else {
7657                 subcommand = NULL;
7658         }
7660         if (subcommand) {
7661                 custom_argv[1] = subcommand;
7662                 j = 2;
7663         }
7665         for (i = 1 + !!subcommand; i < argc; i++) {
7666                 const char *opt = argv[i];
7668                 if (seen_dashdash || !strcmp(opt, "--")) {
7669                         seen_dashdash = TRUE;
7671                 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
7672                         printf("tig version %s\n", TIG_VERSION);
7673                         quit(0);
7675                 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
7676                         printf("%s\n", usage);
7677                         quit(0);
7678                 }
7680                 custom_argv[j++] = opt;
7681                 if (j >= ARRAY_SIZE(custom_argv))
7682                         die("command too long");
7683         }
7685         if (!prepare_update(VIEW(request), custom_argv, NULL, FORMAT_NONE))
7686                 die("Failed to format arguments");
7688         return request;
7691 int
7692 main(int argc, const char *argv[])
7694         enum request request = parse_options(argc, argv);
7695         struct view *view;
7696         size_t i;
7698         signal(SIGINT, quit);
7699         signal(SIGPIPE, SIG_IGN);
7701         if (setlocale(LC_ALL, "")) {
7702                 char *codeset = nl_langinfo(CODESET);
7704                 string_ncopy(opt_codeset, codeset, strlen(codeset));
7705         }
7707         if (load_repo_info() == ERR)
7708                 die("Failed to load repo info.");
7710         if (load_options() == ERR)
7711                 die("Failed to load user config.");
7713         if (load_git_config() == ERR)
7714                 die("Failed to load repo config.");
7716         /* Require a git repository unless when running in pager mode. */
7717         if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
7718                 die("Not a git repository");
7720         if (*opt_encoding && strcmp(opt_codeset, "UTF-8")) {
7721                 opt_iconv_in = iconv_open("UTF-8", opt_encoding);
7722                 if (opt_iconv_in == ICONV_NONE)
7723                         die("Failed to initialize character set conversion");
7724         }
7726         if (*opt_codeset && strcmp(opt_codeset, "UTF-8")) {
7727                 opt_iconv_out = iconv_open(opt_codeset, "UTF-8");
7728                 if (opt_iconv_out == ICONV_NONE)
7729                         die("Failed to initialize character set conversion");
7730         }
7732         if (load_refs() == ERR)
7733                 die("Failed to load refs.");
7735         foreach_view (view, i)
7736                 argv_from_env(view->ops->argv, view->cmd_env);
7738         init_display();
7740         if (request != REQ_NONE)
7741                 open_view(NULL, request, OPEN_PREPARED);
7742         request = request == REQ_NONE ? REQ_VIEW_MAIN : REQ_NONE;
7744         while (view_driver(display[current_view], request)) {
7745                 int key = get_input(0);
7747                 view = display[current_view];
7748                 request = get_keybinding(view->keymap, key);
7750                 /* Some low-level request handling. This keeps access to
7751                  * status_win restricted. */
7752                 switch (request) {
7753                 case REQ_PROMPT:
7754                 {
7755                         char *cmd = read_prompt(":");
7757                         if (cmd && isdigit(*cmd)) {
7758                                 int lineno = view->lineno + 1;
7760                                 if (parse_int(&lineno, cmd, 1, view->lines + 1) == OK) {
7761                                         select_view_line(view, lineno - 1);
7762                                         report("");
7763                                 } else {
7764                                         report("Unable to parse '%s' as a line number", cmd);
7765                                 }
7767                         } else if (cmd) {
7768                                 struct view *next = VIEW(REQ_VIEW_PAGER);
7769                                 const char *argv[SIZEOF_ARG] = { "git" };
7770                                 int argc = 1;
7772                                 /* When running random commands, initially show the
7773                                  * command in the title. However, it maybe later be
7774                                  * overwritten if a commit line is selected. */
7775                                 string_ncopy(next->ref, cmd, strlen(cmd));
7777                                 if (!argv_from_string(argv, &argc, cmd)) {
7778                                         report("Too many arguments");
7779                                 } else if (!prepare_update(next, argv, NULL, FORMAT_DASH)) {
7780                                         report("Failed to format command");
7781                                 } else {
7782                                         open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
7783                                 }
7784                         }
7786                         request = REQ_NONE;
7787                         break;
7788                 }
7789                 case REQ_SEARCH:
7790                 case REQ_SEARCH_BACK:
7791                 {
7792                         const char *prompt = request == REQ_SEARCH ? "/" : "?";
7793                         char *search = read_prompt(prompt);
7795                         if (search)
7796                                 string_ncopy(opt_search, search, strlen(search));
7797                         else if (*opt_search)
7798                                 request = request == REQ_SEARCH ?
7799                                         REQ_FIND_NEXT :
7800                                         REQ_FIND_PREV;
7801                         else
7802                                 request = REQ_NONE;
7803                         break;
7804                 }
7805                 default:
7806                         break;
7807                 }
7808         }
7810         quit(0);
7812         return 0;