Code

Introduce parse_enum and use it to parse the show-date option
[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_date_option(enum date *date)
2489         static const char *help[] = {
2490                 "no",
2491                 "default",
2492                 "relative",
2493                 "short"
2494         };
2496         *date = (*date + 1) % ARRAY_SIZE(help);
2497         redraw_display(FALSE);
2498         report("Displaying %s dates", help[*date]);
2501 static void
2502 toggle_view_option(bool *option, const char *help)
2504         *option = !*option;
2505         redraw_display(FALSE);
2506         report("%sabling %s", *option ? "En" : "Dis", help);
2509 static void
2510 open_option_menu(void)
2512         const struct menu_item menu[] = {
2513                 { '.', "line numbers", &opt_line_number },
2514                 { 'D', "date display", &opt_date },
2515                 { 'A', "author display", &opt_author },
2516                 { 'g', "revision graph display", &opt_rev_graph },
2517                 { 'F', "reference display", &opt_show_refs },
2518                 { 0 }
2519         };
2520         int selected = 0;
2522         if (prompt_menu("Toggle option", menu, &selected)) {
2523                 if (menu[selected].data == &opt_date)
2524                         toggle_date_option(menu[selected].data);
2525                 else
2526                         toggle_view_option(menu[selected].data, menu[selected].text);
2527         }
2530 static void
2531 maximize_view(struct view *view)
2533         memset(display, 0, sizeof(display));
2534         current_view = 0;
2535         display[current_view] = view;
2536         resize_display();
2537         redraw_display(FALSE);
2538         report("");
2542 /*
2543  * Navigation
2544  */
2546 static bool
2547 goto_view_line(struct view *view, unsigned long offset, unsigned long lineno)
2549         if (lineno >= view->lines)
2550                 lineno = view->lines > 0 ? view->lines - 1 : 0;
2552         if (offset > lineno || offset + view->height <= lineno) {
2553                 unsigned long half = view->height / 2;
2555                 if (lineno > half)
2556                         offset = lineno - half;
2557                 else
2558                         offset = 0;
2559         }
2561         if (offset != view->offset || lineno != view->lineno) {
2562                 view->offset = offset;
2563                 view->lineno = lineno;
2564                 return TRUE;
2565         }
2567         return FALSE;
2570 /* Scrolling backend */
2571 static void
2572 do_scroll_view(struct view *view, int lines)
2574         bool redraw_current_line = FALSE;
2576         /* The rendering expects the new offset. */
2577         view->offset += lines;
2579         assert(0 <= view->offset && view->offset < view->lines);
2580         assert(lines);
2582         /* Move current line into the view. */
2583         if (view->lineno < view->offset) {
2584                 view->lineno = view->offset;
2585                 redraw_current_line = TRUE;
2586         } else if (view->lineno >= view->offset + view->height) {
2587                 view->lineno = view->offset + view->height - 1;
2588                 redraw_current_line = TRUE;
2589         }
2591         assert(view->offset <= view->lineno && view->lineno < view->lines);
2593         /* Redraw the whole screen if scrolling is pointless. */
2594         if (view->height < ABS(lines)) {
2595                 redraw_view(view);
2597         } else {
2598                 int line = lines > 0 ? view->height - lines : 0;
2599                 int end = line + ABS(lines);
2601                 scrollok(view->win, TRUE);
2602                 wscrl(view->win, lines);
2603                 scrollok(view->win, FALSE);
2605                 while (line < end && draw_view_line(view, line))
2606                         line++;
2608                 if (redraw_current_line)
2609                         draw_view_line(view, view->lineno - view->offset);
2610                 wnoutrefresh(view->win);
2611         }
2613         view->has_scrolled = TRUE;
2614         report("");
2617 /* Scroll frontend */
2618 static void
2619 scroll_view(struct view *view, enum request request)
2621         int lines = 1;
2623         assert(view_is_displayed(view));
2625         switch (request) {
2626         case REQ_SCROLL_LEFT:
2627                 if (view->yoffset == 0) {
2628                         report("Cannot scroll beyond the first column");
2629                         return;
2630                 }
2631                 if (view->yoffset <= apply_step(opt_hscroll, view->width))
2632                         view->yoffset = 0;
2633                 else
2634                         view->yoffset -= apply_step(opt_hscroll, view->width);
2635                 redraw_view_from(view, 0);
2636                 report("");
2637                 return;
2638         case REQ_SCROLL_RIGHT:
2639                 view->yoffset += apply_step(opt_hscroll, view->width);
2640                 redraw_view(view);
2641                 report("");
2642                 return;
2643         case REQ_SCROLL_PAGE_DOWN:
2644                 lines = view->height;
2645         case REQ_SCROLL_LINE_DOWN:
2646                 if (view->offset + lines > view->lines)
2647                         lines = view->lines - view->offset;
2649                 if (lines == 0 || view->offset + view->height >= view->lines) {
2650                         report("Cannot scroll beyond the last line");
2651                         return;
2652                 }
2653                 break;
2655         case REQ_SCROLL_PAGE_UP:
2656                 lines = view->height;
2657         case REQ_SCROLL_LINE_UP:
2658                 if (lines > view->offset)
2659                         lines = view->offset;
2661                 if (lines == 0) {
2662                         report("Cannot scroll beyond the first line");
2663                         return;
2664                 }
2666                 lines = -lines;
2667                 break;
2669         default:
2670                 die("request %d not handled in switch", request);
2671         }
2673         do_scroll_view(view, lines);
2676 /* Cursor moving */
2677 static void
2678 move_view(struct view *view, enum request request)
2680         int scroll_steps = 0;
2681         int steps;
2683         switch (request) {
2684         case REQ_MOVE_FIRST_LINE:
2685                 steps = -view->lineno;
2686                 break;
2688         case REQ_MOVE_LAST_LINE:
2689                 steps = view->lines - view->lineno - 1;
2690                 break;
2692         case REQ_MOVE_PAGE_UP:
2693                 steps = view->height > view->lineno
2694                       ? -view->lineno : -view->height;
2695                 break;
2697         case REQ_MOVE_PAGE_DOWN:
2698                 steps = view->lineno + view->height >= view->lines
2699                       ? view->lines - view->lineno - 1 : view->height;
2700                 break;
2702         case REQ_MOVE_UP:
2703                 steps = -1;
2704                 break;
2706         case REQ_MOVE_DOWN:
2707                 steps = 1;
2708                 break;
2710         default:
2711                 die("request %d not handled in switch", request);
2712         }
2714         if (steps <= 0 && view->lineno == 0) {
2715                 report("Cannot move beyond the first line");
2716                 return;
2718         } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2719                 report("Cannot move beyond the last line");
2720                 return;
2721         }
2723         /* Move the current line */
2724         view->lineno += steps;
2725         assert(0 <= view->lineno && view->lineno < view->lines);
2727         /* Check whether the view needs to be scrolled */
2728         if (view->lineno < view->offset ||
2729             view->lineno >= view->offset + view->height) {
2730                 scroll_steps = steps;
2731                 if (steps < 0 && -steps > view->offset) {
2732                         scroll_steps = -view->offset;
2734                 } else if (steps > 0) {
2735                         if (view->lineno == view->lines - 1 &&
2736                             view->lines > view->height) {
2737                                 scroll_steps = view->lines - view->offset - 1;
2738                                 if (scroll_steps >= view->height)
2739                                         scroll_steps -= view->height - 1;
2740                         }
2741                 }
2742         }
2744         if (!view_is_displayed(view)) {
2745                 view->offset += scroll_steps;
2746                 assert(0 <= view->offset && view->offset < view->lines);
2747                 view->ops->select(view, &view->line[view->lineno]);
2748                 return;
2749         }
2751         /* Repaint the old "current" line if we be scrolling */
2752         if (ABS(steps) < view->height)
2753                 draw_view_line(view, view->lineno - steps - view->offset);
2755         if (scroll_steps) {
2756                 do_scroll_view(view, scroll_steps);
2757                 return;
2758         }
2760         /* Draw the current line */
2761         draw_view_line(view, view->lineno - view->offset);
2763         wnoutrefresh(view->win);
2764         report("");
2768 /*
2769  * Searching
2770  */
2772 static void search_view(struct view *view, enum request request);
2774 static bool
2775 grep_text(struct view *view, const char *text[])
2777         regmatch_t pmatch;
2778         size_t i;
2780         for (i = 0; text[i]; i++)
2781                 if (*text[i] &&
2782                     regexec(view->regex, text[i], 1, &pmatch, 0) != REG_NOMATCH)
2783                         return TRUE;
2784         return FALSE;
2787 static void
2788 select_view_line(struct view *view, unsigned long lineno)
2790         unsigned long old_lineno = view->lineno;
2791         unsigned long old_offset = view->offset;
2793         if (goto_view_line(view, view->offset, lineno)) {
2794                 if (view_is_displayed(view)) {
2795                         if (old_offset != view->offset) {
2796                                 redraw_view(view);
2797                         } else {
2798                                 draw_view_line(view, old_lineno - view->offset);
2799                                 draw_view_line(view, view->lineno - view->offset);
2800                                 wnoutrefresh(view->win);
2801                         }
2802                 } else {
2803                         view->ops->select(view, &view->line[view->lineno]);
2804                 }
2805         }
2808 static void
2809 find_next(struct view *view, enum request request)
2811         unsigned long lineno = view->lineno;
2812         int direction;
2814         if (!*view->grep) {
2815                 if (!*opt_search)
2816                         report("No previous search");
2817                 else
2818                         search_view(view, request);
2819                 return;
2820         }
2822         switch (request) {
2823         case REQ_SEARCH:
2824         case REQ_FIND_NEXT:
2825                 direction = 1;
2826                 break;
2828         case REQ_SEARCH_BACK:
2829         case REQ_FIND_PREV:
2830                 direction = -1;
2831                 break;
2833         default:
2834                 return;
2835         }
2837         if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
2838                 lineno += direction;
2840         /* Note, lineno is unsigned long so will wrap around in which case it
2841          * will become bigger than view->lines. */
2842         for (; lineno < view->lines; lineno += direction) {
2843                 if (view->ops->grep(view, &view->line[lineno])) {
2844                         select_view_line(view, lineno);
2845                         report("Line %ld matches '%s'", lineno + 1, view->grep);
2846                         return;
2847                 }
2848         }
2850         report("No match found for '%s'", view->grep);
2853 static void
2854 search_view(struct view *view, enum request request)
2856         int regex_err;
2858         if (view->regex) {
2859                 regfree(view->regex);
2860                 *view->grep = 0;
2861         } else {
2862                 view->regex = calloc(1, sizeof(*view->regex));
2863                 if (!view->regex)
2864                         return;
2865         }
2867         regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
2868         if (regex_err != 0) {
2869                 char buf[SIZEOF_STR] = "unknown error";
2871                 regerror(regex_err, view->regex, buf, sizeof(buf));
2872                 report("Search failed: %s", buf);
2873                 return;
2874         }
2876         string_copy(view->grep, opt_search);
2878         find_next(view, request);
2881 /*
2882  * Incremental updating
2883  */
2885 static void
2886 reset_view(struct view *view)
2888         int i;
2890         for (i = 0; i < view->lines; i++)
2891                 free(view->line[i].data);
2892         free(view->line);
2894         view->p_offset = view->offset;
2895         view->p_yoffset = view->yoffset;
2896         view->p_lineno = view->lineno;
2898         view->line = NULL;
2899         view->offset = 0;
2900         view->yoffset = 0;
2901         view->lines  = 0;
2902         view->lineno = 0;
2903         view->vid[0] = 0;
2904         view->update_secs = 0;
2907 static void
2908 free_argv(const char *argv[])
2910         int argc;
2912         for (argc = 0; argv[argc]; argc++)
2913                 free((void *) argv[argc]);
2916 static bool
2917 format_argv(const char *dst_argv[], const char *src_argv[], enum format_flags flags)
2919         char buf[SIZEOF_STR];
2920         int argc;
2921         bool noreplace = flags == FORMAT_NONE;
2923         free_argv(dst_argv);
2925         for (argc = 0; src_argv[argc]; argc++) {
2926                 const char *arg = src_argv[argc];
2927                 size_t bufpos = 0;
2929                 while (arg) {
2930                         char *next = strstr(arg, "%(");
2931                         int len = next - arg;
2932                         const char *value;
2934                         if (!next || noreplace) {
2935                                 if (flags == FORMAT_DASH && !strcmp(arg, "--"))
2936                                         noreplace = TRUE;
2937                                 len = strlen(arg);
2938                                 value = "";
2940                         } else if (!prefixcmp(next, "%(directory)")) {
2941                                 value = opt_path;
2943                         } else if (!prefixcmp(next, "%(file)")) {
2944                                 value = opt_file;
2946                         } else if (!prefixcmp(next, "%(ref)")) {
2947                                 value = *opt_ref ? opt_ref : "HEAD";
2949                         } else if (!prefixcmp(next, "%(head)")) {
2950                                 value = ref_head;
2952                         } else if (!prefixcmp(next, "%(commit)")) {
2953                                 value = ref_commit;
2955                         } else if (!prefixcmp(next, "%(blob)")) {
2956                                 value = ref_blob;
2958                         } else {
2959                                 report("Unknown replacement: `%s`", next);
2960                                 return FALSE;
2961                         }
2963                         if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
2964                                 return FALSE;
2966                         arg = next && !noreplace ? strchr(next, ')') + 1 : NULL;
2967                 }
2969                 dst_argv[argc] = strdup(buf);
2970                 if (!dst_argv[argc])
2971                         break;
2972         }
2974         dst_argv[argc] = NULL;
2976         return src_argv[argc] == NULL;
2979 static bool
2980 restore_view_position(struct view *view)
2982         if (!view->p_restore || (view->pipe && view->lines <= view->p_lineno))
2983                 return FALSE;
2985         /* Changing the view position cancels the restoring. */
2986         /* FIXME: Changing back to the first line is not detected. */
2987         if (view->offset != 0 || view->lineno != 0) {
2988                 view->p_restore = FALSE;
2989                 return FALSE;
2990         }
2992         if (goto_view_line(view, view->p_offset, view->p_lineno) &&
2993             view_is_displayed(view))
2994                 werase(view->win);
2996         view->yoffset = view->p_yoffset;
2997         view->p_restore = FALSE;
2999         return TRUE;
3002 static void
3003 end_update(struct view *view, bool force)
3005         if (!view->pipe)
3006                 return;
3007         while (!view->ops->read(view, NULL))
3008                 if (!force)
3009                         return;
3010         set_nonblocking_input(FALSE);
3011         if (force)
3012                 kill_io(view->pipe);
3013         done_io(view->pipe);
3014         view->pipe = NULL;
3017 static void
3018 setup_update(struct view *view, const char *vid)
3020         set_nonblocking_input(TRUE);
3021         reset_view(view);
3022         string_copy_rev(view->vid, vid);
3023         view->pipe = &view->io;
3024         view->start_time = time(NULL);
3027 static bool
3028 prepare_update(struct view *view, const char *argv[], const char *dir,
3029                enum format_flags flags)
3031         if (view->pipe)
3032                 end_update(view, TRUE);
3033         return init_io_rd(&view->io, argv, dir, flags);
3036 static bool
3037 prepare_update_file(struct view *view, const char *name)
3039         if (view->pipe)
3040                 end_update(view, TRUE);
3041         return io_open(&view->io, "%s", name);
3044 static bool
3045 begin_update(struct view *view, bool refresh)
3047         if (view->pipe)
3048                 end_update(view, TRUE);
3050         if (!refresh) {
3051                 if (view->ops->prepare) {
3052                         if (!view->ops->prepare(view))
3053                                 return FALSE;
3054                 } else if (!init_io_rd(&view->io, view->ops->argv, NULL, FORMAT_ALL)) {
3055                         return FALSE;
3056                 }
3058                 /* Put the current ref_* value to the view title ref
3059                  * member. This is needed by the blob view. Most other
3060                  * views sets it automatically after loading because the
3061                  * first line is a commit line. */
3062                 string_copy_rev(view->ref, view->id);
3063         }
3065         if (!start_io(&view->io))
3066                 return FALSE;
3068         setup_update(view, view->id);
3070         return TRUE;
3073 static bool
3074 update_view(struct view *view)
3076         char out_buffer[BUFSIZ * 2];
3077         char *line;
3078         /* Clear the view and redraw everything since the tree sorting
3079          * might have rearranged things. */
3080         bool redraw = view->lines == 0;
3081         bool can_read = TRUE;
3083         if (!view->pipe)
3084                 return TRUE;
3086         if (!io_can_read(view->pipe)) {
3087                 if (view->lines == 0 && view_is_displayed(view)) {
3088                         time_t secs = time(NULL) - view->start_time;
3090                         if (secs > 1 && secs > view->update_secs) {
3091                                 if (view->update_secs == 0)
3092                                         redraw_view(view);
3093                                 update_view_title(view);
3094                                 view->update_secs = secs;
3095                         }
3096                 }
3097                 return TRUE;
3098         }
3100         for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
3101                 if (opt_iconv_in != ICONV_NONE) {
3102                         ICONV_CONST char *inbuf = line;
3103                         size_t inlen = strlen(line) + 1;
3105                         char *outbuf = out_buffer;
3106                         size_t outlen = sizeof(out_buffer);
3108                         size_t ret;
3110                         ret = iconv(opt_iconv_in, &inbuf, &inlen, &outbuf, &outlen);
3111                         if (ret != (size_t) -1)
3112                                 line = out_buffer;
3113                 }
3115                 if (!view->ops->read(view, line)) {
3116                         report("Allocation failure");
3117                         end_update(view, TRUE);
3118                         return FALSE;
3119                 }
3120         }
3122         {
3123                 unsigned long lines = view->lines;
3124                 int digits;
3126                 for (digits = 0; lines; digits++)
3127                         lines /= 10;
3129                 /* Keep the displayed view in sync with line number scaling. */
3130                 if (digits != view->digits) {
3131                         view->digits = digits;
3132                         if (opt_line_number || view == VIEW(REQ_VIEW_BLAME))
3133                                 redraw = TRUE;
3134                 }
3135         }
3137         if (io_error(view->pipe)) {
3138                 report("Failed to read: %s", io_strerror(view->pipe));
3139                 end_update(view, TRUE);
3141         } else if (io_eof(view->pipe)) {
3142                 report("");
3143                 end_update(view, FALSE);
3144         }
3146         if (restore_view_position(view))
3147                 redraw = TRUE;
3149         if (!view_is_displayed(view))
3150                 return TRUE;
3152         if (redraw)
3153                 redraw_view_from(view, 0);
3154         else
3155                 redraw_view_dirty(view);
3157         /* Update the title _after_ the redraw so that if the redraw picks up a
3158          * commit reference in view->ref it'll be available here. */
3159         update_view_title(view);
3160         return TRUE;
3163 DEFINE_ALLOCATOR(realloc_lines, struct line, 256)
3165 static struct line *
3166 add_line_data(struct view *view, void *data, enum line_type type)
3168         struct line *line;
3170         if (!realloc_lines(&view->line, view->lines, 1))
3171                 return NULL;
3173         line = &view->line[view->lines++];
3174         memset(line, 0, sizeof(*line));
3175         line->type = type;
3176         line->data = data;
3177         line->dirty = 1;
3179         return line;
3182 static struct line *
3183 add_line_text(struct view *view, const char *text, enum line_type type)
3185         char *data = text ? strdup(text) : NULL;
3187         return data ? add_line_data(view, data, type) : NULL;
3190 static struct line *
3191 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
3193         char buf[SIZEOF_STR];
3194         va_list args;
3196         va_start(args, fmt);
3197         if (vsnprintf(buf, sizeof(buf), fmt, args) >= sizeof(buf))
3198                 buf[0] = 0;
3199         va_end(args);
3201         return buf[0] ? add_line_text(view, buf, type) : NULL;
3204 /*
3205  * View opening
3206  */
3208 enum open_flags {
3209         OPEN_DEFAULT = 0,       /* Use default view switching. */
3210         OPEN_SPLIT = 1,         /* Split current view. */
3211         OPEN_RELOAD = 4,        /* Reload view even if it is the current. */
3212         OPEN_REFRESH = 16,      /* Refresh view using previous command. */
3213         OPEN_PREPARED = 32,     /* Open already prepared command. */
3214 };
3216 static void
3217 open_view(struct view *prev, enum request request, enum open_flags flags)
3219         bool split = !!(flags & OPEN_SPLIT);
3220         bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
3221         bool nomaximize = !!(flags & OPEN_REFRESH);
3222         struct view *view = VIEW(request);
3223         int nviews = displayed_views();
3224         struct view *base_view = display[0];
3226         if (view == prev && nviews == 1 && !reload) {
3227                 report("Already in %s view", view->name);
3228                 return;
3229         }
3231         if (view->git_dir && !opt_git_dir[0]) {
3232                 report("The %s view is disabled in pager view", view->name);
3233                 return;
3234         }
3236         if (split) {
3237                 display[1] = view;
3238                 current_view = 1;
3239         } else if (!nomaximize) {
3240                 /* Maximize the current view. */
3241                 memset(display, 0, sizeof(display));
3242                 current_view = 0;
3243                 display[current_view] = view;
3244         }
3246         /* No parent signals that this is the first loaded view. */
3247         if (prev && view != prev) {
3248                 view->parent = prev;
3249         }
3251         /* Resize the view when switching between split- and full-screen,
3252          * or when switching between two different full-screen views. */
3253         if (nviews != displayed_views() ||
3254             (nviews == 1 && base_view != display[0]))
3255                 resize_display();
3257         if (view->ops->open) {
3258                 if (view->pipe)
3259                         end_update(view, TRUE);
3260                 if (!view->ops->open(view)) {
3261                         report("Failed to load %s view", view->name);
3262                         return;
3263                 }
3264                 restore_view_position(view);
3266         } else if ((reload || strcmp(view->vid, view->id)) &&
3267                    !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
3268                 report("Failed to load %s view", view->name);
3269                 return;
3270         }
3272         if (split && prev->lineno - prev->offset >= prev->height) {
3273                 /* Take the title line into account. */
3274                 int lines = prev->lineno - prev->offset - prev->height + 1;
3276                 /* Scroll the view that was split if the current line is
3277                  * outside the new limited view. */
3278                 do_scroll_view(prev, lines);
3279         }
3281         if (prev && view != prev && split && view_is_displayed(prev)) {
3282                 /* "Blur" the previous view. */
3283                 update_view_title(prev);
3284         }
3286         if (view->pipe && view->lines == 0) {
3287                 /* Clear the old view and let the incremental updating refill
3288                  * the screen. */
3289                 werase(view->win);
3290                 view->p_restore = flags & (OPEN_RELOAD | OPEN_REFRESH);
3291                 report("");
3292         } else if (view_is_displayed(view)) {
3293                 redraw_view(view);
3294                 report("");
3295         }
3298 static void
3299 open_external_viewer(const char *argv[], const char *dir)
3301         def_prog_mode();           /* save current tty modes */
3302         endwin();                  /* restore original tty modes */
3303         run_io_fg(argv, dir);
3304         fprintf(stderr, "Press Enter to continue");
3305         getc(opt_tty);
3306         reset_prog_mode();
3307         redraw_display(TRUE);
3310 static void
3311 open_mergetool(const char *file)
3313         const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
3315         open_external_viewer(mergetool_argv, opt_cdup);
3318 static void
3319 open_editor(bool from_root, const char *file)
3321         const char *editor_argv[] = { "vi", file, NULL };
3322         const char *editor;
3324         editor = getenv("GIT_EDITOR");
3325         if (!editor && *opt_editor)
3326                 editor = opt_editor;
3327         if (!editor)
3328                 editor = getenv("VISUAL");
3329         if (!editor)
3330                 editor = getenv("EDITOR");
3331         if (!editor)
3332                 editor = "vi";
3334         editor_argv[0] = editor;
3335         open_external_viewer(editor_argv, from_root ? opt_cdup : NULL);
3338 static void
3339 open_run_request(enum request request)
3341         struct run_request *req = get_run_request(request);
3342         const char *argv[ARRAY_SIZE(req->argv)] = { NULL };
3344         if (!req) {
3345                 report("Unknown run request");
3346                 return;
3347         }
3349         if (format_argv(argv, req->argv, FORMAT_ALL))
3350                 open_external_viewer(argv, NULL);
3351         free_argv(argv);
3354 /*
3355  * User request switch noodle
3356  */
3358 static int
3359 view_driver(struct view *view, enum request request)
3361         int i;
3363         if (request == REQ_NONE)
3364                 return TRUE;
3366         if (request > REQ_NONE) {
3367                 open_run_request(request);
3368                 /* FIXME: When all views can refresh always do this. */
3369                 if (view == VIEW(REQ_VIEW_STATUS) ||
3370                     view == VIEW(REQ_VIEW_MAIN) ||
3371                     view == VIEW(REQ_VIEW_LOG) ||
3372                     view == VIEW(REQ_VIEW_BRANCH) ||
3373                     view == VIEW(REQ_VIEW_STAGE))
3374                         request = REQ_REFRESH;
3375                 else
3376                         return TRUE;
3377         }
3379         if (view && view->lines) {
3380                 request = view->ops->request(view, request, &view->line[view->lineno]);
3381                 if (request == REQ_NONE)
3382                         return TRUE;
3383         }
3385         switch (request) {
3386         case REQ_MOVE_UP:
3387         case REQ_MOVE_DOWN:
3388         case REQ_MOVE_PAGE_UP:
3389         case REQ_MOVE_PAGE_DOWN:
3390         case REQ_MOVE_FIRST_LINE:
3391         case REQ_MOVE_LAST_LINE:
3392                 move_view(view, request);
3393                 break;
3395         case REQ_SCROLL_LEFT:
3396         case REQ_SCROLL_RIGHT:
3397         case REQ_SCROLL_LINE_DOWN:
3398         case REQ_SCROLL_LINE_UP:
3399         case REQ_SCROLL_PAGE_DOWN:
3400         case REQ_SCROLL_PAGE_UP:
3401                 scroll_view(view, request);
3402                 break;
3404         case REQ_VIEW_BLAME:
3405                 if (!opt_file[0]) {
3406                         report("No file chosen, press %s to open tree view",
3407                                get_key(view->keymap, REQ_VIEW_TREE));
3408                         break;
3409                 }
3410                 open_view(view, request, OPEN_DEFAULT);
3411                 break;
3413         case REQ_VIEW_BLOB:
3414                 if (!ref_blob[0]) {
3415                         report("No file chosen, press %s to open tree view",
3416                                get_key(view->keymap, REQ_VIEW_TREE));
3417                         break;
3418                 }
3419                 open_view(view, request, OPEN_DEFAULT);
3420                 break;
3422         case REQ_VIEW_PAGER:
3423                 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3424                         report("No pager content, press %s to run command from prompt",
3425                                get_key(view->keymap, REQ_PROMPT));
3426                         break;
3427                 }
3428                 open_view(view, request, OPEN_DEFAULT);
3429                 break;
3431         case REQ_VIEW_STAGE:
3432                 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3433                         report("No stage content, press %s to open the status view and choose file",
3434                                get_key(view->keymap, REQ_VIEW_STATUS));
3435                         break;
3436                 }
3437                 open_view(view, request, OPEN_DEFAULT);
3438                 break;
3440         case REQ_VIEW_STATUS:
3441                 if (opt_is_inside_work_tree == FALSE) {
3442                         report("The status view requires a working tree");
3443                         break;
3444                 }
3445                 open_view(view, request, OPEN_DEFAULT);
3446                 break;
3448         case REQ_VIEW_MAIN:
3449         case REQ_VIEW_DIFF:
3450         case REQ_VIEW_LOG:
3451         case REQ_VIEW_TREE:
3452         case REQ_VIEW_HELP:
3453         case REQ_VIEW_BRANCH:
3454                 open_view(view, request, OPEN_DEFAULT);
3455                 break;
3457         case REQ_NEXT:
3458         case REQ_PREVIOUS:
3459                 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3461                 if ((view == VIEW(REQ_VIEW_DIFF) &&
3462                      view->parent == VIEW(REQ_VIEW_MAIN)) ||
3463                    (view == VIEW(REQ_VIEW_DIFF) &&
3464                      view->parent == VIEW(REQ_VIEW_BLAME)) ||
3465                    (view == VIEW(REQ_VIEW_STAGE) &&
3466                      view->parent == VIEW(REQ_VIEW_STATUS)) ||
3467                    (view == VIEW(REQ_VIEW_BLOB) &&
3468                      view->parent == VIEW(REQ_VIEW_TREE)) ||
3469                    (view == VIEW(REQ_VIEW_MAIN) &&
3470                      view->parent == VIEW(REQ_VIEW_BRANCH))) {
3471                         int line;
3473                         view = view->parent;
3474                         line = view->lineno;
3475                         move_view(view, request);
3476                         if (view_is_displayed(view))
3477                                 update_view_title(view);
3478                         if (line != view->lineno)
3479                                 view->ops->request(view, REQ_ENTER,
3480                                                    &view->line[view->lineno]);
3482                 } else {
3483                         move_view(view, request);
3484                 }
3485                 break;
3487         case REQ_VIEW_NEXT:
3488         {
3489                 int nviews = displayed_views();
3490                 int next_view = (current_view + 1) % nviews;
3492                 if (next_view == current_view) {
3493                         report("Only one view is displayed");
3494                         break;
3495                 }
3497                 current_view = next_view;
3498                 /* Blur out the title of the previous view. */
3499                 update_view_title(view);
3500                 report("");
3501                 break;
3502         }
3503         case REQ_REFRESH:
3504                 report("Refreshing is not yet supported for the %s view", view->name);
3505                 break;
3507         case REQ_MAXIMIZE:
3508                 if (displayed_views() == 2)
3509                         maximize_view(view);
3510                 break;
3512         case REQ_OPTIONS:
3513                 open_option_menu();
3514                 break;
3516         case REQ_TOGGLE_LINENO:
3517                 toggle_view_option(&opt_line_number, "line numbers");
3518                 break;
3520         case REQ_TOGGLE_DATE:
3521                 toggle_date_option(&opt_date);
3522                 break;
3524         case REQ_TOGGLE_AUTHOR:
3525                 toggle_view_option(&opt_author, "author display");
3526                 break;
3528         case REQ_TOGGLE_REV_GRAPH:
3529                 toggle_view_option(&opt_rev_graph, "revision graph display");
3530                 break;
3532         case REQ_TOGGLE_REFS:
3533                 toggle_view_option(&opt_show_refs, "reference display");
3534                 break;
3536         case REQ_TOGGLE_SORT_FIELD:
3537         case REQ_TOGGLE_SORT_ORDER:
3538                 report("Sorting is not yet supported for the %s view", view->name);
3539                 break;
3541         case REQ_SEARCH:
3542         case REQ_SEARCH_BACK:
3543                 search_view(view, request);
3544                 break;
3546         case REQ_FIND_NEXT:
3547         case REQ_FIND_PREV:
3548                 find_next(view, request);
3549                 break;
3551         case REQ_STOP_LOADING:
3552                 for (i = 0; i < ARRAY_SIZE(views); i++) {
3553                         view = &views[i];
3554                         if (view->pipe)
3555                                 report("Stopped loading the %s view", view->name),
3556                         end_update(view, TRUE);
3557                 }
3558                 break;
3560         case REQ_SHOW_VERSION:
3561                 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3562                 return TRUE;
3564         case REQ_SCREEN_REDRAW:
3565                 redraw_display(TRUE);
3566                 break;
3568         case REQ_EDIT:
3569                 report("Nothing to edit");
3570                 break;
3572         case REQ_ENTER:
3573                 report("Nothing to enter");
3574                 break;
3576         case REQ_VIEW_CLOSE:
3577                 /* XXX: Mark closed views by letting view->parent point to the
3578                  * view itself. Parents to closed view should never be
3579                  * followed. */
3580                 if (view->parent &&
3581                     view->parent->parent != view->parent) {
3582                         maximize_view(view->parent);
3583                         view->parent = view;
3584                         break;
3585                 }
3586                 /* Fall-through */
3587         case REQ_QUIT:
3588                 return FALSE;
3590         default:
3591                 report("Unknown key, press %s for help",
3592                        get_key(view->keymap, REQ_VIEW_HELP));
3593                 return TRUE;
3594         }
3596         return TRUE;
3600 /*
3601  * View backend utilities
3602  */
3604 enum sort_field {
3605         ORDERBY_NAME,
3606         ORDERBY_DATE,
3607         ORDERBY_AUTHOR,
3608 };
3610 struct sort_state {
3611         const enum sort_field *fields;
3612         size_t size, current;
3613         bool reverse;
3614 };
3616 #define SORT_STATE(fields) { fields, ARRAY_SIZE(fields), 0 }
3617 #define get_sort_field(state) ((state).fields[(state).current])
3618 #define sort_order(state, result) ((state).reverse ? -(result) : (result))
3620 static void
3621 sort_view(struct view *view, enum request request, struct sort_state *state,
3622           int (*compare)(const void *, const void *))
3624         switch (request) {
3625         case REQ_TOGGLE_SORT_FIELD:
3626                 state->current = (state->current + 1) % state->size;
3627                 break;
3629         case REQ_TOGGLE_SORT_ORDER:
3630                 state->reverse = !state->reverse;
3631                 break;
3632         default:
3633                 die("Not a sort request");
3634         }
3636         qsort(view->line, view->lines, sizeof(*view->line), compare);
3637         redraw_view(view);
3640 DEFINE_ALLOCATOR(realloc_authors, const char *, 256)
3642 /* Small author cache to reduce memory consumption. It uses binary
3643  * search to lookup or find place to position new entries. No entries
3644  * are ever freed. */
3645 static const char *
3646 get_author(const char *name)
3648         static const char **authors;
3649         static size_t authors_size;
3650         int from = 0, to = authors_size - 1;
3652         while (from <= to) {
3653                 size_t pos = (to + from) / 2;
3654                 int cmp = strcmp(name, authors[pos]);
3656                 if (!cmp)
3657                         return authors[pos];
3659                 if (cmp < 0)
3660                         to = pos - 1;
3661                 else
3662                         from = pos + 1;
3663         }
3665         if (!realloc_authors(&authors, authors_size, 1))
3666                 return NULL;
3667         name = strdup(name);
3668         if (!name)
3669                 return NULL;
3671         memmove(authors + from + 1, authors + from, (authors_size - from) * sizeof(*authors));
3672         authors[from] = name;
3673         authors_size++;
3675         return name;
3678 static void
3679 parse_timezone(time_t *time, const char *zone)
3681         long tz;
3683         tz  = ('0' - zone[1]) * 60 * 60 * 10;
3684         tz += ('0' - zone[2]) * 60 * 60;
3685         tz += ('0' - zone[3]) * 60;
3686         tz += ('0' - zone[4]);
3688         if (zone[0] == '-')
3689                 tz = -tz;
3691         *time -= tz;
3694 /* Parse author lines where the name may be empty:
3695  *      author  <email@address.tld> 1138474660 +0100
3696  */
3697 static void
3698 parse_author_line(char *ident, const char **author, time_t *time)
3700         char *nameend = strchr(ident, '<');
3701         char *emailend = strchr(ident, '>');
3703         if (nameend && emailend)
3704                 *nameend = *emailend = 0;
3705         ident = chomp_string(ident);
3706         if (!*ident) {
3707                 if (nameend)
3708                         ident = chomp_string(nameend + 1);
3709                 if (!*ident)
3710                         ident = "Unknown";
3711         }
3713         *author = get_author(ident);
3715         /* Parse epoch and timezone */
3716         if (emailend && emailend[1] == ' ') {
3717                 char *secs = emailend + 2;
3718                 char *zone = strchr(secs, ' ');
3720                 *time = (time_t) atol(secs);
3722                 if (zone && strlen(zone) == STRING_SIZE(" +0700"))
3723                         parse_timezone(time, zone + 1);
3724         }
3727 static bool
3728 open_commit_parent_menu(char buf[SIZEOF_STR], int *parents)
3730         char rev[SIZEOF_REV];
3731         const char *revlist_argv[] = {
3732                 "git", "log", "--no-color", "-1", "--pretty=format:%s", rev, NULL
3733         };
3734         struct menu_item *items;
3735         char text[SIZEOF_STR];
3736         bool ok = TRUE;
3737         int i;
3739         items = calloc(*parents + 1, sizeof(*items));
3740         if (!items)
3741                 return FALSE;
3743         for (i = 0; i < *parents; i++) {
3744                 string_copy_rev(rev, &buf[SIZEOF_REV * i]);
3745                 if (!run_io_buf(revlist_argv, text, sizeof(text)) ||
3746                     !(items[i].text = strdup(text))) {
3747                         ok = FALSE;
3748                         break;
3749                 }
3750         }
3752         if (ok) {
3753                 *parents = 0;
3754                 ok = prompt_menu("Select parent", items, parents);
3755         }
3756         for (i = 0; items[i].text; i++)
3757                 free((char *) items[i].text);
3758         free(items);
3759         return ok;
3762 static bool
3763 select_commit_parent(const char *id, char rev[SIZEOF_REV], const char *path)
3765         char buf[SIZEOF_STR * 4];
3766         const char *revlist_argv[] = {
3767                 "git", "log", "--no-color", "-1",
3768                         "--pretty=format:%P", id, "--", path, NULL
3769         };
3770         int parents;
3772         if (!run_io_buf(revlist_argv, buf, sizeof(buf)) ||
3773             (parents = strlen(buf) / 40) < 0) {
3774                 report("Failed to get parent information");
3775                 return FALSE;
3777         } else if (parents == 0) {
3778                 if (path)
3779                         report("Path '%s' does not exist in the parent", path);
3780                 else
3781                         report("The selected commit has no parents");
3782                 return FALSE;
3783         }
3785         if (parents > 1 && !open_commit_parent_menu(buf, &parents))
3786                 return FALSE;
3788         string_copy_rev(rev, &buf[41 * parents]);
3789         return TRUE;
3792 /*
3793  * Pager backend
3794  */
3796 static bool
3797 pager_draw(struct view *view, struct line *line, unsigned int lineno)
3799         char text[SIZEOF_STR];
3801         if (opt_line_number && draw_lineno(view, lineno))
3802                 return TRUE;
3804         string_expand(text, sizeof(text), line->data, opt_tab_size);
3805         draw_text(view, line->type, text, TRUE);
3806         return TRUE;
3809 static bool
3810 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
3812         const char *describe_argv[] = { "git", "describe", commit_id, NULL };
3813         char ref[SIZEOF_STR];
3815         if (!run_io_buf(describe_argv, ref, sizeof(ref)) || !*ref)
3816                 return TRUE;
3818         /* This is the only fatal call, since it can "corrupt" the buffer. */
3819         if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
3820                 return FALSE;
3822         return TRUE;
3825 static void
3826 add_pager_refs(struct view *view, struct line *line)
3828         char buf[SIZEOF_STR];
3829         char *commit_id = (char *)line->data + STRING_SIZE("commit ");
3830         struct ref_list *list;
3831         size_t bufpos = 0, i;
3832         const char *sep = "Refs: ";
3833         bool is_tag = FALSE;
3835         assert(line->type == LINE_COMMIT);
3837         list = get_ref_list(commit_id);
3838         if (!list) {
3839                 if (view == VIEW(REQ_VIEW_DIFF))
3840                         goto try_add_describe_ref;
3841                 return;
3842         }
3844         for (i = 0; i < list->size; i++) {
3845                 struct ref *ref = list->refs[i];
3846                 const char *fmt = ref->tag    ? "%s[%s]" :
3847                                   ref->remote ? "%s<%s>" : "%s%s";
3849                 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
3850                         return;
3851                 sep = ", ";
3852                 if (ref->tag)
3853                         is_tag = TRUE;
3854         }
3856         if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
3857 try_add_describe_ref:
3858                 /* Add <tag>-g<commit_id> "fake" reference. */
3859                 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
3860                         return;
3861         }
3863         if (bufpos == 0)
3864                 return;
3866         add_line_text(view, buf, LINE_PP_REFS);
3869 static bool
3870 pager_read(struct view *view, char *data)
3872         struct line *line;
3874         if (!data)
3875                 return TRUE;
3877         line = add_line_text(view, data, get_line_type(data));
3878         if (!line)
3879                 return FALSE;
3881         if (line->type == LINE_COMMIT &&
3882             (view == VIEW(REQ_VIEW_DIFF) ||
3883              view == VIEW(REQ_VIEW_LOG)))
3884                 add_pager_refs(view, line);
3886         return TRUE;
3889 static enum request
3890 pager_request(struct view *view, enum request request, struct line *line)
3892         int split = 0;
3894         if (request != REQ_ENTER)
3895                 return request;
3897         if (line->type == LINE_COMMIT &&
3898            (view == VIEW(REQ_VIEW_LOG) ||
3899             view == VIEW(REQ_VIEW_PAGER))) {
3900                 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
3901                 split = 1;
3902         }
3904         /* Always scroll the view even if it was split. That way
3905          * you can use Enter to scroll through the log view and
3906          * split open each commit diff. */
3907         scroll_view(view, REQ_SCROLL_LINE_DOWN);
3909         /* FIXME: A minor workaround. Scrolling the view will call report("")
3910          * but if we are scrolling a non-current view this won't properly
3911          * update the view title. */
3912         if (split)
3913                 update_view_title(view);
3915         return REQ_NONE;
3918 static bool
3919 pager_grep(struct view *view, struct line *line)
3921         const char *text[] = { line->data, NULL };
3923         return grep_text(view, text);
3926 static void
3927 pager_select(struct view *view, struct line *line)
3929         if (line->type == LINE_COMMIT) {
3930                 char *text = (char *)line->data + STRING_SIZE("commit ");
3932                 if (view != VIEW(REQ_VIEW_PAGER))
3933                         string_copy_rev(view->ref, text);
3934                 string_copy_rev(ref_commit, text);
3935         }
3938 static struct view_ops pager_ops = {
3939         "line",
3940         NULL,
3941         NULL,
3942         pager_read,
3943         pager_draw,
3944         pager_request,
3945         pager_grep,
3946         pager_select,
3947 };
3949 static const char *log_argv[SIZEOF_ARG] = {
3950         "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
3951 };
3953 static enum request
3954 log_request(struct view *view, enum request request, struct line *line)
3956         switch (request) {
3957         case REQ_REFRESH:
3958                 load_refs();
3959                 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
3960                 return REQ_NONE;
3961         default:
3962                 return pager_request(view, request, line);
3963         }
3966 static struct view_ops log_ops = {
3967         "line",
3968         log_argv,
3969         NULL,
3970         pager_read,
3971         pager_draw,
3972         log_request,
3973         pager_grep,
3974         pager_select,
3975 };
3977 static const char *diff_argv[SIZEOF_ARG] = {
3978         "git", "show", "--pretty=fuller", "--no-color", "--root",
3979                 "--patch-with-stat", "--find-copies-harder", "-C", "%(commit)", NULL
3980 };
3982 static struct view_ops diff_ops = {
3983         "line",
3984         diff_argv,
3985         NULL,
3986         pager_read,
3987         pager_draw,
3988         pager_request,
3989         pager_grep,
3990         pager_select,
3991 };
3993 /*
3994  * Help backend
3995  */
3997 static bool help_keymap_hidden[ARRAY_SIZE(keymap_table)];
3999 static bool
4000 help_open_keymap_title(struct view *view, enum keymap keymap)
4002         struct line *line;
4004         line = add_line_format(view, LINE_HELP_KEYMAP, "[%c] %s bindings",
4005                                help_keymap_hidden[keymap] ? '+' : '-',
4006                                enum_name(keymap_table[keymap]));
4007         if (line)
4008                 line->other = keymap;
4010         return help_keymap_hidden[keymap];
4013 static void
4014 help_open_keymap(struct view *view, enum keymap keymap)
4016         const char *group = NULL;
4017         char buf[SIZEOF_STR];
4018         size_t bufpos;
4019         bool add_title = TRUE;
4020         int i;
4022         for (i = 0; i < ARRAY_SIZE(req_info); i++) {
4023                 const char *key = NULL;
4025                 if (req_info[i].request == REQ_NONE)
4026                         continue;
4028                 if (!req_info[i].request) {
4029                         group = req_info[i].help;
4030                         continue;
4031                 }
4033                 key = get_keys(keymap, req_info[i].request, TRUE);
4034                 if (!key || !*key)
4035                         continue;
4037                 if (add_title && help_open_keymap_title(view, keymap))
4038                         return;
4039                 add_title = false;
4041                 if (group) {
4042                         add_line_text(view, group, LINE_HELP_GROUP);
4043                         group = NULL;
4044                 }
4046                 add_line_format(view, LINE_DEFAULT, "    %-25s %-20s %s", key,
4047                                 enum_name(req_info[i]), req_info[i].help);
4048         }
4050         group = "External commands:";
4052         for (i = 0; i < run_requests; i++) {
4053                 struct run_request *req = get_run_request(REQ_NONE + i + 1);
4054                 const char *key;
4055                 int argc;
4057                 if (!req || req->keymap != keymap)
4058                         continue;
4060                 key = get_key_name(req->key);
4061                 if (!*key)
4062                         key = "(no key defined)";
4064                 if (add_title && help_open_keymap_title(view, keymap))
4065                         return;
4066                 if (group) {
4067                         add_line_text(view, group, LINE_HELP_GROUP);
4068                         group = NULL;
4069                 }
4071                 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
4072                         if (!string_format_from(buf, &bufpos, "%s%s",
4073                                                 argc ? " " : "", req->argv[argc]))
4074                                 return;
4076                 add_line_format(view, LINE_DEFAULT, "    %-25s `%s`", key, buf);
4077         }
4080 static bool
4081 help_open(struct view *view)
4083         enum keymap keymap;
4085         reset_view(view);
4086         add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
4087         add_line_text(view, "", LINE_DEFAULT);
4089         for (keymap = 0; keymap < ARRAY_SIZE(keymap_table); keymap++)
4090                 help_open_keymap(view, keymap);
4092         return TRUE;
4095 static enum request
4096 help_request(struct view *view, enum request request, struct line *line)
4098         switch (request) {
4099         case REQ_ENTER:
4100                 if (line->type == LINE_HELP_KEYMAP) {
4101                         help_keymap_hidden[line->other] =
4102                                 !help_keymap_hidden[line->other];
4103                         view->p_restore = TRUE;
4104                         open_view(view, REQ_VIEW_HELP, OPEN_REFRESH);
4105                 }
4107                 return REQ_NONE;
4108         default:
4109                 return pager_request(view, request, line);
4110         }
4113 static struct view_ops help_ops = {
4114         "line",
4115         NULL,
4116         help_open,
4117         NULL,
4118         pager_draw,
4119         help_request,
4120         pager_grep,
4121         pager_select,
4122 };
4125 /*
4126  * Tree backend
4127  */
4129 struct tree_stack_entry {
4130         struct tree_stack_entry *prev;  /* Entry below this in the stack */
4131         unsigned long lineno;           /* Line number to restore */
4132         char *name;                     /* Position of name in opt_path */
4133 };
4135 /* The top of the path stack. */
4136 static struct tree_stack_entry *tree_stack = NULL;
4137 unsigned long tree_lineno = 0;
4139 static void
4140 pop_tree_stack_entry(void)
4142         struct tree_stack_entry *entry = tree_stack;
4144         tree_lineno = entry->lineno;
4145         entry->name[0] = 0;
4146         tree_stack = entry->prev;
4147         free(entry);
4150 static void
4151 push_tree_stack_entry(const char *name, unsigned long lineno)
4153         struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
4154         size_t pathlen = strlen(opt_path);
4156         if (!entry)
4157                 return;
4159         entry->prev = tree_stack;
4160         entry->name = opt_path + pathlen;
4161         tree_stack = entry;
4163         if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
4164                 pop_tree_stack_entry();
4165                 return;
4166         }
4168         /* Move the current line to the first tree entry. */
4169         tree_lineno = 1;
4170         entry->lineno = lineno;
4173 /* Parse output from git-ls-tree(1):
4174  *
4175  * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
4176  */
4178 #define SIZEOF_TREE_ATTR \
4179         STRING_SIZE("100644 blob f931e1d229c3e185caad4449bf5b66ed72462657\t")
4181 #define SIZEOF_TREE_MODE \
4182         STRING_SIZE("100644 ")
4184 #define TREE_ID_OFFSET \
4185         STRING_SIZE("100644 blob ")
4187 struct tree_entry {
4188         char id[SIZEOF_REV];
4189         mode_t mode;
4190         time_t time;                    /* Date from the author ident. */
4191         const char *author;             /* Author of the commit. */
4192         char name[1];
4193 };
4195 static const char *
4196 tree_path(const struct line *line)
4198         return ((struct tree_entry *) line->data)->name;
4201 static int
4202 tree_compare_entry(const struct line *line1, const struct line *line2)
4204         if (line1->type != line2->type)
4205                 return line1->type == LINE_TREE_DIR ? -1 : 1;
4206         return strcmp(tree_path(line1), tree_path(line2));
4209 static const enum sort_field tree_sort_fields[] = {
4210         ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
4211 };
4212 static struct sort_state tree_sort_state = SORT_STATE(tree_sort_fields);
4214 static int
4215 tree_compare(const void *l1, const void *l2)
4217         const struct line *line1 = (const struct line *) l1;
4218         const struct line *line2 = (const struct line *) l2;
4219         const struct tree_entry *entry1 = ((const struct line *) l1)->data;
4220         const struct tree_entry *entry2 = ((const struct line *) l2)->data;
4222         if (line1->type == LINE_TREE_HEAD)
4223                 return -1;
4224         if (line2->type == LINE_TREE_HEAD)
4225                 return 1;
4227         switch (get_sort_field(tree_sort_state)) {
4228         case ORDERBY_DATE:
4229                 return sort_order(tree_sort_state, entry1->time - entry2->time);
4231         case ORDERBY_AUTHOR:
4232                 return sort_order(tree_sort_state, strcmp(entry1->author, entry2->author));
4234         case ORDERBY_NAME:
4235         default:
4236                 return sort_order(tree_sort_state, tree_compare_entry(line1, line2));
4237         }
4241 static struct line *
4242 tree_entry(struct view *view, enum line_type type, const char *path,
4243            const char *mode, const char *id)
4245         struct tree_entry *entry = calloc(1, sizeof(*entry) + strlen(path));
4246         struct line *line = entry ? add_line_data(view, entry, type) : NULL;
4248         if (!entry || !line) {
4249                 free(entry);
4250                 return NULL;
4251         }
4253         strncpy(entry->name, path, strlen(path));
4254         if (mode)
4255                 entry->mode = strtoul(mode, NULL, 8);
4256         if (id)
4257                 string_copy_rev(entry->id, id);
4259         return line;
4262 static bool
4263 tree_read_date(struct view *view, char *text, bool *read_date)
4265         static const char *author_name;
4266         static time_t author_time;
4268         if (!text && *read_date) {
4269                 *read_date = FALSE;
4270                 return TRUE;
4272         } else if (!text) {
4273                 char *path = *opt_path ? opt_path : ".";
4274                 /* Find next entry to process */
4275                 const char *log_file[] = {
4276                         "git", "log", "--no-color", "--pretty=raw",
4277                                 "--cc", "--raw", view->id, "--", path, NULL
4278                 };
4279                 struct io io = {};
4281                 if (!view->lines) {
4282                         tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL);
4283                         report("Tree is empty");
4284                         return TRUE;
4285                 }
4287                 if (!run_io_rd(&io, log_file, opt_cdup, FORMAT_NONE)) {
4288                         report("Failed to load tree data");
4289                         return TRUE;
4290                 }
4292                 done_io(view->pipe);
4293                 view->io = io;
4294                 *read_date = TRUE;
4295                 return FALSE;
4297         } else if (*text == 'a' && get_line_type(text) == LINE_AUTHOR) {
4298                 parse_author_line(text + STRING_SIZE("author "),
4299                                   &author_name, &author_time);
4301         } else if (*text == ':') {
4302                 char *pos;
4303                 size_t annotated = 1;
4304                 size_t i;
4306                 pos = strchr(text, '\t');
4307                 if (!pos)
4308                         return TRUE;
4309                 text = pos + 1;
4310                 if (*opt_path && !strncmp(text, opt_path, strlen(opt_path)))
4311                         text += strlen(opt_path);
4312                 pos = strchr(text, '/');
4313                 if (pos)
4314                         *pos = 0;
4316                 for (i = 1; i < view->lines; i++) {
4317                         struct line *line = &view->line[i];
4318                         struct tree_entry *entry = line->data;
4320                         annotated += !!entry->author;
4321                         if (entry->author || strcmp(entry->name, text))
4322                                 continue;
4324                         entry->author = author_name;
4325                         entry->time = author_time;
4326                         line->dirty = 1;
4327                         break;
4328                 }
4330                 if (annotated == view->lines)
4331                         kill_io(view->pipe);
4332         }
4333         return TRUE;
4336 static bool
4337 tree_read(struct view *view, char *text)
4339         static bool read_date = FALSE;
4340         struct tree_entry *data;
4341         struct line *entry, *line;
4342         enum line_type type;
4343         size_t textlen = text ? strlen(text) : 0;
4344         char *path = text + SIZEOF_TREE_ATTR;
4346         if (read_date || !text)
4347                 return tree_read_date(view, text, &read_date);
4349         if (textlen <= SIZEOF_TREE_ATTR)
4350                 return FALSE;
4351         if (view->lines == 0 &&
4352             !tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL))
4353                 return FALSE;
4355         /* Strip the path part ... */
4356         if (*opt_path) {
4357                 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
4358                 size_t striplen = strlen(opt_path);
4360                 if (pathlen > striplen)
4361                         memmove(path, path + striplen,
4362                                 pathlen - striplen + 1);
4364                 /* Insert "link" to parent directory. */
4365                 if (view->lines == 1 &&
4366                     !tree_entry(view, LINE_TREE_DIR, "..", "040000", view->ref))
4367                         return FALSE;
4368         }
4370         type = text[SIZEOF_TREE_MODE] == 't' ? LINE_TREE_DIR : LINE_TREE_FILE;
4371         entry = tree_entry(view, type, path, text, text + TREE_ID_OFFSET);
4372         if (!entry)
4373                 return FALSE;
4374         data = entry->data;
4376         /* Skip "Directory ..." and ".." line. */
4377         for (line = &view->line[1 + !!*opt_path]; line < entry; line++) {
4378                 if (tree_compare_entry(line, entry) <= 0)
4379                         continue;
4381                 memmove(line + 1, line, (entry - line) * sizeof(*entry));
4383                 line->data = data;
4384                 line->type = type;
4385                 for (; line <= entry; line++)
4386                         line->dirty = line->cleareol = 1;
4387                 return TRUE;
4388         }
4390         if (tree_lineno > view->lineno) {
4391                 view->lineno = tree_lineno;
4392                 tree_lineno = 0;
4393         }
4395         return TRUE;
4398 static bool
4399 tree_draw(struct view *view, struct line *line, unsigned int lineno)
4401         struct tree_entry *entry = line->data;
4403         if (line->type == LINE_TREE_HEAD) {
4404                 if (draw_text(view, line->type, "Directory path /", TRUE))
4405                         return TRUE;
4406         } else {
4407                 if (draw_mode(view, entry->mode))
4408                         return TRUE;
4410                 if (opt_author && draw_author(view, entry->author))
4411                         return TRUE;
4413                 if (opt_date && draw_date(view, entry->author ? &entry->time : NULL))
4414                         return TRUE;
4415         }
4416         if (draw_text(view, line->type, entry->name, TRUE))
4417                 return TRUE;
4418         return TRUE;
4421 static void
4422 open_blob_editor()
4424         char file[SIZEOF_STR] = "/tmp/tigblob.XXXXXX";
4425         int fd = mkstemp(file);
4427         if (fd == -1)
4428                 report("Failed to create temporary file");
4429         else if (!run_io_append(blob_ops.argv, FORMAT_ALL, fd))
4430                 report("Failed to save blob data to file");
4431         else
4432                 open_editor(FALSE, file);
4433         if (fd != -1)
4434                 unlink(file);
4437 static enum request
4438 tree_request(struct view *view, enum request request, struct line *line)
4440         enum open_flags flags;
4442         switch (request) {
4443         case REQ_VIEW_BLAME:
4444                 if (line->type != LINE_TREE_FILE) {
4445                         report("Blame only supported for files");
4446                         return REQ_NONE;
4447                 }
4449                 string_copy(opt_ref, view->vid);
4450                 return request;
4452         case REQ_EDIT:
4453                 if (line->type != LINE_TREE_FILE) {
4454                         report("Edit only supported for files");
4455                 } else if (!is_head_commit(view->vid)) {
4456                         open_blob_editor();
4457                 } else {
4458                         open_editor(TRUE, opt_file);
4459                 }
4460                 return REQ_NONE;
4462         case REQ_TOGGLE_SORT_FIELD:
4463         case REQ_TOGGLE_SORT_ORDER:
4464                 sort_view(view, request, &tree_sort_state, tree_compare);
4465                 return REQ_NONE;
4467         case REQ_PARENT:
4468                 if (!*opt_path) {
4469                         /* quit view if at top of tree */
4470                         return REQ_VIEW_CLOSE;
4471                 }
4472                 /* fake 'cd  ..' */
4473                 line = &view->line[1];
4474                 break;
4476         case REQ_ENTER:
4477                 break;
4479         default:
4480                 return request;
4481         }
4483         /* Cleanup the stack if the tree view is at a different tree. */
4484         while (!*opt_path && tree_stack)
4485                 pop_tree_stack_entry();
4487         switch (line->type) {
4488         case LINE_TREE_DIR:
4489                 /* Depending on whether it is a subdirectory or parent link
4490                  * mangle the path buffer. */
4491                 if (line == &view->line[1] && *opt_path) {
4492                         pop_tree_stack_entry();
4494                 } else {
4495                         const char *basename = tree_path(line);
4497                         push_tree_stack_entry(basename, view->lineno);
4498                 }
4500                 /* Trees and subtrees share the same ID, so they are not not
4501                  * unique like blobs. */
4502                 flags = OPEN_RELOAD;
4503                 request = REQ_VIEW_TREE;
4504                 break;
4506         case LINE_TREE_FILE:
4507                 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4508                 request = REQ_VIEW_BLOB;
4509                 break;
4511         default:
4512                 return REQ_NONE;
4513         }
4515         open_view(view, request, flags);
4516         if (request == REQ_VIEW_TREE)
4517                 view->lineno = tree_lineno;
4519         return REQ_NONE;
4522 static bool
4523 tree_grep(struct view *view, struct line *line)
4525         struct tree_entry *entry = line->data;
4526         const char *text[] = {
4527                 entry->name,
4528                 opt_author ? entry->author : "",
4529                 opt_date ? mkdate(&entry->time) : "",
4530                 NULL
4531         };
4533         return grep_text(view, text);
4536 static void
4537 tree_select(struct view *view, struct line *line)
4539         struct tree_entry *entry = line->data;
4541         if (line->type == LINE_TREE_FILE) {
4542                 string_copy_rev(ref_blob, entry->id);
4543                 string_format(opt_file, "%s%s", opt_path, tree_path(line));
4545         } else if (line->type != LINE_TREE_DIR) {
4546                 return;
4547         }
4549         string_copy_rev(view->ref, entry->id);
4552 static bool
4553 tree_prepare(struct view *view)
4555         if (view->lines == 0 && opt_prefix[0]) {
4556                 char *pos = opt_prefix;
4558                 while (pos && *pos) {
4559                         char *end = strchr(pos, '/');
4561                         if (end)
4562                                 *end = 0;
4563                         push_tree_stack_entry(pos, 0);
4564                         pos = end;
4565                         if (end) {
4566                                 *end = '/';
4567                                 pos++;
4568                         }
4569                 }
4571         } else if (strcmp(view->vid, view->id)) {
4572                 opt_path[0] = 0;
4573         }
4575         return init_io_rd(&view->io, view->ops->argv, opt_cdup, FORMAT_ALL);
4578 static const char *tree_argv[SIZEOF_ARG] = {
4579         "git", "ls-tree", "%(commit)", "%(directory)", NULL
4580 };
4582 static struct view_ops tree_ops = {
4583         "file",
4584         tree_argv,
4585         NULL,
4586         tree_read,
4587         tree_draw,
4588         tree_request,
4589         tree_grep,
4590         tree_select,
4591         tree_prepare,
4592 };
4594 static bool
4595 blob_read(struct view *view, char *line)
4597         if (!line)
4598                 return TRUE;
4599         return add_line_text(view, line, LINE_DEFAULT) != NULL;
4602 static enum request
4603 blob_request(struct view *view, enum request request, struct line *line)
4605         switch (request) {
4606         case REQ_EDIT:
4607                 open_blob_editor();
4608                 return REQ_NONE;
4609         default:
4610                 return pager_request(view, request, line);
4611         }
4614 static const char *blob_argv[SIZEOF_ARG] = {
4615         "git", "cat-file", "blob", "%(blob)", NULL
4616 };
4618 static struct view_ops blob_ops = {
4619         "line",
4620         blob_argv,
4621         NULL,
4622         blob_read,
4623         pager_draw,
4624         blob_request,
4625         pager_grep,
4626         pager_select,
4627 };
4629 /*
4630  * Blame backend
4631  *
4632  * Loading the blame view is a two phase job:
4633  *
4634  *  1. File content is read either using opt_file from the
4635  *     filesystem or using git-cat-file.
4636  *  2. Then blame information is incrementally added by
4637  *     reading output from git-blame.
4638  */
4640 static const char *blame_head_argv[] = {
4641         "git", "blame", "--incremental", "--", "%(file)", NULL
4642 };
4644 static const char *blame_ref_argv[] = {
4645         "git", "blame", "--incremental", "%(ref)", "--", "%(file)", NULL
4646 };
4648 static const char *blame_cat_file_argv[] = {
4649         "git", "cat-file", "blob", "%(ref):%(file)", NULL
4650 };
4652 struct blame_commit {
4653         char id[SIZEOF_REV];            /* SHA1 ID. */
4654         char title[128];                /* First line of the commit message. */
4655         const char *author;             /* Author of the commit. */
4656         time_t time;                    /* Date from the author ident. */
4657         char filename[128];             /* Name of file. */
4658         bool has_previous;              /* Was a "previous" line detected. */
4659 };
4661 struct blame {
4662         struct blame_commit *commit;
4663         unsigned long lineno;
4664         char text[1];
4665 };
4667 static bool
4668 blame_open(struct view *view)
4670         char path[SIZEOF_STR];
4672         if (!view->parent && *opt_prefix) {
4673                 string_copy(path, opt_file);
4674                 if (!string_format(opt_file, "%s%s", opt_prefix, path))
4675                         return FALSE;
4676         }
4678         if (*opt_ref || !io_open(&view->io, "%s%s", opt_cdup, opt_file)) {
4679                 if (!run_io_rd(&view->io, blame_cat_file_argv, opt_cdup, FORMAT_ALL))
4680                         return FALSE;
4681         }
4683         setup_update(view, opt_file);
4684         string_format(view->ref, "%s ...", opt_file);
4686         return TRUE;
4689 static struct blame_commit *
4690 get_blame_commit(struct view *view, const char *id)
4692         size_t i;
4694         for (i = 0; i < view->lines; i++) {
4695                 struct blame *blame = view->line[i].data;
4697                 if (!blame->commit)
4698                         continue;
4700                 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
4701                         return blame->commit;
4702         }
4704         {
4705                 struct blame_commit *commit = calloc(1, sizeof(*commit));
4707                 if (commit)
4708                         string_ncopy(commit->id, id, SIZEOF_REV);
4709                 return commit;
4710         }
4713 static bool
4714 parse_number(const char **posref, size_t *number, size_t min, size_t max)
4716         const char *pos = *posref;
4718         *posref = NULL;
4719         pos = strchr(pos + 1, ' ');
4720         if (!pos || !isdigit(pos[1]))
4721                 return FALSE;
4722         *number = atoi(pos + 1);
4723         if (*number < min || *number > max)
4724                 return FALSE;
4726         *posref = pos;
4727         return TRUE;
4730 static struct blame_commit *
4731 parse_blame_commit(struct view *view, const char *text, int *blamed)
4733         struct blame_commit *commit;
4734         struct blame *blame;
4735         const char *pos = text + SIZEOF_REV - 2;
4736         size_t orig_lineno = 0;
4737         size_t lineno;
4738         size_t group;
4740         if (strlen(text) <= SIZEOF_REV || pos[1] != ' ')
4741                 return NULL;
4743         if (!parse_number(&pos, &orig_lineno, 1, 9999999) ||
4744             !parse_number(&pos, &lineno, 1, view->lines) ||
4745             !parse_number(&pos, &group, 1, view->lines - lineno + 1))
4746                 return NULL;
4748         commit = get_blame_commit(view, text);
4749         if (!commit)
4750                 return NULL;
4752         *blamed += group;
4753         while (group--) {
4754                 struct line *line = &view->line[lineno + group - 1];
4756                 blame = line->data;
4757                 blame->commit = commit;
4758                 blame->lineno = orig_lineno + group - 1;
4759                 line->dirty = 1;
4760         }
4762         return commit;
4765 static bool
4766 blame_read_file(struct view *view, const char *line, bool *read_file)
4768         if (!line) {
4769                 const char **argv = *opt_ref ? blame_ref_argv : blame_head_argv;
4770                 struct io io = {};
4772                 if (view->lines == 0 && !view->parent)
4773                         die("No blame exist for %s", view->vid);
4775                 if (view->lines == 0 || !run_io_rd(&io, argv, opt_cdup, FORMAT_ALL)) {
4776                         report("Failed to load blame data");
4777                         return TRUE;
4778                 }
4780                 done_io(view->pipe);
4781                 view->io = io;
4782                 *read_file = FALSE;
4783                 return FALSE;
4785         } else {
4786                 size_t linelen = strlen(line);
4787                 struct blame *blame = malloc(sizeof(*blame) + linelen);
4789                 if (!blame)
4790                         return FALSE;
4792                 blame->commit = NULL;
4793                 strncpy(blame->text, line, linelen);
4794                 blame->text[linelen] = 0;
4795                 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
4796         }
4799 static bool
4800 match_blame_header(const char *name, char **line)
4802         size_t namelen = strlen(name);
4803         bool matched = !strncmp(name, *line, namelen);
4805         if (matched)
4806                 *line += namelen;
4808         return matched;
4811 static bool
4812 blame_read(struct view *view, char *line)
4814         static struct blame_commit *commit = NULL;
4815         static int blamed = 0;
4816         static bool read_file = TRUE;
4818         if (read_file)
4819                 return blame_read_file(view, line, &read_file);
4821         if (!line) {
4822                 /* Reset all! */
4823                 commit = NULL;
4824                 blamed = 0;
4825                 read_file = TRUE;
4826                 string_format(view->ref, "%s", view->vid);
4827                 if (view_is_displayed(view)) {
4828                         update_view_title(view);
4829                         redraw_view_from(view, 0);
4830                 }
4831                 return TRUE;
4832         }
4834         if (!commit) {
4835                 commit = parse_blame_commit(view, line, &blamed);
4836                 string_format(view->ref, "%s %2d%%", view->vid,
4837                               view->lines ? blamed * 100 / view->lines : 0);
4839         } else if (match_blame_header("author ", &line)) {
4840                 commit->author = get_author(line);
4842         } else if (match_blame_header("author-time ", &line)) {
4843                 commit->time = (time_t) atol(line);
4845         } else if (match_blame_header("author-tz ", &line)) {
4846                 parse_timezone(&commit->time, line);
4848         } else if (match_blame_header("summary ", &line)) {
4849                 string_ncopy(commit->title, line, strlen(line));
4851         } else if (match_blame_header("previous ", &line)) {
4852                 commit->has_previous = TRUE;
4854         } else if (match_blame_header("filename ", &line)) {
4855                 string_ncopy(commit->filename, line, strlen(line));
4856                 commit = NULL;
4857         }
4859         return TRUE;
4862 static bool
4863 blame_draw(struct view *view, struct line *line, unsigned int lineno)
4865         struct blame *blame = line->data;
4866         time_t *time = NULL;
4867         const char *id = NULL, *author = NULL;
4868         char text[SIZEOF_STR];
4870         if (blame->commit && *blame->commit->filename) {
4871                 id = blame->commit->id;
4872                 author = blame->commit->author;
4873                 time = &blame->commit->time;
4874         }
4876         if (opt_date && draw_date(view, time))
4877                 return TRUE;
4879         if (opt_author && draw_author(view, author))
4880                 return TRUE;
4882         if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
4883                 return TRUE;
4885         if (draw_lineno(view, lineno))
4886                 return TRUE;
4888         string_expand(text, sizeof(text), blame->text, opt_tab_size);
4889         draw_text(view, LINE_DEFAULT, text, TRUE);
4890         return TRUE;
4893 static bool
4894 check_blame_commit(struct blame *blame, bool check_null_id)
4896         if (!blame->commit)
4897                 report("Commit data not loaded yet");
4898         else if (check_null_id && !strcmp(blame->commit->id, NULL_ID))
4899                 report("No commit exist for the selected line");
4900         else
4901                 return TRUE;
4902         return FALSE;
4905 static void
4906 setup_blame_parent_line(struct view *view, struct blame *blame)
4908         const char *diff_tree_argv[] = {
4909                 "git", "diff-tree", "-U0", blame->commit->id,
4910                         "--", blame->commit->filename, NULL
4911         };
4912         struct io io = {};
4913         int parent_lineno = -1;
4914         int blamed_lineno = -1;
4915         char *line;
4917         if (!run_io(&io, diff_tree_argv, NULL, IO_RD))
4918                 return;
4920         while ((line = io_get(&io, '\n', TRUE))) {
4921                 if (*line == '@') {
4922                         char *pos = strchr(line, '+');
4924                         parent_lineno = atoi(line + 4);
4925                         if (pos)
4926                                 blamed_lineno = atoi(pos + 1);
4928                 } else if (*line == '+' && parent_lineno != -1) {
4929                         if (blame->lineno == blamed_lineno - 1 &&
4930                             !strcmp(blame->text, line + 1)) {
4931                                 view->lineno = parent_lineno ? parent_lineno - 1 : 0;
4932                                 break;
4933                         }
4934                         blamed_lineno++;
4935                 }
4936         }
4938         done_io(&io);
4941 static enum request
4942 blame_request(struct view *view, enum request request, struct line *line)
4944         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4945         struct blame *blame = line->data;
4947         switch (request) {
4948         case REQ_VIEW_BLAME:
4949                 if (check_blame_commit(blame, TRUE)) {
4950                         string_copy(opt_ref, blame->commit->id);
4951                         string_copy(opt_file, blame->commit->filename);
4952                         if (blame->lineno)
4953                                 view->lineno = blame->lineno;
4954                         open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4955                 }
4956                 break;
4958         case REQ_PARENT:
4959                 if (check_blame_commit(blame, TRUE) &&
4960                     select_commit_parent(blame->commit->id, opt_ref,
4961                                          blame->commit->filename)) {
4962                         string_copy(opt_file, blame->commit->filename);
4963                         setup_blame_parent_line(view, blame);
4964                         open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4965                 }
4966                 break;
4968         case REQ_ENTER:
4969                 if (!check_blame_commit(blame, FALSE))
4970                         break;
4972                 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
4973                     !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
4974                         break;
4976                 if (!strcmp(blame->commit->id, NULL_ID)) {
4977                         struct view *diff = VIEW(REQ_VIEW_DIFF);
4978                         const char *diff_index_argv[] = {
4979                                 "git", "diff-index", "--root", "--patch-with-stat",
4980                                         "-C", "-M", "HEAD", "--", view->vid, NULL
4981                         };
4983                         if (!blame->commit->has_previous) {
4984                                 diff_index_argv[1] = "diff";
4985                                 diff_index_argv[2] = "--no-color";
4986                                 diff_index_argv[6] = "--";
4987                                 diff_index_argv[7] = "/dev/null";
4988                         }
4990                         if (!prepare_update(diff, diff_index_argv, NULL, FORMAT_DASH)) {
4991                                 report("Failed to allocate diff command");
4992                                 break;
4993                         }
4994                         flags |= OPEN_PREPARED;
4995                 }
4997                 open_view(view, REQ_VIEW_DIFF, flags);
4998                 if (VIEW(REQ_VIEW_DIFF)->pipe && !strcmp(blame->commit->id, NULL_ID))
4999                         string_copy_rev(VIEW(REQ_VIEW_DIFF)->ref, NULL_ID);
5000                 break;
5002         default:
5003                 return request;
5004         }
5006         return REQ_NONE;
5009 static bool
5010 blame_grep(struct view *view, struct line *line)
5012         struct blame *blame = line->data;
5013         struct blame_commit *commit = blame->commit;
5014         const char *text[] = {
5015                 blame->text,
5016                 commit ? commit->title : "",
5017                 commit ? commit->id : "",
5018                 commit && opt_author ? commit->author : "",
5019                 commit && opt_date ? mkdate(&commit->time) : "",
5020                 NULL
5021         };
5023         return grep_text(view, text);
5026 static void
5027 blame_select(struct view *view, struct line *line)
5029         struct blame *blame = line->data;
5030         struct blame_commit *commit = blame->commit;
5032         if (!commit)
5033                 return;
5035         if (!strcmp(commit->id, NULL_ID))
5036                 string_ncopy(ref_commit, "HEAD", 4);
5037         else
5038                 string_copy_rev(ref_commit, commit->id);
5041 static struct view_ops blame_ops = {
5042         "line",
5043         NULL,
5044         blame_open,
5045         blame_read,
5046         blame_draw,
5047         blame_request,
5048         blame_grep,
5049         blame_select,
5050 };
5052 /*
5053  * Branch backend
5054  */
5056 struct branch {
5057         const char *author;             /* Author of the last commit. */
5058         time_t time;                    /* Date of the last activity. */
5059         const struct ref *ref;          /* Name and commit ID information. */
5060 };
5062 static const struct ref branch_all;
5064 static const enum sort_field branch_sort_fields[] = {
5065         ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
5066 };
5067 static struct sort_state branch_sort_state = SORT_STATE(branch_sort_fields);
5069 static int
5070 branch_compare(const void *l1, const void *l2)
5072         const struct branch *branch1 = ((const struct line *) l1)->data;
5073         const struct branch *branch2 = ((const struct line *) l2)->data;
5075         switch (get_sort_field(branch_sort_state)) {
5076         case ORDERBY_DATE:
5077                 return sort_order(branch_sort_state, branch1->time - branch2->time);
5079         case ORDERBY_AUTHOR:
5080                 return sort_order(branch_sort_state, strcmp(branch1->author, branch2->author));
5082         case ORDERBY_NAME:
5083         default:
5084                 return sort_order(branch_sort_state, strcmp(branch1->ref->name, branch2->ref->name));
5085         }
5088 static bool
5089 branch_draw(struct view *view, struct line *line, unsigned int lineno)
5091         struct branch *branch = line->data;
5092         enum line_type type = branch->ref->head ? LINE_MAIN_HEAD : LINE_DEFAULT;
5094         if (opt_date && draw_date(view, branch->author ? &branch->time : NULL))
5095                 return TRUE;
5097         if (opt_author && draw_author(view, branch->author))
5098                 return TRUE;
5100         draw_text(view, type, branch->ref == &branch_all ? "All branches" : branch->ref->name, TRUE);
5101         return TRUE;
5104 static enum request
5105 branch_request(struct view *view, enum request request, struct line *line)
5107         struct branch *branch = line->data;
5109         switch (request) {
5110         case REQ_REFRESH:
5111                 load_refs();
5112                 open_view(view, REQ_VIEW_BRANCH, OPEN_REFRESH);
5113                 return REQ_NONE;
5115         case REQ_TOGGLE_SORT_FIELD:
5116         case REQ_TOGGLE_SORT_ORDER:
5117                 sort_view(view, request, &branch_sort_state, branch_compare);
5118                 return REQ_NONE;
5120         case REQ_ENTER:
5121                 if (branch->ref == &branch_all) {
5122                         const char *all_branches_argv[] = {
5123                                 "git", "log", "--no-color", "--pretty=raw", "--parents",
5124                                       "--topo-order", "--all", NULL
5125                         };
5126                         struct view *main_view = VIEW(REQ_VIEW_MAIN);
5128                         if (!prepare_update(main_view, all_branches_argv, NULL, FORMAT_NONE)) {
5129                                 report("Failed to load view of all branches");
5130                                 return REQ_NONE;
5131                         }
5132                         open_view(view, REQ_VIEW_MAIN, OPEN_PREPARED | OPEN_SPLIT);
5133                 } else {
5134                         open_view(view, REQ_VIEW_MAIN, OPEN_SPLIT);
5135                 }
5136                 return REQ_NONE;
5138         default:
5139                 return request;
5140         }
5143 static bool
5144 branch_read(struct view *view, char *line)
5146         static char id[SIZEOF_REV];
5147         struct branch *reference;
5148         size_t i;
5150         if (!line)
5151                 return TRUE;
5153         switch (get_line_type(line)) {
5154         case LINE_COMMIT:
5155                 string_copy_rev(id, line + STRING_SIZE("commit "));
5156                 return TRUE;
5158         case LINE_AUTHOR:
5159                 for (i = 0, reference = NULL; i < view->lines; i++) {
5160                         struct branch *branch = view->line[i].data;
5162                         if (strcmp(branch->ref->id, id))
5163                                 continue;
5165                         view->line[i].dirty = TRUE;
5166                         if (reference) {
5167                                 branch->author = reference->author;
5168                                 branch->time = reference->time;
5169                                 continue;
5170                         }
5172                         parse_author_line(line + STRING_SIZE("author "),
5173                                           &branch->author, &branch->time);
5174                         reference = branch;
5175                 }
5176                 return TRUE;
5178         default:
5179                 return TRUE;
5180         }
5184 static bool
5185 branch_open_visitor(void *data, const struct ref *ref)
5187         struct view *view = data;
5188         struct branch *branch;
5190         if (ref->tag || ref->ltag || ref->remote)
5191                 return TRUE;
5193         branch = calloc(1, sizeof(*branch));
5194         if (!branch)
5195                 return FALSE;
5197         branch->ref = ref;
5198         return !!add_line_data(view, branch, LINE_DEFAULT);
5201 static bool
5202 branch_open(struct view *view)
5204         const char *branch_log[] = {
5205                 "git", "log", "--no-color", "--pretty=raw",
5206                         "--simplify-by-decoration", "--all", NULL
5207         };
5209         if (!run_io_rd(&view->io, branch_log, NULL, FORMAT_NONE)) {
5210                 report("Failed to load branch data");
5211                 return TRUE;
5212         }
5214         setup_update(view, view->id);
5215         branch_open_visitor(view, &branch_all);
5216         foreach_ref(branch_open_visitor, view);
5217         view->p_restore = TRUE;
5219         return TRUE;
5222 static bool
5223 branch_grep(struct view *view, struct line *line)
5225         struct branch *branch = line->data;
5226         const char *text[] = {
5227                 branch->ref->name,
5228                 branch->author,
5229                 NULL
5230         };
5232         return grep_text(view, text);
5235 static void
5236 branch_select(struct view *view, struct line *line)
5238         struct branch *branch = line->data;
5240         string_copy_rev(view->ref, branch->ref->id);
5241         string_copy_rev(ref_commit, branch->ref->id);
5242         string_copy_rev(ref_head, branch->ref->id);
5245 static struct view_ops branch_ops = {
5246         "branch",
5247         NULL,
5248         branch_open,
5249         branch_read,
5250         branch_draw,
5251         branch_request,
5252         branch_grep,
5253         branch_select,
5254 };
5256 /*
5257  * Status backend
5258  */
5260 struct status {
5261         char status;
5262         struct {
5263                 mode_t mode;
5264                 char rev[SIZEOF_REV];
5265                 char name[SIZEOF_STR];
5266         } old;
5267         struct {
5268                 mode_t mode;
5269                 char rev[SIZEOF_REV];
5270                 char name[SIZEOF_STR];
5271         } new;
5272 };
5274 static char status_onbranch[SIZEOF_STR];
5275 static struct status stage_status;
5276 static enum line_type stage_line_type;
5277 static size_t stage_chunks;
5278 static int *stage_chunk;
5280 DEFINE_ALLOCATOR(realloc_ints, int, 32)
5282 /* This should work even for the "On branch" line. */
5283 static inline bool
5284 status_has_none(struct view *view, struct line *line)
5286         return line < view->line + view->lines && !line[1].data;
5289 /* Get fields from the diff line:
5290  * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
5291  */
5292 static inline bool
5293 status_get_diff(struct status *file, const char *buf, size_t bufsize)
5295         const char *old_mode = buf +  1;
5296         const char *new_mode = buf +  8;
5297         const char *old_rev  = buf + 15;
5298         const char *new_rev  = buf + 56;
5299         const char *status   = buf + 97;
5301         if (bufsize < 98 ||
5302             old_mode[-1] != ':' ||
5303             new_mode[-1] != ' ' ||
5304             old_rev[-1]  != ' ' ||
5305             new_rev[-1]  != ' ' ||
5306             status[-1]   != ' ')
5307                 return FALSE;
5309         file->status = *status;
5311         string_copy_rev(file->old.rev, old_rev);
5312         string_copy_rev(file->new.rev, new_rev);
5314         file->old.mode = strtoul(old_mode, NULL, 8);
5315         file->new.mode = strtoul(new_mode, NULL, 8);
5317         file->old.name[0] = file->new.name[0] = 0;
5319         return TRUE;
5322 static bool
5323 status_run(struct view *view, const char *argv[], char status, enum line_type type)
5325         struct status *unmerged = NULL;
5326         char *buf;
5327         struct io io = {};
5329         if (!run_io(&io, argv, opt_cdup, IO_RD))
5330                 return FALSE;
5332         add_line_data(view, NULL, type);
5334         while ((buf = io_get(&io, 0, TRUE))) {
5335                 struct status *file = unmerged;
5337                 if (!file) {
5338                         file = calloc(1, sizeof(*file));
5339                         if (!file || !add_line_data(view, file, type))
5340                                 goto error_out;
5341                 }
5343                 /* Parse diff info part. */
5344                 if (status) {
5345                         file->status = status;
5346                         if (status == 'A')
5347                                 string_copy(file->old.rev, NULL_ID);
5349                 } else if (!file->status || file == unmerged) {
5350                         if (!status_get_diff(file, buf, strlen(buf)))
5351                                 goto error_out;
5353                         buf = io_get(&io, 0, TRUE);
5354                         if (!buf)
5355                                 break;
5357                         /* Collapse all modified entries that follow an
5358                          * associated unmerged entry. */
5359                         if (unmerged == file) {
5360                                 unmerged->status = 'U';
5361                                 unmerged = NULL;
5362                         } else if (file->status == 'U') {
5363                                 unmerged = file;
5364                         }
5365                 }
5367                 /* Grab the old name for rename/copy. */
5368                 if (!*file->old.name &&
5369                     (file->status == 'R' || file->status == 'C')) {
5370                         string_ncopy(file->old.name, buf, strlen(buf));
5372                         buf = io_get(&io, 0, TRUE);
5373                         if (!buf)
5374                                 break;
5375                 }
5377                 /* git-ls-files just delivers a NUL separated list of
5378                  * file names similar to the second half of the
5379                  * git-diff-* output. */
5380                 string_ncopy(file->new.name, buf, strlen(buf));
5381                 if (!*file->old.name)
5382                         string_copy(file->old.name, file->new.name);
5383                 file = NULL;
5384         }
5386         if (io_error(&io)) {
5387 error_out:
5388                 done_io(&io);
5389                 return FALSE;
5390         }
5392         if (!view->line[view->lines - 1].data)
5393                 add_line_data(view, NULL, LINE_STAT_NONE);
5395         done_io(&io);
5396         return TRUE;
5399 /* Don't show unmerged entries in the staged section. */
5400 static const char *status_diff_index_argv[] = {
5401         "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
5402                              "--cached", "-M", "HEAD", NULL
5403 };
5405 static const char *status_diff_files_argv[] = {
5406         "git", "diff-files", "-z", NULL
5407 };
5409 static const char *status_list_other_argv[] = {
5410         "git", "ls-files", "-z", "--others", "--exclude-standard", NULL
5411 };
5413 static const char *status_list_no_head_argv[] = {
5414         "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
5415 };
5417 static const char *update_index_argv[] = {
5418         "git", "update-index", "-q", "--unmerged", "--refresh", NULL
5419 };
5421 /* Restore the previous line number to stay in the context or select a
5422  * line with something that can be updated. */
5423 static void
5424 status_restore(struct view *view)
5426         if (view->p_lineno >= view->lines)
5427                 view->p_lineno = view->lines - 1;
5428         while (view->p_lineno < view->lines && !view->line[view->p_lineno].data)
5429                 view->p_lineno++;
5430         while (view->p_lineno > 0 && !view->line[view->p_lineno].data)
5431                 view->p_lineno--;
5433         /* If the above fails, always skip the "On branch" line. */
5434         if (view->p_lineno < view->lines)
5435                 view->lineno = view->p_lineno;
5436         else
5437                 view->lineno = 1;
5439         if (view->lineno < view->offset)
5440                 view->offset = view->lineno;
5441         else if (view->offset + view->height <= view->lineno)
5442                 view->offset = view->lineno - view->height + 1;
5444         view->p_restore = FALSE;
5447 static void
5448 status_update_onbranch(void)
5450         static const char *paths[][2] = {
5451                 { "rebase-apply/rebasing",      "Rebasing" },
5452                 { "rebase-apply/applying",      "Applying mailbox" },
5453                 { "rebase-apply/",              "Rebasing mailbox" },
5454                 { "rebase-merge/interactive",   "Interactive rebase" },
5455                 { "rebase-merge/",              "Rebase merge" },
5456                 { "MERGE_HEAD",                 "Merging" },
5457                 { "BISECT_LOG",                 "Bisecting" },
5458                 { "HEAD",                       "On branch" },
5459         };
5460         char buf[SIZEOF_STR];
5461         struct stat stat;
5462         int i;
5464         if (is_initial_commit()) {
5465                 string_copy(status_onbranch, "Initial commit");
5466                 return;
5467         }
5469         for (i = 0; i < ARRAY_SIZE(paths); i++) {
5470                 char *head = opt_head;
5472                 if (!string_format(buf, "%s/%s", opt_git_dir, paths[i][0]) ||
5473                     lstat(buf, &stat) < 0)
5474                         continue;
5476                 if (!*opt_head) {
5477                         struct io io = {};
5479                         if (io_open(&io, "%s/rebase-merge/head-name", opt_git_dir) &&
5480                             io_read_buf(&io, buf, sizeof(buf))) {
5481                                 head = buf;
5482                                 if (!prefixcmp(head, "refs/heads/"))
5483                                         head += STRING_SIZE("refs/heads/");
5484                         }
5485                 }
5487                 if (!string_format(status_onbranch, "%s %s", paths[i][1], head))
5488                         string_copy(status_onbranch, opt_head);
5489                 return;
5490         }
5492         string_copy(status_onbranch, "Not currently on any branch");
5495 /* First parse staged info using git-diff-index(1), then parse unstaged
5496  * info using git-diff-files(1), and finally untracked files using
5497  * git-ls-files(1). */
5498 static bool
5499 status_open(struct view *view)
5501         reset_view(view);
5503         add_line_data(view, NULL, LINE_STAT_HEAD);
5504         status_update_onbranch();
5506         run_io_bg(update_index_argv);
5508         if (is_initial_commit()) {
5509                 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
5510                         return FALSE;
5511         } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
5512                 return FALSE;
5513         }
5515         if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
5516             !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
5517                 return FALSE;
5519         /* Restore the exact position or use the specialized restore
5520          * mode? */
5521         if (!view->p_restore)
5522                 status_restore(view);
5523         return TRUE;
5526 static bool
5527 status_draw(struct view *view, struct line *line, unsigned int lineno)
5529         struct status *status = line->data;
5530         enum line_type type;
5531         const char *text;
5533         if (!status) {
5534                 switch (line->type) {
5535                 case LINE_STAT_STAGED:
5536                         type = LINE_STAT_SECTION;
5537                         text = "Changes to be committed:";
5538                         break;
5540                 case LINE_STAT_UNSTAGED:
5541                         type = LINE_STAT_SECTION;
5542                         text = "Changed but not updated:";
5543                         break;
5545                 case LINE_STAT_UNTRACKED:
5546                         type = LINE_STAT_SECTION;
5547                         text = "Untracked files:";
5548                         break;
5550                 case LINE_STAT_NONE:
5551                         type = LINE_DEFAULT;
5552                         text = "  (no files)";
5553                         break;
5555                 case LINE_STAT_HEAD:
5556                         type = LINE_STAT_HEAD;
5557                         text = status_onbranch;
5558                         break;
5560                 default:
5561                         return FALSE;
5562                 }
5563         } else {
5564                 static char buf[] = { '?', ' ', ' ', ' ', 0 };
5566                 buf[0] = status->status;
5567                 if (draw_text(view, line->type, buf, TRUE))
5568                         return TRUE;
5569                 type = LINE_DEFAULT;
5570                 text = status->new.name;
5571         }
5573         draw_text(view, type, text, TRUE);
5574         return TRUE;
5577 static enum request
5578 status_load_error(struct view *view, struct view *stage, const char *path)
5580         if (displayed_views() == 2 || display[current_view] != view)
5581                 maximize_view(view);
5582         report("Failed to load '%s': %s", path, io_strerror(&stage->io));
5583         return REQ_NONE;
5586 static enum request
5587 status_enter(struct view *view, struct line *line)
5589         struct status *status = line->data;
5590         const char *oldpath = status ? status->old.name : NULL;
5591         /* Diffs for unmerged entries are empty when passing the new
5592          * path, so leave it empty. */
5593         const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
5594         const char *info;
5595         enum open_flags split;
5596         struct view *stage = VIEW(REQ_VIEW_STAGE);
5598         if (line->type == LINE_STAT_NONE ||
5599             (!status && line[1].type == LINE_STAT_NONE)) {
5600                 report("No file to diff");
5601                 return REQ_NONE;
5602         }
5604         switch (line->type) {
5605         case LINE_STAT_STAGED:
5606                 if (is_initial_commit()) {
5607                         const char *no_head_diff_argv[] = {
5608                                 "git", "diff", "--no-color", "--patch-with-stat",
5609                                         "--", "/dev/null", newpath, NULL
5610                         };
5612                         if (!prepare_update(stage, no_head_diff_argv, opt_cdup, FORMAT_DASH))
5613                                 return status_load_error(view, stage, newpath);
5614                 } else {
5615                         const char *index_show_argv[] = {
5616                                 "git", "diff-index", "--root", "--patch-with-stat",
5617                                         "-C", "-M", "--cached", "HEAD", "--",
5618                                         oldpath, newpath, NULL
5619                         };
5621                         if (!prepare_update(stage, index_show_argv, opt_cdup, FORMAT_DASH))
5622                                 return status_load_error(view, stage, newpath);
5623                 }
5625                 if (status)
5626                         info = "Staged changes to %s";
5627                 else
5628                         info = "Staged changes";
5629                 break;
5631         case LINE_STAT_UNSTAGED:
5632         {
5633                 const char *files_show_argv[] = {
5634                         "git", "diff-files", "--root", "--patch-with-stat",
5635                                 "-C", "-M", "--", oldpath, newpath, NULL
5636                 };
5638                 if (!prepare_update(stage, files_show_argv, opt_cdup, FORMAT_DASH))
5639                         return status_load_error(view, stage, newpath);
5640                 if (status)
5641                         info = "Unstaged changes to %s";
5642                 else
5643                         info = "Unstaged changes";
5644                 break;
5645         }
5646         case LINE_STAT_UNTRACKED:
5647                 if (!newpath) {
5648                         report("No file to show");
5649                         return REQ_NONE;
5650                 }
5652                 if (!suffixcmp(status->new.name, -1, "/")) {
5653                         report("Cannot display a directory");
5654                         return REQ_NONE;
5655                 }
5657                 if (!prepare_update_file(stage, newpath))
5658                         return status_load_error(view, stage, newpath);
5659                 info = "Untracked file %s";
5660                 break;
5662         case LINE_STAT_HEAD:
5663                 return REQ_NONE;
5665         default:
5666                 die("line type %d not handled in switch", line->type);
5667         }
5669         split = view_is_displayed(view) ? OPEN_SPLIT : 0;
5670         open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split);
5671         if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
5672                 if (status) {
5673                         stage_status = *status;
5674                 } else {
5675                         memset(&stage_status, 0, sizeof(stage_status));
5676                 }
5678                 stage_line_type = line->type;
5679                 stage_chunks = 0;
5680                 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
5681         }
5683         return REQ_NONE;
5686 static bool
5687 status_exists(struct status *status, enum line_type type)
5689         struct view *view = VIEW(REQ_VIEW_STATUS);
5690         unsigned long lineno;
5692         for (lineno = 0; lineno < view->lines; lineno++) {
5693                 struct line *line = &view->line[lineno];
5694                 struct status *pos = line->data;
5696                 if (line->type != type)
5697                         continue;
5698                 if (!pos && (!status || !status->status) && line[1].data) {
5699                         select_view_line(view, lineno);
5700                         return TRUE;
5701                 }
5702                 if (pos && !strcmp(status->new.name, pos->new.name)) {
5703                         select_view_line(view, lineno);
5704                         return TRUE;
5705                 }
5706         }
5708         return FALSE;
5712 static bool
5713 status_update_prepare(struct io *io, enum line_type type)
5715         const char *staged_argv[] = {
5716                 "git", "update-index", "-z", "--index-info", NULL
5717         };
5718         const char *others_argv[] = {
5719                 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
5720         };
5722         switch (type) {
5723         case LINE_STAT_STAGED:
5724                 return run_io(io, staged_argv, opt_cdup, IO_WR);
5726         case LINE_STAT_UNSTAGED:
5727         case LINE_STAT_UNTRACKED:
5728                 return run_io(io, others_argv, opt_cdup, IO_WR);
5730         default:
5731                 die("line type %d not handled in switch", type);
5732                 return FALSE;
5733         }
5736 static bool
5737 status_update_write(struct io *io, struct status *status, enum line_type type)
5739         char buf[SIZEOF_STR];
5740         size_t bufsize = 0;
5742         switch (type) {
5743         case LINE_STAT_STAGED:
5744                 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
5745                                         status->old.mode,
5746                                         status->old.rev,
5747                                         status->old.name, 0))
5748                         return FALSE;
5749                 break;
5751         case LINE_STAT_UNSTAGED:
5752         case LINE_STAT_UNTRACKED:
5753                 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
5754                         return FALSE;
5755                 break;
5757         default:
5758                 die("line type %d not handled in switch", type);
5759         }
5761         return io_write(io, buf, bufsize);
5764 static bool
5765 status_update_file(struct status *status, enum line_type type)
5767         struct io io = {};
5768         bool result;
5770         if (!status_update_prepare(&io, type))
5771                 return FALSE;
5773         result = status_update_write(&io, status, type);
5774         return done_io(&io) && result;
5777 static bool
5778 status_update_files(struct view *view, struct line *line)
5780         char buf[sizeof(view->ref)];
5781         struct io io = {};
5782         bool result = TRUE;
5783         struct line *pos = view->line + view->lines;
5784         int files = 0;
5785         int file, done;
5786         int cursor_y = -1, cursor_x = -1;
5788         if (!status_update_prepare(&io, line->type))
5789                 return FALSE;
5791         for (pos = line; pos < view->line + view->lines && pos->data; pos++)
5792                 files++;
5794         string_copy(buf, view->ref);
5795         getsyx(cursor_y, cursor_x);
5796         for (file = 0, done = 5; result && file < files; line++, file++) {
5797                 int almost_done = file * 100 / files;
5799                 if (almost_done > done) {
5800                         done = almost_done;
5801                         string_format(view->ref, "updating file %u of %u (%d%% done)",
5802                                       file, files, done);
5803                         update_view_title(view);
5804                         setsyx(cursor_y, cursor_x);
5805                         doupdate();
5806                 }
5807                 result = status_update_write(&io, line->data, line->type);
5808         }
5809         string_copy(view->ref, buf);
5811         return done_io(&io) && result;
5814 static bool
5815 status_update(struct view *view)
5817         struct line *line = &view->line[view->lineno];
5819         assert(view->lines);
5821         if (!line->data) {
5822                 /* This should work even for the "On branch" line. */
5823                 if (line < view->line + view->lines && !line[1].data) {
5824                         report("Nothing to update");
5825                         return FALSE;
5826                 }
5828                 if (!status_update_files(view, line + 1)) {
5829                         report("Failed to update file status");
5830                         return FALSE;
5831                 }
5833         } else if (!status_update_file(line->data, line->type)) {
5834                 report("Failed to update file status");
5835                 return FALSE;
5836         }
5838         return TRUE;
5841 static bool
5842 status_revert(struct status *status, enum line_type type, bool has_none)
5844         if (!status || type != LINE_STAT_UNSTAGED) {
5845                 if (type == LINE_STAT_STAGED) {
5846                         report("Cannot revert changes to staged files");
5847                 } else if (type == LINE_STAT_UNTRACKED) {
5848                         report("Cannot revert changes to untracked files");
5849                 } else if (has_none) {
5850                         report("Nothing to revert");
5851                 } else {
5852                         report("Cannot revert changes to multiple files");
5853                 }
5855         } else if (prompt_yesno("Are you sure you want to revert changes?")) {
5856                 char mode[10] = "100644";
5857                 const char *reset_argv[] = {
5858                         "git", "update-index", "--cacheinfo", mode,
5859                                 status->old.rev, status->old.name, NULL
5860                 };
5861                 const char *checkout_argv[] = {
5862                         "git", "checkout", "--", status->old.name, NULL
5863                 };
5865                 if (status->status == 'U') {
5866                         string_format(mode, "%5o", status->old.mode);
5868                         if (status->old.mode == 0 && status->new.mode == 0) {
5869                                 reset_argv[2] = "--force-remove";
5870                                 reset_argv[3] = status->old.name;
5871                                 reset_argv[4] = NULL;
5872                         }
5874                         if (!run_io_fg(reset_argv, opt_cdup))
5875                                 return FALSE;
5876                         if (status->old.mode == 0 && status->new.mode == 0)
5877                                 return TRUE;
5878                 }
5880                 return run_io_fg(checkout_argv, opt_cdup);
5881         }
5883         return FALSE;
5886 static enum request
5887 status_request(struct view *view, enum request request, struct line *line)
5889         struct status *status = line->data;
5891         switch (request) {
5892         case REQ_STATUS_UPDATE:
5893                 if (!status_update(view))
5894                         return REQ_NONE;
5895                 break;
5897         case REQ_STATUS_REVERT:
5898                 if (!status_revert(status, line->type, status_has_none(view, line)))
5899                         return REQ_NONE;
5900                 break;
5902         case REQ_STATUS_MERGE:
5903                 if (!status || status->status != 'U') {
5904                         report("Merging only possible for files with unmerged status ('U').");
5905                         return REQ_NONE;
5906                 }
5907                 open_mergetool(status->new.name);
5908                 break;
5910         case REQ_EDIT:
5911                 if (!status)
5912                         return request;
5913                 if (status->status == 'D') {
5914                         report("File has been deleted.");
5915                         return REQ_NONE;
5916                 }
5918                 open_editor(status->status != '?', status->new.name);
5919                 break;
5921         case REQ_VIEW_BLAME:
5922                 if (status)
5923                         opt_ref[0] = 0;
5924                 return request;
5926         case REQ_ENTER:
5927                 /* After returning the status view has been split to
5928                  * show the stage view. No further reloading is
5929                  * necessary. */
5930                 return status_enter(view, line);
5932         case REQ_REFRESH:
5933                 /* Simply reload the view. */
5934                 break;
5936         default:
5937                 return request;
5938         }
5940         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
5942         return REQ_NONE;
5945 static void
5946 status_select(struct view *view, struct line *line)
5948         struct status *status = line->data;
5949         char file[SIZEOF_STR] = "all files";
5950         const char *text;
5951         const char *key;
5953         if (status && !string_format(file, "'%s'", status->new.name))
5954                 return;
5956         if (!status && line[1].type == LINE_STAT_NONE)
5957                 line++;
5959         switch (line->type) {
5960         case LINE_STAT_STAGED:
5961                 text = "Press %s to unstage %s for commit";
5962                 break;
5964         case LINE_STAT_UNSTAGED:
5965                 text = "Press %s to stage %s for commit";
5966                 break;
5968         case LINE_STAT_UNTRACKED:
5969                 text = "Press %s to stage %s for addition";
5970                 break;
5972         case LINE_STAT_HEAD:
5973         case LINE_STAT_NONE:
5974                 text = "Nothing to update";
5975                 break;
5977         default:
5978                 die("line type %d not handled in switch", line->type);
5979         }
5981         if (status && status->status == 'U') {
5982                 text = "Press %s to resolve conflict in %s";
5983                 key = get_key(KEYMAP_STATUS, REQ_STATUS_MERGE);
5985         } else {
5986                 key = get_key(KEYMAP_STATUS, REQ_STATUS_UPDATE);
5987         }
5989         string_format(view->ref, text, key, file);
5990         if (status)
5991                 string_copy(opt_file, status->new.name);
5994 static bool
5995 status_grep(struct view *view, struct line *line)
5997         struct status *status = line->data;
5999         if (status) {
6000                 const char buf[2] = { status->status, 0 };
6001                 const char *text[] = { status->new.name, buf, NULL };
6003                 return grep_text(view, text);
6004         }
6006         return FALSE;
6009 static struct view_ops status_ops = {
6010         "file",
6011         NULL,
6012         status_open,
6013         NULL,
6014         status_draw,
6015         status_request,
6016         status_grep,
6017         status_select,
6018 };
6021 static bool
6022 stage_diff_write(struct io *io, struct line *line, struct line *end)
6024         while (line < end) {
6025                 if (!io_write(io, line->data, strlen(line->data)) ||
6026                     !io_write(io, "\n", 1))
6027                         return FALSE;
6028                 line++;
6029                 if (line->type == LINE_DIFF_CHUNK ||
6030                     line->type == LINE_DIFF_HEADER)
6031                         break;
6032         }
6034         return TRUE;
6037 static struct line *
6038 stage_diff_find(struct view *view, struct line *line, enum line_type type)
6040         for (; view->line < line; line--)
6041                 if (line->type == type)
6042                         return line;
6044         return NULL;
6047 static bool
6048 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
6050         const char *apply_argv[SIZEOF_ARG] = {
6051                 "git", "apply", "--whitespace=nowarn", NULL
6052         };
6053         struct line *diff_hdr;
6054         struct io io = {};
6055         int argc = 3;
6057         diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
6058         if (!diff_hdr)
6059                 return FALSE;
6061         if (!revert)
6062                 apply_argv[argc++] = "--cached";
6063         if (revert || stage_line_type == LINE_STAT_STAGED)
6064                 apply_argv[argc++] = "-R";
6065         apply_argv[argc++] = "-";
6066         apply_argv[argc++] = NULL;
6067         if (!run_io(&io, apply_argv, opt_cdup, IO_WR))
6068                 return FALSE;
6070         if (!stage_diff_write(&io, diff_hdr, chunk) ||
6071             !stage_diff_write(&io, chunk, view->line + view->lines))
6072                 chunk = NULL;
6074         done_io(&io);
6075         run_io_bg(update_index_argv);
6077         return chunk ? TRUE : FALSE;
6080 static bool
6081 stage_update(struct view *view, struct line *line)
6083         struct line *chunk = NULL;
6085         if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
6086                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6088         if (chunk) {
6089                 if (!stage_apply_chunk(view, chunk, FALSE)) {
6090                         report("Failed to apply chunk");
6091                         return FALSE;
6092                 }
6094         } else if (!stage_status.status) {
6095                 view = VIEW(REQ_VIEW_STATUS);
6097                 for (line = view->line; line < view->line + view->lines; line++)
6098                         if (line->type == stage_line_type)
6099                                 break;
6101                 if (!status_update_files(view, line + 1)) {
6102                         report("Failed to update files");
6103                         return FALSE;
6104                 }
6106         } else if (!status_update_file(&stage_status, stage_line_type)) {
6107                 report("Failed to update file");
6108                 return FALSE;
6109         }
6111         return TRUE;
6114 static bool
6115 stage_revert(struct view *view, struct line *line)
6117         struct line *chunk = NULL;
6119         if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
6120                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6122         if (chunk) {
6123                 if (!prompt_yesno("Are you sure you want to revert changes?"))
6124                         return FALSE;
6126                 if (!stage_apply_chunk(view, chunk, TRUE)) {
6127                         report("Failed to revert chunk");
6128                         return FALSE;
6129                 }
6130                 return TRUE;
6132         } else {
6133                 return status_revert(stage_status.status ? &stage_status : NULL,
6134                                      stage_line_type, FALSE);
6135         }
6139 static void
6140 stage_next(struct view *view, struct line *line)
6142         int i;
6144         if (!stage_chunks) {
6145                 for (line = view->line; line < view->line + view->lines; line++) {
6146                         if (line->type != LINE_DIFF_CHUNK)
6147                                 continue;
6149                         if (!realloc_ints(&stage_chunk, stage_chunks, 1)) {
6150                                 report("Allocation failure");
6151                                 return;
6152                         }
6154                         stage_chunk[stage_chunks++] = line - view->line;
6155                 }
6156         }
6158         for (i = 0; i < stage_chunks; i++) {
6159                 if (stage_chunk[i] > view->lineno) {
6160                         do_scroll_view(view, stage_chunk[i] - view->lineno);
6161                         report("Chunk %d of %d", i + 1, stage_chunks);
6162                         return;
6163                 }
6164         }
6166         report("No next chunk found");
6169 static enum request
6170 stage_request(struct view *view, enum request request, struct line *line)
6172         switch (request) {
6173         case REQ_STATUS_UPDATE:
6174                 if (!stage_update(view, line))
6175                         return REQ_NONE;
6176                 break;
6178         case REQ_STATUS_REVERT:
6179                 if (!stage_revert(view, line))
6180                         return REQ_NONE;
6181                 break;
6183         case REQ_STAGE_NEXT:
6184                 if (stage_line_type == LINE_STAT_UNTRACKED) {
6185                         report("File is untracked; press %s to add",
6186                                get_key(KEYMAP_STAGE, REQ_STATUS_UPDATE));
6187                         return REQ_NONE;
6188                 }
6189                 stage_next(view, line);
6190                 return REQ_NONE;
6192         case REQ_EDIT:
6193                 if (!stage_status.new.name[0])
6194                         return request;
6195                 if (stage_status.status == 'D') {
6196                         report("File has been deleted.");
6197                         return REQ_NONE;
6198                 }
6200                 open_editor(stage_status.status != '?', stage_status.new.name);
6201                 break;
6203         case REQ_REFRESH:
6204                 /* Reload everything ... */
6205                 break;
6207         case REQ_VIEW_BLAME:
6208                 if (stage_status.new.name[0]) {
6209                         string_copy(opt_file, stage_status.new.name);
6210                         opt_ref[0] = 0;
6211                 }
6212                 return request;
6214         case REQ_ENTER:
6215                 return pager_request(view, request, line);
6217         default:
6218                 return request;
6219         }
6221         VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
6222         open_view(view, REQ_VIEW_STATUS, OPEN_REFRESH);
6224         /* Check whether the staged entry still exists, and close the
6225          * stage view if it doesn't. */
6226         if (!status_exists(&stage_status, stage_line_type)) {
6227                 status_restore(VIEW(REQ_VIEW_STATUS));
6228                 return REQ_VIEW_CLOSE;
6229         }
6231         if (stage_line_type == LINE_STAT_UNTRACKED) {
6232                 if (!suffixcmp(stage_status.new.name, -1, "/")) {
6233                         report("Cannot display a directory");
6234                         return REQ_NONE;
6235                 }
6237                 if (!prepare_update_file(view, stage_status.new.name)) {
6238                         report("Failed to open file: %s", strerror(errno));
6239                         return REQ_NONE;
6240                 }
6241         }
6242         open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
6244         return REQ_NONE;
6247 static struct view_ops stage_ops = {
6248         "line",
6249         NULL,
6250         NULL,
6251         pager_read,
6252         pager_draw,
6253         stage_request,
6254         pager_grep,
6255         pager_select,
6256 };
6259 /*
6260  * Revision graph
6261  */
6263 struct commit {
6264         char id[SIZEOF_REV];            /* SHA1 ID. */
6265         char title[128];                /* First line of the commit message. */
6266         const char *author;             /* Author of the commit. */
6267         time_t time;                    /* Date from the author ident. */
6268         struct ref_list *refs;          /* Repository references. */
6269         chtype graph[SIZEOF_REVGRAPH];  /* Ancestry chain graphics. */
6270         size_t graph_size;              /* The width of the graph array. */
6271         bool has_parents;               /* Rewritten --parents seen. */
6272 };
6274 /* Size of rev graph with no  "padding" columns */
6275 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
6277 struct rev_graph {
6278         struct rev_graph *prev, *next, *parents;
6279         char rev[SIZEOF_REVITEMS][SIZEOF_REV];
6280         size_t size;
6281         struct commit *commit;
6282         size_t pos;
6283         unsigned int boundary:1;
6284 };
6286 /* Parents of the commit being visualized. */
6287 static struct rev_graph graph_parents[4];
6289 /* The current stack of revisions on the graph. */
6290 static struct rev_graph graph_stacks[4] = {
6291         { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
6292         { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
6293         { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
6294         { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
6295 };
6297 static inline bool
6298 graph_parent_is_merge(struct rev_graph *graph)
6300         return graph->parents->size > 1;
6303 static inline void
6304 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
6306         struct commit *commit = graph->commit;
6308         if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
6309                 commit->graph[commit->graph_size++] = symbol;
6312 static void
6313 clear_rev_graph(struct rev_graph *graph)
6315         graph->boundary = 0;
6316         graph->size = graph->pos = 0;
6317         graph->commit = NULL;
6318         memset(graph->parents, 0, sizeof(*graph->parents));
6321 static void
6322 done_rev_graph(struct rev_graph *graph)
6324         if (graph_parent_is_merge(graph) &&
6325             graph->pos < graph->size - 1 &&
6326             graph->next->size == graph->size + graph->parents->size - 1) {
6327                 size_t i = graph->pos + graph->parents->size - 1;
6329                 graph->commit->graph_size = i * 2;
6330                 while (i < graph->next->size - 1) {
6331                         append_to_rev_graph(graph, ' ');
6332                         append_to_rev_graph(graph, '\\');
6333                         i++;
6334                 }
6335         }
6337         clear_rev_graph(graph);
6340 static void
6341 push_rev_graph(struct rev_graph *graph, const char *parent)
6343         int i;
6345         /* "Collapse" duplicate parents lines.
6346          *
6347          * FIXME: This needs to also update update the drawn graph but
6348          * for now it just serves as a method for pruning graph lines. */
6349         for (i = 0; i < graph->size; i++)
6350                 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
6351                         return;
6353         if (graph->size < SIZEOF_REVITEMS) {
6354                 string_copy_rev(graph->rev[graph->size++], parent);
6355         }
6358 static chtype
6359 get_rev_graph_symbol(struct rev_graph *graph)
6361         chtype symbol;
6363         if (graph->boundary)
6364                 symbol = REVGRAPH_BOUND;
6365         else if (graph->parents->size == 0)
6366                 symbol = REVGRAPH_INIT;
6367         else if (graph_parent_is_merge(graph))
6368                 symbol = REVGRAPH_MERGE;
6369         else if (graph->pos >= graph->size)
6370                 symbol = REVGRAPH_BRANCH;
6371         else
6372                 symbol = REVGRAPH_COMMIT;
6374         return symbol;
6377 static void
6378 draw_rev_graph(struct rev_graph *graph)
6380         struct rev_filler {
6381                 chtype separator, line;
6382         };
6383         enum { DEFAULT, RSHARP, RDIAG, LDIAG };
6384         static struct rev_filler fillers[] = {
6385                 { ' ',  '|' },
6386                 { '`',  '.' },
6387                 { '\'', ' ' },
6388                 { '/',  ' ' },
6389         };
6390         chtype symbol = get_rev_graph_symbol(graph);
6391         struct rev_filler *filler;
6392         size_t i;
6394         if (opt_line_graphics)
6395                 fillers[DEFAULT].line = line_graphics[LINE_GRAPHIC_VLINE];
6397         filler = &fillers[DEFAULT];
6399         for (i = 0; i < graph->pos; i++) {
6400                 append_to_rev_graph(graph, filler->line);
6401                 if (graph_parent_is_merge(graph->prev) &&
6402                     graph->prev->pos == i)
6403                         filler = &fillers[RSHARP];
6405                 append_to_rev_graph(graph, filler->separator);
6406         }
6408         /* Place the symbol for this revision. */
6409         append_to_rev_graph(graph, symbol);
6411         if (graph->prev->size > graph->size)
6412                 filler = &fillers[RDIAG];
6413         else
6414                 filler = &fillers[DEFAULT];
6416         i++;
6418         for (; i < graph->size; i++) {
6419                 append_to_rev_graph(graph, filler->separator);
6420                 append_to_rev_graph(graph, filler->line);
6421                 if (graph_parent_is_merge(graph->prev) &&
6422                     i < graph->prev->pos + graph->parents->size)
6423                         filler = &fillers[RSHARP];
6424                 if (graph->prev->size > graph->size)
6425                         filler = &fillers[LDIAG];
6426         }
6428         if (graph->prev->size > graph->size) {
6429                 append_to_rev_graph(graph, filler->separator);
6430                 if (filler->line != ' ')
6431                         append_to_rev_graph(graph, filler->line);
6432         }
6435 /* Prepare the next rev graph */
6436 static void
6437 prepare_rev_graph(struct rev_graph *graph)
6439         size_t i;
6441         /* First, traverse all lines of revisions up to the active one. */
6442         for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
6443                 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
6444                         break;
6446                 push_rev_graph(graph->next, graph->rev[graph->pos]);
6447         }
6449         /* Interleave the new revision parent(s). */
6450         for (i = 0; !graph->boundary && i < graph->parents->size; i++)
6451                 push_rev_graph(graph->next, graph->parents->rev[i]);
6453         /* Lastly, put any remaining revisions. */
6454         for (i = graph->pos + 1; i < graph->size; i++)
6455                 push_rev_graph(graph->next, graph->rev[i]);
6458 static void
6459 update_rev_graph(struct view *view, struct rev_graph *graph)
6461         /* If this is the finalizing update ... */
6462         if (graph->commit)
6463                 prepare_rev_graph(graph);
6465         /* Graph visualization needs a one rev look-ahead,
6466          * so the first update doesn't visualize anything. */
6467         if (!graph->prev->commit)
6468                 return;
6470         if (view->lines > 2)
6471                 view->line[view->lines - 3].dirty = 1;
6472         if (view->lines > 1)
6473                 view->line[view->lines - 2].dirty = 1;
6474         draw_rev_graph(graph->prev);
6475         done_rev_graph(graph->prev->prev);
6479 /*
6480  * Main view backend
6481  */
6483 static const char *main_argv[SIZEOF_ARG] = {
6484         "git", "log", "--no-color", "--pretty=raw", "--parents",
6485                       "--topo-order", "%(head)", NULL
6486 };
6488 static bool
6489 main_draw(struct view *view, struct line *line, unsigned int lineno)
6491         struct commit *commit = line->data;
6493         if (!commit->author)
6494                 return FALSE;
6496         if (opt_date && draw_date(view, &commit->time))
6497                 return TRUE;
6499         if (opt_author && draw_author(view, commit->author))
6500                 return TRUE;
6502         if (opt_rev_graph && commit->graph_size &&
6503             draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
6504                 return TRUE;
6506         if (opt_show_refs && commit->refs) {
6507                 size_t i;
6509                 for (i = 0; i < commit->refs->size; i++) {
6510                         struct ref *ref = commit->refs->refs[i];
6511                         enum line_type type;
6513                         if (ref->head)
6514                                 type = LINE_MAIN_HEAD;
6515                         else if (ref->ltag)
6516                                 type = LINE_MAIN_LOCAL_TAG;
6517                         else if (ref->tag)
6518                                 type = LINE_MAIN_TAG;
6519                         else if (ref->tracked)
6520                                 type = LINE_MAIN_TRACKED;
6521                         else if (ref->remote)
6522                                 type = LINE_MAIN_REMOTE;
6523                         else
6524                                 type = LINE_MAIN_REF;
6526                         if (draw_text(view, type, "[", TRUE) ||
6527                             draw_text(view, type, ref->name, TRUE) ||
6528                             draw_text(view, type, "]", TRUE))
6529                                 return TRUE;
6531                         if (draw_text(view, LINE_DEFAULT, " ", TRUE))
6532                                 return TRUE;
6533                 }
6534         }
6536         draw_text(view, LINE_DEFAULT, commit->title, TRUE);
6537         return TRUE;
6540 /* Reads git log --pretty=raw output and parses it into the commit struct. */
6541 static bool
6542 main_read(struct view *view, char *line)
6544         static struct rev_graph *graph = graph_stacks;
6545         enum line_type type;
6546         struct commit *commit;
6548         if (!line) {
6549                 int i;
6551                 if (!view->lines && !view->parent)
6552                         die("No revisions match the given arguments.");
6553                 if (view->lines > 0) {
6554                         commit = view->line[view->lines - 1].data;
6555                         view->line[view->lines - 1].dirty = 1;
6556                         if (!commit->author) {
6557                                 view->lines--;
6558                                 free(commit);
6559                                 graph->commit = NULL;
6560                         }
6561                 }
6562                 update_rev_graph(view, graph);
6564                 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
6565                         clear_rev_graph(&graph_stacks[i]);
6566                 return TRUE;
6567         }
6569         type = get_line_type(line);
6570         if (type == LINE_COMMIT) {
6571                 commit = calloc(1, sizeof(struct commit));
6572                 if (!commit)
6573                         return FALSE;
6575                 line += STRING_SIZE("commit ");
6576                 if (*line == '-') {
6577                         graph->boundary = 1;
6578                         line++;
6579                 }
6581                 string_copy_rev(commit->id, line);
6582                 commit->refs = get_ref_list(commit->id);
6583                 graph->commit = commit;
6584                 add_line_data(view, commit, LINE_MAIN_COMMIT);
6586                 while ((line = strchr(line, ' '))) {
6587                         line++;
6588                         push_rev_graph(graph->parents, line);
6589                         commit->has_parents = TRUE;
6590                 }
6591                 return TRUE;
6592         }
6594         if (!view->lines)
6595                 return TRUE;
6596         commit = view->line[view->lines - 1].data;
6598         switch (type) {
6599         case LINE_PARENT:
6600                 if (commit->has_parents)
6601                         break;
6602                 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
6603                 break;
6605         case LINE_AUTHOR:
6606                 parse_author_line(line + STRING_SIZE("author "),
6607                                   &commit->author, &commit->time);
6608                 update_rev_graph(view, graph);
6609                 graph = graph->next;
6610                 break;
6612         default:
6613                 /* Fill in the commit title if it has not already been set. */
6614                 if (commit->title[0])
6615                         break;
6617                 /* Require titles to start with a non-space character at the
6618                  * offset used by git log. */
6619                 if (strncmp(line, "    ", 4))
6620                         break;
6621                 line += 4;
6622                 /* Well, if the title starts with a whitespace character,
6623                  * try to be forgiving.  Otherwise we end up with no title. */
6624                 while (isspace(*line))
6625                         line++;
6626                 if (*line == '\0')
6627                         break;
6628                 /* FIXME: More graceful handling of titles; append "..." to
6629                  * shortened titles, etc. */
6631                 string_expand(commit->title, sizeof(commit->title), line, 1);
6632                 view->line[view->lines - 1].dirty = 1;
6633         }
6635         return TRUE;
6638 static enum request
6639 main_request(struct view *view, enum request request, struct line *line)
6641         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
6643         switch (request) {
6644         case REQ_ENTER:
6645                 open_view(view, REQ_VIEW_DIFF, flags);
6646                 break;
6647         case REQ_REFRESH:
6648                 load_refs();
6649                 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
6650                 break;
6651         default:
6652                 return request;
6653         }
6655         return REQ_NONE;
6658 static bool
6659 grep_refs(struct ref_list *list, regex_t *regex)
6661         regmatch_t pmatch;
6662         size_t i;
6664         if (!opt_show_refs || !list)
6665                 return FALSE;
6667         for (i = 0; i < list->size; i++) {
6668                 if (regexec(regex, list->refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
6669                         return TRUE;
6670         }
6672         return FALSE;
6675 static bool
6676 main_grep(struct view *view, struct line *line)
6678         struct commit *commit = line->data;
6679         const char *text[] = {
6680                 commit->title,
6681                 opt_author ? commit->author : "",
6682                 opt_date ? mkdate(&commit->time) : "",
6683                 NULL
6684         };
6686         return grep_text(view, text) || grep_refs(commit->refs, view->regex);
6689 static void
6690 main_select(struct view *view, struct line *line)
6692         struct commit *commit = line->data;
6694         string_copy_rev(view->ref, commit->id);
6695         string_copy_rev(ref_commit, view->ref);
6698 static struct view_ops main_ops = {
6699         "commit",
6700         main_argv,
6701         NULL,
6702         main_read,
6703         main_draw,
6704         main_request,
6705         main_grep,
6706         main_select,
6707 };
6710 /*
6711  * Unicode / UTF-8 handling
6712  *
6713  * NOTE: Much of the following code for dealing with Unicode is derived from
6714  * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
6715  * src/intl/charset.c from the UTF-8 branch commit elinks-0.11.0-g31f2c28.
6716  */
6718 static inline int
6719 unicode_width(unsigned long c)
6721         if (c >= 0x1100 &&
6722            (c <= 0x115f                         /* Hangul Jamo */
6723             || c == 0x2329
6724             || c == 0x232a
6725             || (c >= 0x2e80  && c <= 0xa4cf && c != 0x303f)
6726                                                 /* CJK ... Yi */
6727             || (c >= 0xac00  && c <= 0xd7a3)    /* Hangul Syllables */
6728             || (c >= 0xf900  && c <= 0xfaff)    /* CJK Compatibility Ideographs */
6729             || (c >= 0xfe30  && c <= 0xfe6f)    /* CJK Compatibility Forms */
6730             || (c >= 0xff00  && c <= 0xff60)    /* Fullwidth Forms */
6731             || (c >= 0xffe0  && c <= 0xffe6)
6732             || (c >= 0x20000 && c <= 0x2fffd)
6733             || (c >= 0x30000 && c <= 0x3fffd)))
6734                 return 2;
6736         if (c == '\t')
6737                 return opt_tab_size;
6739         return 1;
6742 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
6743  * Illegal bytes are set one. */
6744 static const unsigned char utf8_bytes[256] = {
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         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,
6751         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,
6752         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,
6753 };
6755 /* Decode UTF-8 multi-byte representation into a Unicode character. */
6756 static inline unsigned long
6757 utf8_to_unicode(const char *string, size_t length)
6759         unsigned long unicode;
6761         switch (length) {
6762         case 1:
6763                 unicode  =   string[0];
6764                 break;
6765         case 2:
6766                 unicode  =  (string[0] & 0x1f) << 6;
6767                 unicode +=  (string[1] & 0x3f);
6768                 break;
6769         case 3:
6770                 unicode  =  (string[0] & 0x0f) << 12;
6771                 unicode += ((string[1] & 0x3f) << 6);
6772                 unicode +=  (string[2] & 0x3f);
6773                 break;
6774         case 4:
6775                 unicode  =  (string[0] & 0x0f) << 18;
6776                 unicode += ((string[1] & 0x3f) << 12);
6777                 unicode += ((string[2] & 0x3f) << 6);
6778                 unicode +=  (string[3] & 0x3f);
6779                 break;
6780         case 5:
6781                 unicode  =  (string[0] & 0x0f) << 24;
6782                 unicode += ((string[1] & 0x3f) << 18);
6783                 unicode += ((string[2] & 0x3f) << 12);
6784                 unicode += ((string[3] & 0x3f) << 6);
6785                 unicode +=  (string[4] & 0x3f);
6786                 break;
6787         case 6:
6788                 unicode  =  (string[0] & 0x01) << 30;
6789                 unicode += ((string[1] & 0x3f) << 24);
6790                 unicode += ((string[2] & 0x3f) << 18);
6791                 unicode += ((string[3] & 0x3f) << 12);
6792                 unicode += ((string[4] & 0x3f) << 6);
6793                 unicode +=  (string[5] & 0x3f);
6794                 break;
6795         default:
6796                 die("Invalid Unicode length");
6797         }
6799         /* Invalid characters could return the special 0xfffd value but NUL
6800          * should be just as good. */
6801         return unicode > 0xffff ? 0 : unicode;
6804 /* Calculates how much of string can be shown within the given maximum width
6805  * and sets trimmed parameter to non-zero value if all of string could not be
6806  * shown. If the reserve flag is TRUE, it will reserve at least one
6807  * trailing character, which can be useful when drawing a delimiter.
6808  *
6809  * Returns the number of bytes to output from string to satisfy max_width. */
6810 static size_t
6811 utf8_length(const char **start, size_t skip, int *width, size_t max_width, int *trimmed, bool reserve)
6813         const char *string = *start;
6814         const char *end = strchr(string, '\0');
6815         unsigned char last_bytes = 0;
6816         size_t last_ucwidth = 0;
6818         *width = 0;
6819         *trimmed = 0;
6821         while (string < end) {
6822                 int c = *(unsigned char *) string;
6823                 unsigned char bytes = utf8_bytes[c];
6824                 size_t ucwidth;
6825                 unsigned long unicode;
6827                 if (string + bytes > end)
6828                         break;
6830                 /* Change representation to figure out whether
6831                  * it is a single- or double-width character. */
6833                 unicode = utf8_to_unicode(string, bytes);
6834                 /* FIXME: Graceful handling of invalid Unicode character. */
6835                 if (!unicode)
6836                         break;
6838                 ucwidth = unicode_width(unicode);
6839                 if (skip > 0) {
6840                         skip -= ucwidth <= skip ? ucwidth : skip;
6841                         *start += bytes;
6842                 }
6843                 *width  += ucwidth;
6844                 if (*width > max_width) {
6845                         *trimmed = 1;
6846                         *width -= ucwidth;
6847                         if (reserve && *width == max_width) {
6848                                 string -= last_bytes;
6849                                 *width -= last_ucwidth;
6850                         }
6851                         break;
6852                 }
6854                 string  += bytes;
6855                 last_bytes = ucwidth ? bytes : 0;
6856                 last_ucwidth = ucwidth;
6857         }
6859         return string - *start;
6863 /*
6864  * Status management
6865  */
6867 /* Whether or not the curses interface has been initialized. */
6868 static bool cursed = FALSE;
6870 /* Terminal hacks and workarounds. */
6871 static bool use_scroll_redrawwin;
6872 static bool use_scroll_status_wclear;
6874 /* The status window is used for polling keystrokes. */
6875 static WINDOW *status_win;
6877 /* Reading from the prompt? */
6878 static bool input_mode = FALSE;
6880 static bool status_empty = FALSE;
6882 /* Update status and title window. */
6883 static void
6884 report(const char *msg, ...)
6886         struct view *view = display[current_view];
6888         if (input_mode)
6889                 return;
6891         if (!view) {
6892                 char buf[SIZEOF_STR];
6893                 va_list args;
6895                 va_start(args, msg);
6896                 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
6897                         buf[sizeof(buf) - 1] = 0;
6898                         buf[sizeof(buf) - 2] = '.';
6899                         buf[sizeof(buf) - 3] = '.';
6900                         buf[sizeof(buf) - 4] = '.';
6901                 }
6902                 va_end(args);
6903                 die("%s", buf);
6904         }
6906         if (!status_empty || *msg) {
6907                 va_list args;
6909                 va_start(args, msg);
6911                 wmove(status_win, 0, 0);
6912                 if (view->has_scrolled && use_scroll_status_wclear)
6913                         wclear(status_win);
6914                 if (*msg) {
6915                         vwprintw(status_win, msg, args);
6916                         status_empty = FALSE;
6917                 } else {
6918                         status_empty = TRUE;
6919                 }
6920                 wclrtoeol(status_win);
6921                 wnoutrefresh(status_win);
6923                 va_end(args);
6924         }
6926         update_view_title(view);
6929 /* Controls when nodelay should be in effect when polling user input. */
6930 static void
6931 set_nonblocking_input(bool loading)
6933         static unsigned int loading_views;
6935         if ((loading == FALSE && loading_views-- == 1) ||
6936             (loading == TRUE  && loading_views++ == 0))
6937                 nodelay(status_win, loading);
6940 static void
6941 init_display(void)
6943         const char *term;
6944         int x, y;
6946         /* Initialize the curses library */
6947         if (isatty(STDIN_FILENO)) {
6948                 cursed = !!initscr();
6949                 opt_tty = stdin;
6950         } else {
6951                 /* Leave stdin and stdout alone when acting as a pager. */
6952                 opt_tty = fopen("/dev/tty", "r+");
6953                 if (!opt_tty)
6954                         die("Failed to open /dev/tty");
6955                 cursed = !!newterm(NULL, opt_tty, opt_tty);
6956         }
6958         if (!cursed)
6959                 die("Failed to initialize curses");
6961         nonl();         /* Disable conversion and detect newlines from input. */
6962         cbreak();       /* Take input chars one at a time, no wait for \n */
6963         noecho();       /* Don't echo input */
6964         leaveok(stdscr, FALSE);
6966         if (has_colors())
6967                 init_colors();
6969         getmaxyx(stdscr, y, x);
6970         status_win = newwin(1, 0, y - 1, 0);
6971         if (!status_win)
6972                 die("Failed to create status window");
6974         /* Enable keyboard mapping */
6975         keypad(status_win, TRUE);
6976         wbkgdset(status_win, get_line_attr(LINE_STATUS));
6978         TABSIZE = opt_tab_size;
6979         if (opt_line_graphics) {
6980                 line_graphics[LINE_GRAPHIC_VLINE] = ACS_VLINE;
6981         }
6983         term = getenv("XTERM_VERSION") ? NULL : getenv("COLORTERM");
6984         if (term && !strcmp(term, "gnome-terminal")) {
6985                 /* In the gnome-terminal-emulator, the message from
6986                  * scrolling up one line when impossible followed by
6987                  * scrolling down one line causes corruption of the
6988                  * status line. This is fixed by calling wclear. */
6989                 use_scroll_status_wclear = TRUE;
6990                 use_scroll_redrawwin = FALSE;
6992         } else if (term && !strcmp(term, "xrvt-xpm")) {
6993                 /* No problems with full optimizations in xrvt-(unicode)
6994                  * and aterm. */
6995                 use_scroll_status_wclear = use_scroll_redrawwin = FALSE;
6997         } else {
6998                 /* When scrolling in (u)xterm the last line in the
6999                  * scrolling direction will update slowly. */
7000                 use_scroll_redrawwin = TRUE;
7001                 use_scroll_status_wclear = FALSE;
7002         }
7005 static int
7006 get_input(int prompt_position)
7008         struct view *view;
7009         int i, key, cursor_y, cursor_x;
7011         if (prompt_position)
7012                 input_mode = TRUE;
7014         while (TRUE) {
7015                 foreach_view (view, i) {
7016                         update_view(view);
7017                         if (view_is_displayed(view) && view->has_scrolled &&
7018                             use_scroll_redrawwin)
7019                                 redrawwin(view->win);
7020                         view->has_scrolled = FALSE;
7021                 }
7023                 /* Update the cursor position. */
7024                 if (prompt_position) {
7025                         getbegyx(status_win, cursor_y, cursor_x);
7026                         cursor_x = prompt_position;
7027                 } else {
7028                         view = display[current_view];
7029                         getbegyx(view->win, cursor_y, cursor_x);
7030                         cursor_x = view->width - 1;
7031                         cursor_y += view->lineno - view->offset;
7032                 }
7033                 setsyx(cursor_y, cursor_x);
7035                 /* Refresh, accept single keystroke of input */
7036                 doupdate();
7037                 key = wgetch(status_win);
7039                 /* wgetch() with nodelay() enabled returns ERR when
7040                  * there's no input. */
7041                 if (key == ERR) {
7043                 } else if (key == KEY_RESIZE) {
7044                         int height, width;
7046                         getmaxyx(stdscr, height, width);
7048                         wresize(status_win, 1, width);
7049                         mvwin(status_win, height - 1, 0);
7050                         wnoutrefresh(status_win);
7051                         resize_display();
7052                         redraw_display(TRUE);
7054                 } else {
7055                         input_mode = FALSE;
7056                         return key;
7057                 }
7058         }
7061 static char *
7062 prompt_input(const char *prompt, input_handler handler, void *data)
7064         enum input_status status = INPUT_OK;
7065         static char buf[SIZEOF_STR];
7066         size_t pos = 0;
7068         buf[pos] = 0;
7070         while (status == INPUT_OK || status == INPUT_SKIP) {
7071                 int key;
7073                 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
7074                 wclrtoeol(status_win);
7076                 key = get_input(pos + 1);
7077                 switch (key) {
7078                 case KEY_RETURN:
7079                 case KEY_ENTER:
7080                 case '\n':
7081                         status = pos ? INPUT_STOP : INPUT_CANCEL;
7082                         break;
7084                 case KEY_BACKSPACE:
7085                         if (pos > 0)
7086                                 buf[--pos] = 0;
7087                         else
7088                                 status = INPUT_CANCEL;
7089                         break;
7091                 case KEY_ESC:
7092                         status = INPUT_CANCEL;
7093                         break;
7095                 default:
7096                         if (pos >= sizeof(buf)) {
7097                                 report("Input string too long");
7098                                 return NULL;
7099                         }
7101                         status = handler(data, buf, key);
7102                         if (status == INPUT_OK)
7103                                 buf[pos++] = (char) key;
7104                 }
7105         }
7107         /* Clear the status window */
7108         status_empty = FALSE;
7109         report("");
7111         if (status == INPUT_CANCEL)
7112                 return NULL;
7114         buf[pos++] = 0;
7116         return buf;
7119 static enum input_status
7120 prompt_yesno_handler(void *data, char *buf, int c)
7122         if (c == 'y' || c == 'Y')
7123                 return INPUT_STOP;
7124         if (c == 'n' || c == 'N')
7125                 return INPUT_CANCEL;
7126         return INPUT_SKIP;
7129 static bool
7130 prompt_yesno(const char *prompt)
7132         char prompt2[SIZEOF_STR];
7134         if (!string_format(prompt2, "%s [Yy/Nn]", prompt))
7135                 return FALSE;
7137         return !!prompt_input(prompt2, prompt_yesno_handler, NULL);
7140 static enum input_status
7141 read_prompt_handler(void *data, char *buf, int c)
7143         return isprint(c) ? INPUT_OK : INPUT_SKIP;
7146 static char *
7147 read_prompt(const char *prompt)
7149         return prompt_input(prompt, read_prompt_handler, NULL);
7152 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected)
7154         enum input_status status = INPUT_OK;
7155         int size = 0;
7157         while (items[size].text)
7158                 size++;
7160         while (status == INPUT_OK) {
7161                 const struct menu_item *item = &items[*selected];
7162                 int key;
7163                 int i;
7165                 mvwprintw(status_win, 0, 0, "%s (%d of %d) ",
7166                           prompt, *selected + 1, size);
7167                 if (item->hotkey)
7168                         wprintw(status_win, "[%c] ", (char) item->hotkey);
7169                 wprintw(status_win, "%s", item->text);
7170                 wclrtoeol(status_win);
7172                 key = get_input(COLS - 1);
7173                 switch (key) {
7174                 case KEY_RETURN:
7175                 case KEY_ENTER:
7176                 case '\n':
7177                         status = INPUT_STOP;
7178                         break;
7180                 case KEY_LEFT:
7181                 case KEY_UP:
7182                         *selected = *selected - 1;
7183                         if (*selected < 0)
7184                                 *selected = size - 1;
7185                         break;
7187                 case KEY_RIGHT:
7188                 case KEY_DOWN:
7189                         *selected = (*selected + 1) % size;
7190                         break;
7192                 case KEY_ESC:
7193                         status = INPUT_CANCEL;
7194                         break;
7196                 default:
7197                         for (i = 0; items[i].text; i++)
7198                                 if (items[i].hotkey == key) {
7199                                         *selected = i;
7200                                         status = INPUT_STOP;
7201                                         break;
7202                                 }
7203                 }
7204         }
7206         /* Clear the status window */
7207         status_empty = FALSE;
7208         report("");
7210         return status != INPUT_CANCEL;
7213 /*
7214  * Repository properties
7215  */
7217 static struct ref **refs = NULL;
7218 static size_t refs_size = 0;
7220 static struct ref_list **ref_lists = NULL;
7221 static size_t ref_lists_size = 0;
7223 DEFINE_ALLOCATOR(realloc_refs, struct ref *, 256)
7224 DEFINE_ALLOCATOR(realloc_refs_list, struct ref *, 8)
7225 DEFINE_ALLOCATOR(realloc_ref_lists, struct ref_list *, 8)
7227 static int
7228 compare_refs(const void *ref1_, const void *ref2_)
7230         const struct ref *ref1 = *(const struct ref **)ref1_;
7231         const struct ref *ref2 = *(const struct ref **)ref2_;
7233         if (ref1->tag != ref2->tag)
7234                 return ref2->tag - ref1->tag;
7235         if (ref1->ltag != ref2->ltag)
7236                 return ref2->ltag - ref2->ltag;
7237         if (ref1->head != ref2->head)
7238                 return ref2->head - ref1->head;
7239         if (ref1->tracked != ref2->tracked)
7240                 return ref2->tracked - ref1->tracked;
7241         if (ref1->remote != ref2->remote)
7242                 return ref2->remote - ref1->remote;
7243         return strcmp(ref1->name, ref2->name);
7246 static void
7247 foreach_ref(bool (*visitor)(void *data, const struct ref *ref), void *data)
7249         size_t i;
7251         for (i = 0; i < refs_size; i++)
7252                 if (!visitor(data, refs[i]))
7253                         break;
7256 static struct ref_list *
7257 get_ref_list(const char *id)
7259         struct ref_list *list;
7260         size_t i;
7262         for (i = 0; i < ref_lists_size; i++)
7263                 if (!strcmp(id, ref_lists[i]->id))
7264                         return ref_lists[i];
7266         if (!realloc_ref_lists(&ref_lists, ref_lists_size, 1))
7267                 return NULL;
7268         list = calloc(1, sizeof(*list));
7269         if (!list)
7270                 return NULL;
7272         for (i = 0; i < refs_size; i++) {
7273                 if (!strcmp(id, refs[i]->id) &&
7274                     realloc_refs_list(&list->refs, list->size, 1))
7275                         list->refs[list->size++] = refs[i];
7276         }
7278         if (!list->refs) {
7279                 free(list);
7280                 return NULL;
7281         }
7283         qsort(list->refs, list->size, sizeof(*list->refs), compare_refs);
7284         ref_lists[ref_lists_size++] = list;
7285         return list;
7288 static int
7289 read_ref(char *id, size_t idlen, char *name, size_t namelen)
7291         struct ref *ref = NULL;
7292         bool tag = FALSE;
7293         bool ltag = FALSE;
7294         bool remote = FALSE;
7295         bool tracked = FALSE;
7296         bool head = FALSE;
7297         int from = 0, to = refs_size - 1;
7299         if (!prefixcmp(name, "refs/tags/")) {
7300                 if (!suffixcmp(name, namelen, "^{}")) {
7301                         namelen -= 3;
7302                         name[namelen] = 0;
7303                 } else {
7304                         ltag = TRUE;
7305                 }
7307                 tag = TRUE;
7308                 namelen -= STRING_SIZE("refs/tags/");
7309                 name    += STRING_SIZE("refs/tags/");
7311         } else if (!prefixcmp(name, "refs/remotes/")) {
7312                 remote = TRUE;
7313                 namelen -= STRING_SIZE("refs/remotes/");
7314                 name    += STRING_SIZE("refs/remotes/");
7315                 tracked  = !strcmp(opt_remote, name);
7317         } else if (!prefixcmp(name, "refs/heads/")) {
7318                 namelen -= STRING_SIZE("refs/heads/");
7319                 name    += STRING_SIZE("refs/heads/");
7320                 head     = !strncmp(opt_head, name, namelen);
7322         } else if (!strcmp(name, "HEAD")) {
7323                 string_ncopy(opt_head_rev, id, idlen);
7324                 return OK;
7325         }
7327         /* If we are reloading or it's an annotated tag, replace the
7328          * previous SHA1 with the resolved commit id; relies on the fact
7329          * git-ls-remote lists the commit id of an annotated tag right
7330          * before the commit id it points to. */
7331         while (from <= to) {
7332                 size_t pos = (to + from) / 2;
7333                 int cmp = strcmp(name, refs[pos]->name);
7335                 if (!cmp) {
7336                         ref = refs[pos];
7337                         break;
7338                 }
7340                 if (cmp < 0)
7341                         to = pos - 1;
7342                 else
7343                         from = pos + 1;
7344         }
7346         if (!ref) {
7347                 if (!realloc_refs(&refs, refs_size, 1))
7348                         return ERR;
7349                 ref = calloc(1, sizeof(*ref) + namelen);
7350                 if (!ref)
7351                         return ERR;
7352                 memmove(refs + from + 1, refs + from,
7353                         (refs_size - from) * sizeof(*refs));
7354                 refs[from] = ref;
7355                 strncpy(ref->name, name, namelen);
7356                 refs_size++;
7357         }
7359         ref->head = head;
7360         ref->tag = tag;
7361         ref->ltag = ltag;
7362         ref->remote = remote;
7363         ref->tracked = tracked;
7364         string_copy_rev(ref->id, id);
7366         return OK;
7369 static int
7370 load_refs(void)
7372         const char *head_argv[] = {
7373                 "git", "symbolic-ref", "HEAD", NULL
7374         };
7375         static const char *ls_remote_argv[SIZEOF_ARG] = {
7376                 "git", "ls-remote", opt_git_dir, NULL
7377         };
7378         static bool init = FALSE;
7379         size_t i;
7381         if (!init) {
7382                 argv_from_env(ls_remote_argv, "TIG_LS_REMOTE");
7383                 init = TRUE;
7384         }
7386         if (!*opt_git_dir)
7387                 return OK;
7389         if (run_io_buf(head_argv, opt_head, sizeof(opt_head)) &&
7390             !prefixcmp(opt_head, "refs/heads/")) {
7391                 char *offset = opt_head + STRING_SIZE("refs/heads/");
7393                 memmove(opt_head, offset, strlen(offset) + 1);
7394         }
7396         for (i = 0; i < refs_size; i++)
7397                 refs[i]->id[0] = 0;
7399         if (run_io_load(ls_remote_argv, "\t", read_ref) == ERR)
7400                 return ERR;
7402         /* Update the ref lists to reflect changes. */
7403         for (i = 0; i < ref_lists_size; i++) {
7404                 struct ref_list *list = ref_lists[i];
7405                 size_t old, new;
7407                 for (old = new = 0; old < list->size; old++)
7408                         if (!strcmp(list->id, list->refs[old]->id))
7409                                 list->refs[new++] = list->refs[old];
7410                 list->size = new;
7411         }
7413         return OK;
7416 static void
7417 set_remote_branch(const char *name, const char *value, size_t valuelen)
7419         if (!strcmp(name, ".remote")) {
7420                 string_ncopy(opt_remote, value, valuelen);
7422         } else if (*opt_remote && !strcmp(name, ".merge")) {
7423                 size_t from = strlen(opt_remote);
7425                 if (!prefixcmp(value, "refs/heads/"))
7426                         value += STRING_SIZE("refs/heads/");
7428                 if (!string_format_from(opt_remote, &from, "/%s", value))
7429                         opt_remote[0] = 0;
7430         }
7433 static void
7434 set_repo_config_option(char *name, char *value, int (*cmd)(int, const char **))
7436         const char *argv[SIZEOF_ARG] = { name, "=" };
7437         int argc = 1 + (cmd == option_set_command);
7438         int error = ERR;
7440         if (!argv_from_string(argv, &argc, value))
7441                 config_msg = "Too many option arguments";
7442         else
7443                 error = cmd(argc, argv);
7445         if (error == ERR)
7446                 warn("Option 'tig.%s': %s", name, config_msg);
7449 static bool
7450 set_environment_variable(const char *name, const char *value)
7452         size_t len = strlen(name) + 1 + strlen(value) + 1;
7453         char *env = malloc(len);
7455         if (env &&
7456             string_nformat(env, len, NULL, "%s=%s", name, value) &&
7457             putenv(env) == 0)
7458                 return TRUE;
7459         free(env);
7460         return FALSE;
7463 static void
7464 set_work_tree(const char *value)
7466         char cwd[SIZEOF_STR];
7468         if (!getcwd(cwd, sizeof(cwd)))
7469                 die("Failed to get cwd path: %s", strerror(errno));
7470         if (chdir(opt_git_dir) < 0)
7471                 die("Failed to chdir(%s): %s", strerror(errno));
7472         if (!getcwd(opt_git_dir, sizeof(opt_git_dir)))
7473                 die("Failed to get git path: %s", strerror(errno));
7474         if (chdir(cwd) < 0)
7475                 die("Failed to chdir(%s): %s", cwd, strerror(errno));
7476         if (chdir(value) < 0)
7477                 die("Failed to chdir(%s): %s", value, strerror(errno));
7478         if (!getcwd(cwd, sizeof(cwd)))
7479                 die("Failed to get cwd path: %s", strerror(errno));
7480         if (!set_environment_variable("GIT_WORK_TREE", cwd))
7481                 die("Failed to set GIT_WORK_TREE to '%s'", cwd);
7482         if (!set_environment_variable("GIT_DIR", opt_git_dir))
7483                 die("Failed to set GIT_DIR to '%s'", opt_git_dir);
7484         opt_is_inside_work_tree = TRUE;
7487 static int
7488 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
7490         if (!strcmp(name, "i18n.commitencoding"))
7491                 string_ncopy(opt_encoding, value, valuelen);
7493         else if (!strcmp(name, "core.editor"))
7494                 string_ncopy(opt_editor, value, valuelen);
7496         else if (!strcmp(name, "core.worktree"))
7497                 set_work_tree(value);
7499         else if (!prefixcmp(name, "tig.color."))
7500                 set_repo_config_option(name + 10, value, option_color_command);
7502         else if (!prefixcmp(name, "tig.bind."))
7503                 set_repo_config_option(name + 9, value, option_bind_command);
7505         else if (!prefixcmp(name, "tig."))
7506                 set_repo_config_option(name + 4, value, option_set_command);
7508         else if (*opt_head && !prefixcmp(name, "branch.") &&
7509                  !strncmp(name + 7, opt_head, strlen(opt_head)))
7510                 set_remote_branch(name + 7 + strlen(opt_head), value, valuelen);
7512         return OK;
7515 static int
7516 load_git_config(void)
7518         const char *config_list_argv[] = { "git", "config", "--list", NULL };
7520         return run_io_load(config_list_argv, "=", read_repo_config_option);
7523 static int
7524 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
7526         if (!opt_git_dir[0]) {
7527                 string_ncopy(opt_git_dir, name, namelen);
7529         } else if (opt_is_inside_work_tree == -1) {
7530                 /* This can be 3 different values depending on the
7531                  * version of git being used. If git-rev-parse does not
7532                  * understand --is-inside-work-tree it will simply echo
7533                  * the option else either "true" or "false" is printed.
7534                  * Default to true for the unknown case. */
7535                 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
7537         } else if (*name == '.') {
7538                 string_ncopy(opt_cdup, name, namelen);
7540         } else {
7541                 string_ncopy(opt_prefix, name, namelen);
7542         }
7544         return OK;
7547 static int
7548 load_repo_info(void)
7550         const char *rev_parse_argv[] = {
7551                 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
7552                         "--show-cdup", "--show-prefix", NULL
7553         };
7555         return run_io_load(rev_parse_argv, "=", read_repo_info);
7559 /*
7560  * Main
7561  */
7563 static const char usage[] =
7564 "tig " TIG_VERSION " (" __DATE__ ")\n"
7565 "\n"
7566 "Usage: tig        [options] [revs] [--] [paths]\n"
7567 "   or: tig show   [options] [revs] [--] [paths]\n"
7568 "   or: tig blame  [rev] path\n"
7569 "   or: tig status\n"
7570 "   or: tig <      [git command output]\n"
7571 "\n"
7572 "Options:\n"
7573 "  -v, --version   Show version and exit\n"
7574 "  -h, --help      Show help message and exit";
7576 static void __NORETURN
7577 quit(int sig)
7579         /* XXX: Restore tty modes and let the OS cleanup the rest! */
7580         if (cursed)
7581                 endwin();
7582         exit(0);
7585 static void __NORETURN
7586 die(const char *err, ...)
7588         va_list args;
7590         endwin();
7592         va_start(args, err);
7593         fputs("tig: ", stderr);
7594         vfprintf(stderr, err, args);
7595         fputs("\n", stderr);
7596         va_end(args);
7598         exit(1);
7601 static void
7602 warn(const char *msg, ...)
7604         va_list args;
7606         va_start(args, msg);
7607         fputs("tig warning: ", stderr);
7608         vfprintf(stderr, msg, args);
7609         fputs("\n", stderr);
7610         va_end(args);
7613 static enum request
7614 parse_options(int argc, const char *argv[])
7616         enum request request = REQ_VIEW_MAIN;
7617         const char *subcommand;
7618         bool seen_dashdash = FALSE;
7619         /* XXX: This is vulnerable to the user overriding options
7620          * required for the main view parser. */
7621         const char *custom_argv[SIZEOF_ARG] = {
7622                 "git", "log", "--no-color", "--pretty=raw", "--parents",
7623                         "--topo-order", NULL
7624         };
7625         int i, j = 6;
7627         if (!isatty(STDIN_FILENO)) {
7628                 io_open(&VIEW(REQ_VIEW_PAGER)->io, "");
7629                 return REQ_VIEW_PAGER;
7630         }
7632         if (argc <= 1)
7633                 return REQ_NONE;
7635         subcommand = argv[1];
7636         if (!strcmp(subcommand, "status")) {
7637                 if (argc > 2)
7638                         warn("ignoring arguments after `%s'", subcommand);
7639                 return REQ_VIEW_STATUS;
7641         } else if (!strcmp(subcommand, "blame")) {
7642                 if (argc <= 2 || argc > 4)
7643                         die("invalid number of options to blame\n\n%s", usage);
7645                 i = 2;
7646                 if (argc == 4) {
7647                         string_ncopy(opt_ref, argv[i], strlen(argv[i]));
7648                         i++;
7649                 }
7651                 string_ncopy(opt_file, argv[i], strlen(argv[i]));
7652                 return REQ_VIEW_BLAME;
7654         } else if (!strcmp(subcommand, "show")) {
7655                 request = REQ_VIEW_DIFF;
7657         } else {
7658                 subcommand = NULL;
7659         }
7661         if (subcommand) {
7662                 custom_argv[1] = subcommand;
7663                 j = 2;
7664         }
7666         for (i = 1 + !!subcommand; i < argc; i++) {
7667                 const char *opt = argv[i];
7669                 if (seen_dashdash || !strcmp(opt, "--")) {
7670                         seen_dashdash = TRUE;
7672                 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
7673                         printf("tig version %s\n", TIG_VERSION);
7674                         quit(0);
7676                 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
7677                         printf("%s\n", usage);
7678                         quit(0);
7679                 }
7681                 custom_argv[j++] = opt;
7682                 if (j >= ARRAY_SIZE(custom_argv))
7683                         die("command too long");
7684         }
7686         if (!prepare_update(VIEW(request), custom_argv, NULL, FORMAT_NONE))
7687                 die("Failed to format arguments");
7689         return request;
7692 int
7693 main(int argc, const char *argv[])
7695         enum request request = parse_options(argc, argv);
7696         struct view *view;
7697         size_t i;
7699         signal(SIGINT, quit);
7700         signal(SIGPIPE, SIG_IGN);
7702         if (setlocale(LC_ALL, "")) {
7703                 char *codeset = nl_langinfo(CODESET);
7705                 string_ncopy(opt_codeset, codeset, strlen(codeset));
7706         }
7708         if (load_repo_info() == ERR)
7709                 die("Failed to load repo info.");
7711         if (load_options() == ERR)
7712                 die("Failed to load user config.");
7714         if (load_git_config() == ERR)
7715                 die("Failed to load repo config.");
7717         /* Require a git repository unless when running in pager mode. */
7718         if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
7719                 die("Not a git repository");
7721         if (*opt_encoding && strcmp(opt_codeset, "UTF-8")) {
7722                 opt_iconv_in = iconv_open("UTF-8", opt_encoding);
7723                 if (opt_iconv_in == ICONV_NONE)
7724                         die("Failed to initialize character set conversion");
7725         }
7727         if (*opt_codeset && strcmp(opt_codeset, "UTF-8")) {
7728                 opt_iconv_out = iconv_open(opt_codeset, "UTF-8");
7729                 if (opt_iconv_out == ICONV_NONE)
7730                         die("Failed to initialize character set conversion");
7731         }
7733         if (load_refs() == ERR)
7734                 die("Failed to load refs.");
7736         foreach_view (view, i)
7737                 argv_from_env(view->ops->argv, view->cmd_env);
7739         init_display();
7741         if (request != REQ_NONE)
7742                 open_view(NULL, request, OPEN_PREPARED);
7743         request = request == REQ_NONE ? REQ_VIEW_MAIN : REQ_NONE;
7745         while (view_driver(display[current_view], request)) {
7746                 int key = get_input(0);
7748                 view = display[current_view];
7749                 request = get_keybinding(view->keymap, key);
7751                 /* Some low-level request handling. This keeps access to
7752                  * status_win restricted. */
7753                 switch (request) {
7754                 case REQ_PROMPT:
7755                 {
7756                         char *cmd = read_prompt(":");
7758                         if (cmd && isdigit(*cmd)) {
7759                                 int lineno = view->lineno + 1;
7761                                 if (parse_int(&lineno, cmd, 1, view->lines + 1) == OK) {
7762                                         select_view_line(view, lineno - 1);
7763                                         report("");
7764                                 } else {
7765                                         report("Unable to parse '%s' as a line number", cmd);
7766                                 }
7768                         } else if (cmd) {
7769                                 struct view *next = VIEW(REQ_VIEW_PAGER);
7770                                 const char *argv[SIZEOF_ARG] = { "git" };
7771                                 int argc = 1;
7773                                 /* When running random commands, initially show the
7774                                  * command in the title. However, it maybe later be
7775                                  * overwritten if a commit line is selected. */
7776                                 string_ncopy(next->ref, cmd, strlen(cmd));
7778                                 if (!argv_from_string(argv, &argc, cmd)) {
7779                                         report("Too many arguments");
7780                                 } else if (!prepare_update(next, argv, NULL, FORMAT_DASH)) {
7781                                         report("Failed to format command");
7782                                 } else {
7783                                         open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
7784                                 }
7785                         }
7787                         request = REQ_NONE;
7788                         break;
7789                 }
7790                 case REQ_SEARCH:
7791                 case REQ_SEARCH_BACK:
7792                 {
7793                         const char *prompt = request == REQ_SEARCH ? "/" : "?";
7794                         char *search = read_prompt(prompt);
7796                         if (search)
7797                                 string_ncopy(opt_search, search, strlen(search));
7798                         else if (*opt_search)
7799                                 request = request == REQ_SEARCH ?
7800                                         REQ_FIND_NEXT :
7801                                         REQ_FIND_PREV;
7802                         else
7803                                 request = REQ_NONE;
7804                         break;
7805                 }
7806                 default:
7807                         break;
7808                 }
7809         }
7811         quit(0);
7813         return 0;