Code

Define date values in DATE_INFO macro
[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 #define DATE_INFO \
385         DATE_(NO), \
386         DATE_(DEFAULT), \
387         DATE_(RELATIVE), \
388         DATE_(SHORT)
390 enum date {
391 #define DATE_(name) DATE_##name
392         DATE_INFO
393 #undef  DATE_
394 };
396 static const struct enum_map date_map[] = {
397 #define DATE_(name) ENUM_MAP(#name, DATE_##name)
398         DATE_INFO
399 #undef  DATE_
400 };
402 static char *
403 string_date(const time_t *time, enum date date)
405         static char buf[DATE_COLS + 1];
406         static const struct enum_map reldate[] = {
407                 { "second", 1,                  60 * 2 },
408                 { "minute", 60,                 60 * 60 * 2 },
409                 { "hour",   60 * 60,            60 * 60 * 24 * 2 },
410                 { "day",    60 * 60 * 24,       60 * 60 * 24 * 7 * 2 },
411                 { "week",   60 * 60 * 24 * 7,   60 * 60 * 24 * 7 * 5 },
412                 { "month",  60 * 60 * 24 * 30,  60 * 60 * 24 * 30 * 12 },
413         };
414         struct tm tm;
416         if (date == DATE_RELATIVE) {
417                 struct timeval now;
418                 time_t date = *time + local_tzoffset(*time);
419                 time_t seconds;
420                 int i;
422                 gettimeofday(&now, NULL);
423                 seconds = now.tv_sec < date ? date - now.tv_sec : now.tv_sec - date;
424                 for (i = 0; i < ARRAY_SIZE(reldate); i++) {
425                         if (seconds >= reldate[i].value)
426                                 continue;
428                         seconds /= reldate[i].namelen;
429                         if (!string_format(buf, "%ld %s%s %s",
430                                            seconds, reldate[i].name,
431                                            seconds > 1 ? "s" : "",
432                                            now.tv_sec >= date ? "ago" : "ahead"))
433                                 break;
434                         return buf;
435                 }
436         }
438         gmtime_r(time, &tm);
439         return strftime(buf, sizeof(buf), DATE_FORMAT, &tm) ? buf : NULL;
443 static bool
444 argv_from_string(const char *argv[SIZEOF_ARG], int *argc, char *cmd)
446         int valuelen;
448         while (*cmd && *argc < SIZEOF_ARG && (valuelen = strcspn(cmd, " \t"))) {
449                 bool advance = cmd[valuelen] != 0;
451                 cmd[valuelen] = 0;
452                 argv[(*argc)++] = chomp_string(cmd);
453                 cmd = chomp_string(cmd + valuelen + advance);
454         }
456         if (*argc < SIZEOF_ARG)
457                 argv[*argc] = NULL;
458         return *argc < SIZEOF_ARG;
461 static void
462 argv_from_env(const char **argv, const char *name)
464         char *env = argv ? getenv(name) : NULL;
465         int argc = 0;
467         if (env && *env)
468                 env = strdup(env);
469         if (env && !argv_from_string(argv, &argc, env))
470                 die("Too many arguments in the `%s` environment variable", name);
474 /*
475  * Executing external commands.
476  */
478 enum io_type {
479         IO_FD,                  /* File descriptor based IO. */
480         IO_BG,                  /* Execute command in the background. */
481         IO_FG,                  /* Execute command with same std{in,out,err}. */
482         IO_RD,                  /* Read only fork+exec IO. */
483         IO_WR,                  /* Write only fork+exec IO. */
484         IO_AP,                  /* Append fork+exec output to file. */
485 };
487 struct io {
488         enum io_type type;      /* The requested type of pipe. */
489         const char *dir;        /* Directory from which to execute. */
490         pid_t pid;              /* Pipe for reading or writing. */
491         int pipe;               /* Pipe end for reading or writing. */
492         int error;              /* Error status. */
493         const char *argv[SIZEOF_ARG];   /* Shell command arguments. */
494         char *buf;              /* Read buffer. */
495         size_t bufalloc;        /* Allocated buffer size. */
496         size_t bufsize;         /* Buffer content size. */
497         char *bufpos;           /* Current buffer position. */
498         unsigned int eof:1;     /* Has end of file been reached. */
499 };
501 static void
502 reset_io(struct io *io)
504         io->pipe = -1;
505         io->pid = 0;
506         io->buf = io->bufpos = NULL;
507         io->bufalloc = io->bufsize = 0;
508         io->error = 0;
509         io->eof = 0;
512 static void
513 init_io(struct io *io, const char *dir, enum io_type type)
515         reset_io(io);
516         io->type = type;
517         io->dir = dir;
520 static bool
521 init_io_rd(struct io *io, const char *argv[], const char *dir,
522                 enum format_flags flags)
524         init_io(io, dir, IO_RD);
525         return format_argv(io->argv, argv, flags);
528 static bool
529 io_open(struct io *io, const char *fmt, ...)
531         char name[SIZEOF_STR] = "";
532         bool fits;
533         va_list args;
535         init_io(io, NULL, IO_FD);
537         va_start(args, fmt);
538         fits = vsnprintf(name, sizeof(name), fmt, args) < sizeof(name);
539         va_end(args);
541         if (!fits) {
542                 io->error = ENAMETOOLONG;
543                 return FALSE;
544         }
545         io->pipe = *name ? open(name, O_RDONLY) : STDIN_FILENO;
546         if (io->pipe == -1)
547                 io->error = errno;
548         return io->pipe != -1;
551 static bool
552 kill_io(struct io *io)
554         return io->pid == 0 || kill(io->pid, SIGKILL) != -1;
557 static bool
558 done_io(struct io *io)
560         pid_t pid = io->pid;
562         if (io->pipe != -1)
563                 close(io->pipe);
564         free(io->buf);
565         reset_io(io);
567         while (pid > 0) {
568                 int status;
569                 pid_t waiting = waitpid(pid, &status, 0);
571                 if (waiting < 0) {
572                         if (errno == EINTR)
573                                 continue;
574                         report("waitpid failed (%s)", strerror(errno));
575                         return FALSE;
576                 }
578                 return waiting == pid &&
579                        !WIFSIGNALED(status) &&
580                        WIFEXITED(status) &&
581                        !WEXITSTATUS(status);
582         }
584         return TRUE;
587 static bool
588 start_io(struct io *io)
590         int pipefds[2] = { -1, -1 };
592         if (io->type == IO_FD)
593                 return TRUE;
595         if ((io->type == IO_RD || io->type == IO_WR) &&
596             pipe(pipefds) < 0)
597                 return FALSE;
598         else if (io->type == IO_AP)
599                 pipefds[1] = io->pipe;
601         if ((io->pid = fork())) {
602                 if (pipefds[!(io->type == IO_WR)] != -1)
603                         close(pipefds[!(io->type == IO_WR)]);
604                 if (io->pid != -1) {
605                         io->pipe = pipefds[!!(io->type == IO_WR)];
606                         return TRUE;
607                 }
609         } else {
610                 if (io->type != IO_FG) {
611                         int devnull = open("/dev/null", O_RDWR);
612                         int readfd  = io->type == IO_WR ? pipefds[0] : devnull;
613                         int writefd = (io->type == IO_RD || io->type == IO_AP)
614                                                         ? pipefds[1] : devnull;
616                         dup2(readfd,  STDIN_FILENO);
617                         dup2(writefd, STDOUT_FILENO);
618                         dup2(devnull, STDERR_FILENO);
620                         close(devnull);
621                         if (pipefds[0] != -1)
622                                 close(pipefds[0]);
623                         if (pipefds[1] != -1)
624                                 close(pipefds[1]);
625                 }
627                 if (io->dir && *io->dir && chdir(io->dir) == -1)
628                         die("Failed to change directory: %s", strerror(errno));
630                 execvp(io->argv[0], (char *const*) io->argv);
631                 die("Failed to execute program: %s", strerror(errno));
632         }
634         if (pipefds[!!(io->type == IO_WR)] != -1)
635                 close(pipefds[!!(io->type == IO_WR)]);
636         return FALSE;
639 static bool
640 run_io(struct io *io, const char **argv, const char *dir, enum io_type type)
642         init_io(io, dir, type);
643         if (!format_argv(io->argv, argv, FORMAT_NONE))
644                 return FALSE;
645         return start_io(io);
648 static int
649 run_io_do(struct io *io)
651         return start_io(io) && done_io(io);
654 static int
655 run_io_bg(const char **argv)
657         struct io io = {};
659         init_io(&io, NULL, IO_BG);
660         if (!format_argv(io.argv, argv, FORMAT_NONE))
661                 return FALSE;
662         return run_io_do(&io);
665 static bool
666 run_io_fg(const char **argv, const char *dir)
668         struct io io = {};
670         init_io(&io, dir, IO_FG);
671         if (!format_argv(io.argv, argv, FORMAT_NONE))
672                 return FALSE;
673         return run_io_do(&io);
676 static bool
677 run_io_append(const char **argv, enum format_flags flags, int fd)
679         struct io io = {};
681         init_io(&io, NULL, IO_AP);
682         io.pipe = fd;
683         if (format_argv(io.argv, argv, flags))
684                 return run_io_do(&io);
685         close(fd);
686         return FALSE;
689 static bool
690 run_io_rd(struct io *io, const char **argv, const char *dir, enum format_flags flags)
692         return init_io_rd(io, argv, dir, flags) && start_io(io);
695 static bool
696 io_eof(struct io *io)
698         return io->eof;
701 static int
702 io_error(struct io *io)
704         return io->error;
707 static char *
708 io_strerror(struct io *io)
710         return strerror(io->error);
713 static bool
714 io_can_read(struct io *io)
716         struct timeval tv = { 0, 500 };
717         fd_set fds;
719         FD_ZERO(&fds);
720         FD_SET(io->pipe, &fds);
722         return select(io->pipe + 1, &fds, NULL, NULL, &tv) > 0;
725 static ssize_t
726 io_read(struct io *io, void *buf, size_t bufsize)
728         do {
729                 ssize_t readsize = read(io->pipe, buf, bufsize);
731                 if (readsize < 0 && (errno == EAGAIN || errno == EINTR))
732                         continue;
733                 else if (readsize == -1)
734                         io->error = errno;
735                 else if (readsize == 0)
736                         io->eof = 1;
737                 return readsize;
738         } while (1);
741 DEFINE_ALLOCATOR(realloc_io_buf, char, BUFSIZ)
743 static char *
744 io_get(struct io *io, int c, bool can_read)
746         char *eol;
747         ssize_t readsize;
749         while (TRUE) {
750                 if (io->bufsize > 0) {
751                         eol = memchr(io->bufpos, c, io->bufsize);
752                         if (eol) {
753                                 char *line = io->bufpos;
755                                 *eol = 0;
756                                 io->bufpos = eol + 1;
757                                 io->bufsize -= io->bufpos - line;
758                                 return line;
759                         }
760                 }
762                 if (io_eof(io)) {
763                         if (io->bufsize) {
764                                 io->bufpos[io->bufsize] = 0;
765                                 io->bufsize = 0;
766                                 return io->bufpos;
767                         }
768                         return NULL;
769                 }
771                 if (!can_read)
772                         return NULL;
774                 if (io->bufsize > 0 && io->bufpos > io->buf)
775                         memmove(io->buf, io->bufpos, io->bufsize);
777                 if (io->bufalloc == io->bufsize) {
778                         if (!realloc_io_buf(&io->buf, io->bufalloc, BUFSIZ))
779                                 return NULL;
780                         io->bufalloc += BUFSIZ;
781                 }
783                 io->bufpos = io->buf;
784                 readsize = io_read(io, io->buf + io->bufsize, io->bufalloc - io->bufsize);
785                 if (io_error(io))
786                         return NULL;
787                 io->bufsize += readsize;
788         }
791 static bool
792 io_write(struct io *io, const void *buf, size_t bufsize)
794         size_t written = 0;
796         while (!io_error(io) && written < bufsize) {
797                 ssize_t size;
799                 size = write(io->pipe, buf + written, bufsize - written);
800                 if (size < 0 && (errno == EAGAIN || errno == EINTR))
801                         continue;
802                 else if (size == -1)
803                         io->error = errno;
804                 else
805                         written += size;
806         }
808         return written == bufsize;
811 static bool
812 io_read_buf(struct io *io, char buf[], size_t bufsize)
814         char *result = io_get(io, '\n', TRUE);
816         if (result) {
817                 result = chomp_string(result);
818                 string_ncopy_do(buf, bufsize, result, strlen(result));
819         }
821         return done_io(io) && result;
824 static bool
825 run_io_buf(const char **argv, char buf[], size_t bufsize)
827         struct io io = {};
829         return run_io_rd(&io, argv, NULL, FORMAT_NONE)
830             && io_read_buf(&io, buf, bufsize);
833 static int
834 io_load(struct io *io, const char *separators,
835         int (*read_property)(char *, size_t, char *, size_t))
837         char *name;
838         int state = OK;
840         if (!start_io(io))
841                 return ERR;
843         while (state == OK && (name = io_get(io, '\n', TRUE))) {
844                 char *value;
845                 size_t namelen;
846                 size_t valuelen;
848                 name = chomp_string(name);
849                 namelen = strcspn(name, separators);
851                 if (name[namelen]) {
852                         name[namelen] = 0;
853                         value = chomp_string(name + namelen + 1);
854                         valuelen = strlen(value);
856                 } else {
857                         value = "";
858                         valuelen = 0;
859                 }
861                 state = read_property(name, namelen, value, valuelen);
862         }
864         if (state != ERR && io_error(io))
865                 state = ERR;
866         done_io(io);
868         return state;
871 static int
872 run_io_load(const char **argv, const char *separators,
873             int (*read_property)(char *, size_t, char *, size_t))
875         struct io io = {};
877         return init_io_rd(&io, argv, NULL, FORMAT_NONE)
878                 ? io_load(&io, separators, read_property) : ERR;
882 /*
883  * User requests
884  */
886 #define REQ_INFO \
887         /* XXX: Keep the view request first and in sync with views[]. */ \
888         REQ_GROUP("View switching") \
889         REQ_(VIEW_MAIN,         "Show main view"), \
890         REQ_(VIEW_DIFF,         "Show diff view"), \
891         REQ_(VIEW_LOG,          "Show log view"), \
892         REQ_(VIEW_TREE,         "Show tree view"), \
893         REQ_(VIEW_BLOB,         "Show blob view"), \
894         REQ_(VIEW_BLAME,        "Show blame view"), \
895         REQ_(VIEW_BRANCH,       "Show branch view"), \
896         REQ_(VIEW_HELP,         "Show help page"), \
897         REQ_(VIEW_PAGER,        "Show pager view"), \
898         REQ_(VIEW_STATUS,       "Show status view"), \
899         REQ_(VIEW_STAGE,        "Show stage view"), \
900         \
901         REQ_GROUP("View manipulation") \
902         REQ_(ENTER,             "Enter current line and scroll"), \
903         REQ_(NEXT,              "Move to next"), \
904         REQ_(PREVIOUS,          "Move to previous"), \
905         REQ_(PARENT,            "Move to parent"), \
906         REQ_(VIEW_NEXT,         "Move focus to next view"), \
907         REQ_(REFRESH,           "Reload and refresh"), \
908         REQ_(MAXIMIZE,          "Maximize the current view"), \
909         REQ_(VIEW_CLOSE,        "Close the current view"), \
910         REQ_(QUIT,              "Close all views and quit"), \
911         \
912         REQ_GROUP("View specific requests") \
913         REQ_(STATUS_UPDATE,     "Update file status"), \
914         REQ_(STATUS_REVERT,     "Revert file changes"), \
915         REQ_(STATUS_MERGE,      "Merge file using external tool"), \
916         REQ_(STAGE_NEXT,        "Find next chunk to stage"), \
917         \
918         REQ_GROUP("Cursor navigation") \
919         REQ_(MOVE_UP,           "Move cursor one line up"), \
920         REQ_(MOVE_DOWN,         "Move cursor one line down"), \
921         REQ_(MOVE_PAGE_DOWN,    "Move cursor one page down"), \
922         REQ_(MOVE_PAGE_UP,      "Move cursor one page up"), \
923         REQ_(MOVE_FIRST_LINE,   "Move cursor to first line"), \
924         REQ_(MOVE_LAST_LINE,    "Move cursor to last line"), \
925         \
926         REQ_GROUP("Scrolling") \
927         REQ_(SCROLL_LEFT,       "Scroll two columns left"), \
928         REQ_(SCROLL_RIGHT,      "Scroll two columns right"), \
929         REQ_(SCROLL_LINE_UP,    "Scroll one line up"), \
930         REQ_(SCROLL_LINE_DOWN,  "Scroll one line down"), \
931         REQ_(SCROLL_PAGE_UP,    "Scroll one page up"), \
932         REQ_(SCROLL_PAGE_DOWN,  "Scroll one page down"), \
933         \
934         REQ_GROUP("Searching") \
935         REQ_(SEARCH,            "Search the view"), \
936         REQ_(SEARCH_BACK,       "Search backwards in the view"), \
937         REQ_(FIND_NEXT,         "Find next search match"), \
938         REQ_(FIND_PREV,         "Find previous search match"), \
939         \
940         REQ_GROUP("Option manipulation") \
941         REQ_(OPTIONS,           "Open option menu"), \
942         REQ_(TOGGLE_LINENO,     "Toggle line numbers"), \
943         REQ_(TOGGLE_DATE,       "Toggle date display"), \
944         REQ_(TOGGLE_DATE_SHORT, "Toggle short (date-only) dates"), \
945         REQ_(TOGGLE_AUTHOR,     "Toggle author display"), \
946         REQ_(TOGGLE_REV_GRAPH,  "Toggle revision graph visualization"), \
947         REQ_(TOGGLE_REFS,       "Toggle reference display (tags/branches)"), \
948         REQ_(TOGGLE_SORT_ORDER, "Toggle ascending/descending sort order"), \
949         REQ_(TOGGLE_SORT_FIELD, "Toggle field to sort by"), \
950         \
951         REQ_GROUP("Misc") \
952         REQ_(PROMPT,            "Bring up the prompt"), \
953         REQ_(SCREEN_REDRAW,     "Redraw the screen"), \
954         REQ_(SHOW_VERSION,      "Show version information"), \
955         REQ_(STOP_LOADING,      "Stop all loading views"), \
956         REQ_(EDIT,              "Open in editor"), \
957         REQ_(NONE,              "Do nothing")
960 /* User action requests. */
961 enum request {
962 #define REQ_GROUP(help)
963 #define REQ_(req, help) REQ_##req
965         /* Offset all requests to avoid conflicts with ncurses getch values. */
966         REQ_OFFSET = KEY_MAX + 1,
967         REQ_INFO
969 #undef  REQ_GROUP
970 #undef  REQ_
971 };
973 struct request_info {
974         enum request request;
975         const char *name;
976         int namelen;
977         const char *help;
978 };
980 static const struct request_info req_info[] = {
981 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
982 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
983         REQ_INFO
984 #undef  REQ_GROUP
985 #undef  REQ_
986 };
988 static enum request
989 get_request(const char *name)
991         int namelen = strlen(name);
992         int i;
994         for (i = 0; i < ARRAY_SIZE(req_info); i++)
995                 if (req_info[i].namelen == namelen &&
996                     !string_enum_compare(req_info[i].name, name, namelen))
997                         return req_info[i].request;
999         return REQ_NONE;
1003 /*
1004  * Options
1005  */
1007 /* Option and state variables. */
1008 static enum date opt_date               = DATE_DEFAULT;
1009 static bool opt_author                  = TRUE;
1010 static bool opt_line_number             = FALSE;
1011 static bool opt_line_graphics           = TRUE;
1012 static bool opt_rev_graph               = FALSE;
1013 static bool opt_show_refs               = TRUE;
1014 static int opt_num_interval             = 5;
1015 static double opt_hscroll               = 0.50;
1016 static double opt_scale_split_view      = 2.0 / 3.0;
1017 static int opt_tab_size                 = 8;
1018 static int opt_author_cols              = 19;
1019 static char opt_path[SIZEOF_STR]        = "";
1020 static char opt_file[SIZEOF_STR]        = "";
1021 static char opt_ref[SIZEOF_REF]         = "";
1022 static char opt_head[SIZEOF_REF]        = "";
1023 static char opt_head_rev[SIZEOF_REV]    = "";
1024 static char opt_remote[SIZEOF_REF]      = "";
1025 static char opt_encoding[20]            = "UTF-8";
1026 static char opt_codeset[20]             = "UTF-8";
1027 static iconv_t opt_iconv_in             = ICONV_NONE;
1028 static iconv_t opt_iconv_out            = ICONV_NONE;
1029 static char opt_search[SIZEOF_STR]      = "";
1030 static char opt_cdup[SIZEOF_STR]        = "";
1031 static char opt_prefix[SIZEOF_STR]      = "";
1032 static char opt_git_dir[SIZEOF_STR]     = "";
1033 static signed char opt_is_inside_work_tree      = -1; /* set to TRUE or FALSE */
1034 static char opt_editor[SIZEOF_STR]      = "";
1035 static FILE *opt_tty                    = NULL;
1037 #define is_initial_commit()     (!*opt_head_rev)
1038 #define is_head_commit(rev)     (!strcmp((rev), "HEAD") || !strcmp(opt_head_rev, (rev)))
1039 #define mkdate(time)            string_date(time, opt_date)
1042 /*
1043  * Line-oriented content detection.
1044  */
1046 #define LINE_INFO \
1047 LINE(DIFF_HEADER,  "diff --git ",       COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1048 LINE(DIFF_CHUNK,   "@@",                COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1049 LINE(DIFF_ADD,     "+",                 COLOR_GREEN,    COLOR_DEFAULT,  0), \
1050 LINE(DIFF_DEL,     "-",                 COLOR_RED,      COLOR_DEFAULT,  0), \
1051 LINE(DIFF_INDEX,        "index ",         COLOR_BLUE,   COLOR_DEFAULT,  0), \
1052 LINE(DIFF_OLDMODE,      "old file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
1053 LINE(DIFF_NEWMODE,      "new file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
1054 LINE(DIFF_COPY_FROM,    "copy from",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
1055 LINE(DIFF_COPY_TO,      "copy to",        COLOR_YELLOW, COLOR_DEFAULT,  0), \
1056 LINE(DIFF_RENAME_FROM,  "rename from",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
1057 LINE(DIFF_RENAME_TO,    "rename to",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
1058 LINE(DIFF_SIMILARITY,   "similarity ",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
1059 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
1060 LINE(DIFF_TREE,         "diff-tree ",     COLOR_BLUE,   COLOR_DEFAULT,  0), \
1061 LINE(PP_AUTHOR,    "Author: ",          COLOR_CYAN,     COLOR_DEFAULT,  0), \
1062 LINE(PP_COMMIT,    "Commit: ",          COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1063 LINE(PP_MERGE,     "Merge: ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
1064 LINE(PP_DATE,      "Date:   ",          COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1065 LINE(PP_ADATE,     "AuthorDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1066 LINE(PP_CDATE,     "CommitDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1067 LINE(PP_REFS,      "Refs: ",            COLOR_RED,      COLOR_DEFAULT,  0), \
1068 LINE(COMMIT,       "commit ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
1069 LINE(PARENT,       "parent ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
1070 LINE(TREE,         "tree ",             COLOR_BLUE,     COLOR_DEFAULT,  0), \
1071 LINE(AUTHOR,       "author ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
1072 LINE(COMMITTER,    "committer ",        COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1073 LINE(SIGNOFF,      "    Signed-off-by", COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1074 LINE(ACKED,        "    Acked-by",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1075 LINE(DEFAULT,      "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
1076 LINE(CURSOR,       "",                  COLOR_WHITE,    COLOR_GREEN,    A_BOLD), \
1077 LINE(STATUS,       "",                  COLOR_GREEN,    COLOR_DEFAULT,  0), \
1078 LINE(DELIMITER,    "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1079 LINE(DATE,         "",                  COLOR_BLUE,     COLOR_DEFAULT,  0), \
1080 LINE(MODE,         "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1081 LINE(LINE_NUMBER,  "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1082 LINE(TITLE_BLUR,   "",                  COLOR_WHITE,    COLOR_BLUE,     0), \
1083 LINE(TITLE_FOCUS,  "",                  COLOR_WHITE,    COLOR_BLUE,     A_BOLD), \
1084 LINE(MAIN_COMMIT,  "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
1085 LINE(MAIN_TAG,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  A_BOLD), \
1086 LINE(MAIN_LOCAL_TAG,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1087 LINE(MAIN_REMOTE,  "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1088 LINE(MAIN_TRACKED, "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_BOLD), \
1089 LINE(MAIN_REF,     "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1090 LINE(MAIN_HEAD,    "",                  COLOR_CYAN,     COLOR_DEFAULT,  A_BOLD), \
1091 LINE(MAIN_REVGRAPH,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1092 LINE(TREE_HEAD,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_BOLD), \
1093 LINE(TREE_DIR,     "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_NORMAL), \
1094 LINE(TREE_FILE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
1095 LINE(STAT_HEAD,    "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1096 LINE(STAT_SECTION, "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1097 LINE(STAT_NONE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
1098 LINE(STAT_STAGED,  "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1099 LINE(STAT_UNSTAGED,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1100 LINE(STAT_UNTRACKED,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1101 LINE(HELP_KEYMAP,  "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1102 LINE(HELP_GROUP,   "",                  COLOR_BLUE,     COLOR_DEFAULT,  0), \
1103 LINE(BLAME_ID,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0)
1105 enum line_type {
1106 #define LINE(type, line, fg, bg, attr) \
1107         LINE_##type
1108         LINE_INFO,
1109         LINE_NONE
1110 #undef  LINE
1111 };
1113 struct line_info {
1114         const char *name;       /* Option name. */
1115         int namelen;            /* Size of option name. */
1116         const char *line;       /* The start of line to match. */
1117         int linelen;            /* Size of string to match. */
1118         int fg, bg, attr;       /* Color and text attributes for the lines. */
1119 };
1121 static struct line_info line_info[] = {
1122 #define LINE(type, line, fg, bg, attr) \
1123         { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
1124         LINE_INFO
1125 #undef  LINE
1126 };
1128 static enum line_type
1129 get_line_type(const char *line)
1131         int linelen = strlen(line);
1132         enum line_type type;
1134         for (type = 0; type < ARRAY_SIZE(line_info); type++)
1135                 /* Case insensitive search matches Signed-off-by lines better. */
1136                 if (linelen >= line_info[type].linelen &&
1137                     !strncasecmp(line_info[type].line, line, line_info[type].linelen))
1138                         return type;
1140         return LINE_DEFAULT;
1143 static inline int
1144 get_line_attr(enum line_type type)
1146         assert(type < ARRAY_SIZE(line_info));
1147         return COLOR_PAIR(type) | line_info[type].attr;
1150 static struct line_info *
1151 get_line_info(const char *name)
1153         size_t namelen = strlen(name);
1154         enum line_type type;
1156         for (type = 0; type < ARRAY_SIZE(line_info); type++)
1157                 if (namelen == line_info[type].namelen &&
1158                     !string_enum_compare(line_info[type].name, name, namelen))
1159                         return &line_info[type];
1161         return NULL;
1164 static void
1165 init_colors(void)
1167         int default_bg = line_info[LINE_DEFAULT].bg;
1168         int default_fg = line_info[LINE_DEFAULT].fg;
1169         enum line_type type;
1171         start_color();
1173         if (assume_default_colors(default_fg, default_bg) == ERR) {
1174                 default_bg = COLOR_BLACK;
1175                 default_fg = COLOR_WHITE;
1176         }
1178         for (type = 0; type < ARRAY_SIZE(line_info); type++) {
1179                 struct line_info *info = &line_info[type];
1180                 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
1181                 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
1183                 init_pair(type, fg, bg);
1184         }
1187 struct line {
1188         enum line_type type;
1190         /* State flags */
1191         unsigned int selected:1;
1192         unsigned int dirty:1;
1193         unsigned int cleareol:1;
1194         unsigned int other:16;
1196         void *data;             /* User data */
1197 };
1200 /*
1201  * Keys
1202  */
1204 struct keybinding {
1205         int alias;
1206         enum request request;
1207 };
1209 static const struct keybinding default_keybindings[] = {
1210         /* View switching */
1211         { 'm',          REQ_VIEW_MAIN },
1212         { 'd',          REQ_VIEW_DIFF },
1213         { 'l',          REQ_VIEW_LOG },
1214         { 't',          REQ_VIEW_TREE },
1215         { 'f',          REQ_VIEW_BLOB },
1216         { 'B',          REQ_VIEW_BLAME },
1217         { 'H',          REQ_VIEW_BRANCH },
1218         { 'p',          REQ_VIEW_PAGER },
1219         { 'h',          REQ_VIEW_HELP },
1220         { 'S',          REQ_VIEW_STATUS },
1221         { 'c',          REQ_VIEW_STAGE },
1223         /* View manipulation */
1224         { 'q',          REQ_VIEW_CLOSE },
1225         { KEY_TAB,      REQ_VIEW_NEXT },
1226         { KEY_RETURN,   REQ_ENTER },
1227         { KEY_UP,       REQ_PREVIOUS },
1228         { KEY_DOWN,     REQ_NEXT },
1229         { 'R',          REQ_REFRESH },
1230         { KEY_F(5),     REQ_REFRESH },
1231         { 'O',          REQ_MAXIMIZE },
1233         /* Cursor navigation */
1234         { 'k',          REQ_MOVE_UP },
1235         { 'j',          REQ_MOVE_DOWN },
1236         { KEY_HOME,     REQ_MOVE_FIRST_LINE },
1237         { KEY_END,      REQ_MOVE_LAST_LINE },
1238         { KEY_NPAGE,    REQ_MOVE_PAGE_DOWN },
1239         { ' ',          REQ_MOVE_PAGE_DOWN },
1240         { KEY_PPAGE,    REQ_MOVE_PAGE_UP },
1241         { 'b',          REQ_MOVE_PAGE_UP },
1242         { '-',          REQ_MOVE_PAGE_UP },
1244         /* Scrolling */
1245         { KEY_LEFT,     REQ_SCROLL_LEFT },
1246         { KEY_RIGHT,    REQ_SCROLL_RIGHT },
1247         { KEY_IC,       REQ_SCROLL_LINE_UP },
1248         { KEY_DC,       REQ_SCROLL_LINE_DOWN },
1249         { 'w',          REQ_SCROLL_PAGE_UP },
1250         { 's',          REQ_SCROLL_PAGE_DOWN },
1252         /* Searching */
1253         { '/',          REQ_SEARCH },
1254         { '?',          REQ_SEARCH_BACK },
1255         { 'n',          REQ_FIND_NEXT },
1256         { 'N',          REQ_FIND_PREV },
1258         /* Misc */
1259         { 'Q',          REQ_QUIT },
1260         { 'z',          REQ_STOP_LOADING },
1261         { 'v',          REQ_SHOW_VERSION },
1262         { 'r',          REQ_SCREEN_REDRAW },
1263         { 'o',          REQ_OPTIONS },
1264         { '.',          REQ_TOGGLE_LINENO },
1265         { 'D',          REQ_TOGGLE_DATE },
1266         { 'A',          REQ_TOGGLE_AUTHOR },
1267         { 'g',          REQ_TOGGLE_REV_GRAPH },
1268         { 'F',          REQ_TOGGLE_REFS },
1269         { 'I',          REQ_TOGGLE_SORT_ORDER },
1270         { 'i',          REQ_TOGGLE_SORT_FIELD },
1271         { ':',          REQ_PROMPT },
1272         { 'u',          REQ_STATUS_UPDATE },
1273         { '!',          REQ_STATUS_REVERT },
1274         { 'M',          REQ_STATUS_MERGE },
1275         { '@',          REQ_STAGE_NEXT },
1276         { ',',          REQ_PARENT },
1277         { 'e',          REQ_EDIT },
1278 };
1280 #define KEYMAP_INFO \
1281         KEYMAP_(GENERIC), \
1282         KEYMAP_(MAIN), \
1283         KEYMAP_(DIFF), \
1284         KEYMAP_(LOG), \
1285         KEYMAP_(TREE), \
1286         KEYMAP_(BLOB), \
1287         KEYMAP_(BLAME), \
1288         KEYMAP_(BRANCH), \
1289         KEYMAP_(PAGER), \
1290         KEYMAP_(HELP), \
1291         KEYMAP_(STATUS), \
1292         KEYMAP_(STAGE)
1294 enum keymap {
1295 #define KEYMAP_(name) KEYMAP_##name
1296         KEYMAP_INFO
1297 #undef  KEYMAP_
1298 };
1300 static const struct enum_map keymap_table[] = {
1301 #define KEYMAP_(name) ENUM_MAP(#name, KEYMAP_##name)
1302         KEYMAP_INFO
1303 #undef  KEYMAP_
1304 };
1306 #define set_keymap(map, name) map_enum(map, keymap_table, name)
1308 struct keybinding_table {
1309         struct keybinding *data;
1310         size_t size;
1311 };
1313 static struct keybinding_table keybindings[ARRAY_SIZE(keymap_table)];
1315 static void
1316 add_keybinding(enum keymap keymap, enum request request, int key)
1318         struct keybinding_table *table = &keybindings[keymap];
1320         table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data));
1321         if (!table->data)
1322                 die("Failed to allocate keybinding");
1323         table->data[table->size].alias = key;
1324         table->data[table->size++].request = request;
1327 /* Looks for a key binding first in the given map, then in the generic map, and
1328  * lastly in the default keybindings. */
1329 static enum request
1330 get_keybinding(enum keymap keymap, int key)
1332         size_t i;
1334         for (i = 0; i < keybindings[keymap].size; i++)
1335                 if (keybindings[keymap].data[i].alias == key)
1336                         return keybindings[keymap].data[i].request;
1338         for (i = 0; i < keybindings[KEYMAP_GENERIC].size; i++)
1339                 if (keybindings[KEYMAP_GENERIC].data[i].alias == key)
1340                         return keybindings[KEYMAP_GENERIC].data[i].request;
1342         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1343                 if (default_keybindings[i].alias == key)
1344                         return default_keybindings[i].request;
1346         return (enum request) key;
1350 struct key {
1351         const char *name;
1352         int value;
1353 };
1355 static const struct key key_table[] = {
1356         { "Enter",      KEY_RETURN },
1357         { "Space",      ' ' },
1358         { "Backspace",  KEY_BACKSPACE },
1359         { "Tab",        KEY_TAB },
1360         { "Escape",     KEY_ESC },
1361         { "Left",       KEY_LEFT },
1362         { "Right",      KEY_RIGHT },
1363         { "Up",         KEY_UP },
1364         { "Down",       KEY_DOWN },
1365         { "Insert",     KEY_IC },
1366         { "Delete",     KEY_DC },
1367         { "Hash",       '#' },
1368         { "Home",       KEY_HOME },
1369         { "End",        KEY_END },
1370         { "PageUp",     KEY_PPAGE },
1371         { "PageDown",   KEY_NPAGE },
1372         { "F1",         KEY_F(1) },
1373         { "F2",         KEY_F(2) },
1374         { "F3",         KEY_F(3) },
1375         { "F4",         KEY_F(4) },
1376         { "F5",         KEY_F(5) },
1377         { "F6",         KEY_F(6) },
1378         { "F7",         KEY_F(7) },
1379         { "F8",         KEY_F(8) },
1380         { "F9",         KEY_F(9) },
1381         { "F10",        KEY_F(10) },
1382         { "F11",        KEY_F(11) },
1383         { "F12",        KEY_F(12) },
1384 };
1386 static int
1387 get_key_value(const char *name)
1389         int i;
1391         for (i = 0; i < ARRAY_SIZE(key_table); i++)
1392                 if (!strcasecmp(key_table[i].name, name))
1393                         return key_table[i].value;
1395         if (strlen(name) == 1 && isprint(*name))
1396                 return (int) *name;
1398         return ERR;
1401 static const char *
1402 get_key_name(int key_value)
1404         static char key_char[] = "'X'";
1405         const char *seq = NULL;
1406         int key;
1408         for (key = 0; key < ARRAY_SIZE(key_table); key++)
1409                 if (key_table[key].value == key_value)
1410                         seq = key_table[key].name;
1412         if (seq == NULL &&
1413             key_value < 127 &&
1414             isprint(key_value)) {
1415                 key_char[1] = (char) key_value;
1416                 seq = key_char;
1417         }
1419         return seq ? seq : "(no key)";
1422 static bool
1423 append_key(char *buf, size_t *pos, const struct keybinding *keybinding)
1425         const char *sep = *pos > 0 ? ", " : "";
1426         const char *keyname = get_key_name(keybinding->alias);
1428         return string_nformat(buf, BUFSIZ, pos, "%s%s", sep, keyname);
1431 static bool
1432 append_keymap_request_keys(char *buf, size_t *pos, enum request request,
1433                            enum keymap keymap, bool all)
1435         int i;
1437         for (i = 0; i < keybindings[keymap].size; i++) {
1438                 if (keybindings[keymap].data[i].request == request) {
1439                         if (!append_key(buf, pos, &keybindings[keymap].data[i]))
1440                                 return FALSE;
1441                         if (!all)
1442                                 break;
1443                 }
1444         }
1446         return TRUE;
1449 #define get_key(keymap, request) get_keys(keymap, request, FALSE)
1451 static const char *
1452 get_keys(enum keymap keymap, enum request request, bool all)
1454         static char buf[BUFSIZ];
1455         size_t pos = 0;
1456         int i;
1458         buf[pos] = 0;
1460         if (!append_keymap_request_keys(buf, &pos, request, keymap, all))
1461                 return "Too many keybindings!";
1462         if (pos > 0 && !all)
1463                 return buf;
1465         if (keymap != KEYMAP_GENERIC) {
1466                 /* Only the generic keymap includes the default keybindings when
1467                  * listing all keys. */
1468                 if (all)
1469                         return buf;
1471                 if (!append_keymap_request_keys(buf, &pos, request, KEYMAP_GENERIC, all))
1472                         return "Too many keybindings!";
1473                 if (pos)
1474                         return buf;
1475         }
1477         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1478                 if (default_keybindings[i].request == request) {
1479                         if (!append_key(buf, &pos, &default_keybindings[i]))
1480                                 return "Too many keybindings!";
1481                         if (!all)
1482                                 return buf;
1483                 }
1484         }
1486         return buf;
1489 struct run_request {
1490         enum keymap keymap;
1491         int key;
1492         const char *argv[SIZEOF_ARG];
1493 };
1495 static struct run_request *run_request;
1496 static size_t run_requests;
1498 DEFINE_ALLOCATOR(realloc_run_requests, struct run_request, 8)
1500 static enum request
1501 add_run_request(enum keymap keymap, int key, int argc, const char **argv)
1503         struct run_request *req;
1505         if (argc >= ARRAY_SIZE(req->argv) - 1)
1506                 return REQ_NONE;
1508         if (!realloc_run_requests(&run_request, run_requests, 1))
1509                 return REQ_NONE;
1511         req = &run_request[run_requests];
1512         req->keymap = keymap;
1513         req->key = key;
1514         req->argv[0] = NULL;
1516         if (!format_argv(req->argv, argv, FORMAT_NONE))
1517                 return REQ_NONE;
1519         return REQ_NONE + ++run_requests;
1522 static struct run_request *
1523 get_run_request(enum request request)
1525         if (request <= REQ_NONE)
1526                 return NULL;
1527         return &run_request[request - REQ_NONE - 1];
1530 static void
1531 add_builtin_run_requests(void)
1533         const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1534         const char *commit[] = { "git", "commit", NULL };
1535         const char *gc[] = { "git", "gc", NULL };
1536         struct {
1537                 enum keymap keymap;
1538                 int key;
1539                 int argc;
1540                 const char **argv;
1541         } reqs[] = {
1542                 { KEYMAP_MAIN,    'C', ARRAY_SIZE(cherry_pick) - 1, cherry_pick },
1543                 { KEYMAP_STATUS,  'C', ARRAY_SIZE(commit) - 1, commit },
1544                 { KEYMAP_GENERIC, 'G', ARRAY_SIZE(gc) - 1, gc },
1545         };
1546         int i;
1548         for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1549                 enum request req;
1551                 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argc, reqs[i].argv);
1552                 if (req != REQ_NONE)
1553                         add_keybinding(reqs[i].keymap, req, reqs[i].key);
1554         }
1557 /*
1558  * User config file handling.
1559  */
1561 static int   config_lineno;
1562 static bool  config_errors;
1563 static const char *config_msg;
1565 static const struct enum_map color_map[] = {
1566 #define COLOR_MAP(name) ENUM_MAP(#name, COLOR_##name)
1567         COLOR_MAP(DEFAULT),
1568         COLOR_MAP(BLACK),
1569         COLOR_MAP(BLUE),
1570         COLOR_MAP(CYAN),
1571         COLOR_MAP(GREEN),
1572         COLOR_MAP(MAGENTA),
1573         COLOR_MAP(RED),
1574         COLOR_MAP(WHITE),
1575         COLOR_MAP(YELLOW),
1576 };
1578 static const struct enum_map attr_map[] = {
1579 #define ATTR_MAP(name) ENUM_MAP(#name, A_##name)
1580         ATTR_MAP(NORMAL),
1581         ATTR_MAP(BLINK),
1582         ATTR_MAP(BOLD),
1583         ATTR_MAP(DIM),
1584         ATTR_MAP(REVERSE),
1585         ATTR_MAP(STANDOUT),
1586         ATTR_MAP(UNDERLINE),
1587 };
1589 #define set_attribute(attr, name)       map_enum(attr, attr_map, name)
1591 static int parse_step(double *opt, const char *arg)
1593         *opt = atoi(arg);
1594         if (!strchr(arg, '%'))
1595                 return OK;
1597         /* "Shift down" so 100% and 1 does not conflict. */
1598         *opt = (*opt - 1) / 100;
1599         if (*opt >= 1.0) {
1600                 *opt = 0.99;
1601                 config_msg = "Step value larger than 100%";
1602                 return ERR;
1603         }
1604         if (*opt < 0.0) {
1605                 *opt = 1;
1606                 config_msg = "Invalid step value";
1607                 return ERR;
1608         }
1609         return OK;
1612 static int
1613 parse_int(int *opt, const char *arg, int min, int max)
1615         int value = atoi(arg);
1617         if (min <= value && value <= max) {
1618                 *opt = value;
1619                 return OK;
1620         }
1622         config_msg = "Integer value out of bound";
1623         return ERR;
1626 static bool
1627 set_color(int *color, const char *name)
1629         if (map_enum(color, color_map, name))
1630                 return TRUE;
1631         if (!prefixcmp(name, "color"))
1632                 return parse_int(color, name + 5, 0, 255) == OK;
1633         return FALSE;
1636 /* Wants: object fgcolor bgcolor [attribute] */
1637 static int
1638 option_color_command(int argc, const char *argv[])
1640         struct line_info *info;
1642         if (argc < 3) {
1643                 config_msg = "Wrong number of arguments given to color command";
1644                 return ERR;
1645         }
1647         info = get_line_info(argv[0]);
1648         if (!info) {
1649                 static const struct enum_map obsolete[] = {
1650                         ENUM_MAP("main-delim",  LINE_DELIMITER),
1651                         ENUM_MAP("main-date",   LINE_DATE),
1652                         ENUM_MAP("main-author", LINE_AUTHOR),
1653                 };
1654                 int index;
1656                 if (!map_enum(&index, obsolete, argv[0])) {
1657                         config_msg = "Unknown color name";
1658                         return ERR;
1659                 }
1660                 info = &line_info[index];
1661         }
1663         if (!set_color(&info->fg, argv[1]) ||
1664             !set_color(&info->bg, argv[2])) {
1665                 config_msg = "Unknown color";
1666                 return ERR;
1667         }
1669         info->attr = 0;
1670         while (argc-- > 3) {
1671                 int attr;
1673                 if (!set_attribute(&attr, argv[argc])) {
1674                         config_msg = "Unknown attribute";
1675                         return ERR;
1676                 }
1677                 info->attr |= attr;
1678         }
1680         return OK;
1683 static int parse_bool(bool *opt, const char *arg)
1685         *opt = (!strcmp(arg, "1") || !strcmp(arg, "true") || !strcmp(arg, "yes"))
1686                 ? TRUE : FALSE;
1687         return OK;
1690 static int parse_enum_do(unsigned int *opt, const char *arg,
1691                          const struct enum_map *map, size_t map_size)
1693         bool is_true;
1695         assert(map_size > 1);
1697         if (map_enum_do(map, map_size, (int *) opt, arg))
1698                 return OK;
1700         if (parse_bool(&is_true, arg) != OK)
1701                 return ERR;
1703         *opt = is_true ? map[1].value : map[0].value;
1704         return OK;
1707 #define parse_enum(opt, arg, map) \
1708         parse_enum_do(opt, arg, map, ARRAY_SIZE(map))
1710 static int
1711 parse_string(char *opt, const char *arg, size_t optsize)
1713         int arglen = strlen(arg);
1715         switch (arg[0]) {
1716         case '\"':
1717         case '\'':
1718                 if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1719                         config_msg = "Unmatched quotation";
1720                         return ERR;
1721                 }
1722                 arg += 1; arglen -= 2;
1723         default:
1724                 string_ncopy_do(opt, optsize, arg, arglen);
1725                 return OK;
1726         }
1729 /* Wants: name = value */
1730 static int
1731 option_set_command(int argc, const char *argv[])
1733         if (argc != 3) {
1734                 config_msg = "Wrong number of arguments given to set command";
1735                 return ERR;
1736         }
1738         if (strcmp(argv[1], "=")) {
1739                 config_msg = "No value assigned";
1740                 return ERR;
1741         }
1743         if (!strcmp(argv[0], "show-author"))
1744                 return parse_bool(&opt_author, argv[2]);
1746         if (!strcmp(argv[0], "show-date"))
1747                 return parse_enum(&opt_date, argv[2], date_map);
1749         if (!strcmp(argv[0], "show-rev-graph"))
1750                 return parse_bool(&opt_rev_graph, argv[2]);
1752         if (!strcmp(argv[0], "show-refs"))
1753                 return parse_bool(&opt_show_refs, argv[2]);
1755         if (!strcmp(argv[0], "show-line-numbers"))
1756                 return parse_bool(&opt_line_number, argv[2]);
1758         if (!strcmp(argv[0], "line-graphics"))
1759                 return parse_bool(&opt_line_graphics, argv[2]);
1761         if (!strcmp(argv[0], "line-number-interval"))
1762                 return parse_int(&opt_num_interval, argv[2], 1, 1024);
1764         if (!strcmp(argv[0], "author-width"))
1765                 return parse_int(&opt_author_cols, argv[2], 0, 1024);
1767         if (!strcmp(argv[0], "horizontal-scroll"))
1768                 return parse_step(&opt_hscroll, argv[2]);
1770         if (!strcmp(argv[0], "split-view-height"))
1771                 return parse_step(&opt_scale_split_view, argv[2]);
1773         if (!strcmp(argv[0], "tab-size"))
1774                 return parse_int(&opt_tab_size, argv[2], 1, 1024);
1776         if (!strcmp(argv[0], "commit-encoding"))
1777                 return parse_string(opt_encoding, argv[2], sizeof(opt_encoding));
1779         config_msg = "Unknown variable name";
1780         return ERR;
1783 /* Wants: mode request key */
1784 static int
1785 option_bind_command(int argc, const char *argv[])
1787         enum request request;
1788         int keymap = -1;
1789         int key;
1791         if (argc < 3) {
1792                 config_msg = "Wrong number of arguments given to bind command";
1793                 return ERR;
1794         }
1796         if (set_keymap(&keymap, argv[0]) == ERR) {
1797                 config_msg = "Unknown key map";
1798                 return ERR;
1799         }
1801         key = get_key_value(argv[1]);
1802         if (key == ERR) {
1803                 config_msg = "Unknown key";
1804                 return ERR;
1805         }
1807         request = get_request(argv[2]);
1808         if (request == REQ_NONE) {
1809                 static const struct enum_map obsolete[] = {
1810                         ENUM_MAP("cherry-pick",         REQ_NONE),
1811                         ENUM_MAP("screen-resize",       REQ_NONE),
1812                         ENUM_MAP("tree-parent",         REQ_PARENT),
1813                 };
1814                 int alias;
1816                 if (map_enum(&alias, obsolete, argv[2])) {
1817                         if (alias != REQ_NONE)
1818                                 add_keybinding(keymap, alias, key);
1819                         config_msg = "Obsolete request name";
1820                         return ERR;
1821                 }
1822         }
1823         if (request == REQ_NONE && *argv[2]++ == '!')
1824                 request = add_run_request(keymap, key, argc - 2, argv + 2);
1825         if (request == REQ_NONE) {
1826                 config_msg = "Unknown request name";
1827                 return ERR;
1828         }
1830         add_keybinding(keymap, request, key);
1832         return OK;
1835 static int
1836 set_option(const char *opt, char *value)
1838         const char *argv[SIZEOF_ARG];
1839         int argc = 0;
1841         if (!argv_from_string(argv, &argc, value)) {
1842                 config_msg = "Too many option arguments";
1843                 return ERR;
1844         }
1846         if (!strcmp(opt, "color"))
1847                 return option_color_command(argc, argv);
1849         if (!strcmp(opt, "set"))
1850                 return option_set_command(argc, argv);
1852         if (!strcmp(opt, "bind"))
1853                 return option_bind_command(argc, argv);
1855         config_msg = "Unknown option command";
1856         return ERR;
1859 static int
1860 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
1862         int status = OK;
1864         config_lineno++;
1865         config_msg = "Internal error";
1867         /* Check for comment markers, since read_properties() will
1868          * only ensure opt and value are split at first " \t". */
1869         optlen = strcspn(opt, "#");
1870         if (optlen == 0)
1871                 return OK;
1873         if (opt[optlen] != 0) {
1874                 config_msg = "No option value";
1875                 status = ERR;
1877         }  else {
1878                 /* Look for comment endings in the value. */
1879                 size_t len = strcspn(value, "#");
1881                 if (len < valuelen) {
1882                         valuelen = len;
1883                         value[valuelen] = 0;
1884                 }
1886                 status = set_option(opt, value);
1887         }
1889         if (status == ERR) {
1890                 warn("Error on line %d, near '%.*s': %s",
1891                      config_lineno, (int) optlen, opt, config_msg);
1892                 config_errors = TRUE;
1893         }
1895         /* Always keep going if errors are encountered. */
1896         return OK;
1899 static void
1900 load_option_file(const char *path)
1902         struct io io = {};
1904         /* It's OK that the file doesn't exist. */
1905         if (!io_open(&io, "%s", path))
1906                 return;
1908         config_lineno = 0;
1909         config_errors = FALSE;
1911         if (io_load(&io, " \t", read_option) == ERR ||
1912             config_errors == TRUE)
1913                 warn("Errors while loading %s.", path);
1916 static int
1917 load_options(void)
1919         const char *home = getenv("HOME");
1920         const char *tigrc_user = getenv("TIGRC_USER");
1921         const char *tigrc_system = getenv("TIGRC_SYSTEM");
1922         char buf[SIZEOF_STR];
1924         add_builtin_run_requests();
1926         if (!tigrc_system)
1927                 tigrc_system = SYSCONFDIR "/tigrc";
1928         load_option_file(tigrc_system);
1930         if (!tigrc_user) {
1931                 if (!home || !string_format(buf, "%s/.tigrc", home))
1932                         return ERR;
1933                 tigrc_user = buf;
1934         }
1935         load_option_file(tigrc_user);
1937         return OK;
1941 /*
1942  * The viewer
1943  */
1945 struct view;
1946 struct view_ops;
1948 /* The display array of active views and the index of the current view. */
1949 static struct view *display[2];
1950 static unsigned int current_view;
1952 #define foreach_displayed_view(view, i) \
1953         for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1955 #define displayed_views()       (display[1] != NULL ? 2 : 1)
1957 /* Current head and commit ID */
1958 static char ref_blob[SIZEOF_REF]        = "";
1959 static char ref_commit[SIZEOF_REF]      = "HEAD";
1960 static char ref_head[SIZEOF_REF]        = "HEAD";
1962 struct view {
1963         const char *name;       /* View name */
1964         const char *cmd_env;    /* Command line set via environment */
1965         const char *id;         /* Points to either of ref_{head,commit,blob} */
1967         struct view_ops *ops;   /* View operations */
1969         enum keymap keymap;     /* What keymap does this view have */
1970         bool git_dir;           /* Whether the view requires a git directory. */
1972         char ref[SIZEOF_REF];   /* Hovered commit reference */
1973         char vid[SIZEOF_REF];   /* View ID. Set to id member when updating. */
1975         int height, width;      /* The width and height of the main window */
1976         WINDOW *win;            /* The main window */
1977         WINDOW *title;          /* The title window living below the main window */
1979         /* Navigation */
1980         unsigned long offset;   /* Offset of the window top */
1981         unsigned long yoffset;  /* Offset from the window side. */
1982         unsigned long lineno;   /* Current line number */
1983         unsigned long p_offset; /* Previous offset of the window top */
1984         unsigned long p_yoffset;/* Previous offset from the window side */
1985         unsigned long p_lineno; /* Previous current line number */
1986         bool p_restore;         /* Should the previous position be restored. */
1988         /* Searching */
1989         char grep[SIZEOF_STR];  /* Search string */
1990         regex_t *regex;         /* Pre-compiled regexp */
1992         /* If non-NULL, points to the view that opened this view. If this view
1993          * is closed tig will switch back to the parent view. */
1994         struct view *parent;
1996         /* Buffering */
1997         size_t lines;           /* Total number of lines */
1998         struct line *line;      /* Line index */
1999         unsigned int digits;    /* Number of digits in the lines member. */
2001         /* Drawing */
2002         struct line *curline;   /* Line currently being drawn. */
2003         enum line_type curtype; /* Attribute currently used for drawing. */
2004         unsigned long col;      /* Column when drawing. */
2005         bool has_scrolled;      /* View was scrolled. */
2007         /* Loading */
2008         struct io io;
2009         struct io *pipe;
2010         time_t start_time;
2011         time_t update_secs;
2012 };
2014 struct view_ops {
2015         /* What type of content being displayed. Used in the title bar. */
2016         const char *type;
2017         /* Default command arguments. */
2018         const char **argv;
2019         /* Open and reads in all view content. */
2020         bool (*open)(struct view *view);
2021         /* Read one line; updates view->line. */
2022         bool (*read)(struct view *view, char *data);
2023         /* Draw one line; @lineno must be < view->height. */
2024         bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
2025         /* Depending on view handle a special requests. */
2026         enum request (*request)(struct view *view, enum request request, struct line *line);
2027         /* Search for regexp in a line. */
2028         bool (*grep)(struct view *view, struct line *line);
2029         /* Select line */
2030         void (*select)(struct view *view, struct line *line);
2031         /* Prepare view for loading */
2032         bool (*prepare)(struct view *view);
2033 };
2035 static struct view_ops blame_ops;
2036 static struct view_ops blob_ops;
2037 static struct view_ops diff_ops;
2038 static struct view_ops help_ops;
2039 static struct view_ops log_ops;
2040 static struct view_ops main_ops;
2041 static struct view_ops pager_ops;
2042 static struct view_ops stage_ops;
2043 static struct view_ops status_ops;
2044 static struct view_ops tree_ops;
2045 static struct view_ops branch_ops;
2047 #define VIEW_STR(name, env, ref, ops, map, git) \
2048         { name, #env, ref, ops, map, git }
2050 #define VIEW_(id, name, ops, git, ref) \
2051         VIEW_STR(name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
2054 static struct view views[] = {
2055         VIEW_(MAIN,   "main",   &main_ops,   TRUE,  ref_head),
2056         VIEW_(DIFF,   "diff",   &diff_ops,   TRUE,  ref_commit),
2057         VIEW_(LOG,    "log",    &log_ops,    TRUE,  ref_head),
2058         VIEW_(TREE,   "tree",   &tree_ops,   TRUE,  ref_commit),
2059         VIEW_(BLOB,   "blob",   &blob_ops,   TRUE,  ref_blob),
2060         VIEW_(BLAME,  "blame",  &blame_ops,  TRUE,  ref_commit),
2061         VIEW_(BRANCH, "branch", &branch_ops, TRUE,  ref_head),
2062         VIEW_(HELP,   "help",   &help_ops,   FALSE, ""),
2063         VIEW_(PAGER,  "pager",  &pager_ops,  FALSE, "stdin"),
2064         VIEW_(STATUS, "status", &status_ops, TRUE,  ""),
2065         VIEW_(STAGE,  "stage",  &stage_ops,  TRUE,  ""),
2066 };
2068 #define VIEW(req)       (&views[(req) - REQ_OFFSET - 1])
2069 #define VIEW_REQ(view)  ((view) - views + REQ_OFFSET + 1)
2071 #define foreach_view(view, i) \
2072         for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
2074 #define view_is_displayed(view) \
2075         (view == display[0] || view == display[1])
2078 enum line_graphic {
2079         LINE_GRAPHIC_VLINE
2080 };
2082 static chtype line_graphics[] = {
2083         /* LINE_GRAPHIC_VLINE: */ '|'
2084 };
2086 static inline void
2087 set_view_attr(struct view *view, enum line_type type)
2089         if (!view->curline->selected && view->curtype != type) {
2090                 wattrset(view->win, get_line_attr(type));
2091                 wchgat(view->win, -1, 0, type, NULL);
2092                 view->curtype = type;
2093         }
2096 static int
2097 draw_chars(struct view *view, enum line_type type, const char *string,
2098            int max_len, bool use_tilde)
2100         static char out_buffer[BUFSIZ * 2];
2101         int len = 0;
2102         int col = 0;
2103         int trimmed = FALSE;
2104         size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2106         if (max_len <= 0)
2107                 return 0;
2109         len = utf8_length(&string, skip, &col, max_len, &trimmed, use_tilde);
2111         set_view_attr(view, type);
2112         if (len > 0) {
2113                 if (opt_iconv_out != ICONV_NONE) {
2114                         ICONV_CONST char *inbuf = (ICONV_CONST char *) string;
2115                         size_t inlen = len + 1;
2117                         char *outbuf = out_buffer;
2118                         size_t outlen = sizeof(out_buffer);
2120                         size_t ret;
2122                         ret = iconv(opt_iconv_out, &inbuf, &inlen, &outbuf, &outlen);
2123                         if (ret != (size_t) -1) {
2124                                 string = out_buffer;
2125                                 len = sizeof(out_buffer) - outlen;
2126                         }
2127                 }
2129                 waddnstr(view->win, string, len);
2130         }
2131         if (trimmed && use_tilde) {
2132                 set_view_attr(view, LINE_DELIMITER);
2133                 waddch(view->win, '~');
2134                 col++;
2135         }
2137         return col;
2140 static int
2141 draw_space(struct view *view, enum line_type type, int max, int spaces)
2143         static char space[] = "                    ";
2144         int col = 0;
2146         spaces = MIN(max, spaces);
2148         while (spaces > 0) {
2149                 int len = MIN(spaces, sizeof(space) - 1);
2151                 col += draw_chars(view, type, space, len, FALSE);
2152                 spaces -= len;
2153         }
2155         return col;
2158 static bool
2159 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
2161         view->col += draw_chars(view, type, string, view->width + view->yoffset - view->col, trim);
2162         return view->width + view->yoffset <= view->col;
2165 static bool
2166 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
2168         size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2169         int max = view->width + view->yoffset - view->col;
2170         int i;
2172         if (max < size)
2173                 size = max;
2175         set_view_attr(view, type);
2176         /* Using waddch() instead of waddnstr() ensures that
2177          * they'll be rendered correctly for the cursor line. */
2178         for (i = skip; i < size; i++)
2179                 waddch(view->win, graphic[i]);
2181         view->col += size;
2182         if (size < max && skip <= size)
2183                 waddch(view->win, ' ');
2184         view->col++;
2186         return view->width + view->yoffset <= view->col;
2189 static bool
2190 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
2192         int max = MIN(view->width + view->yoffset - view->col, len);
2193         int col;
2195         if (text)
2196                 col = draw_chars(view, type, text, max - 1, trim);
2197         else
2198                 col = draw_space(view, type, max - 1, max - 1);
2200         view->col += col;
2201         view->col += draw_space(view, LINE_DEFAULT, max - col, max - col);
2202         return view->width + view->yoffset <= view->col;
2205 static bool
2206 draw_date(struct view *view, time_t *time)
2208         const char *date = time ? mkdate(time) : "";
2209         int cols = opt_date == DATE_SHORT ? DATE_SHORT_COLS : DATE_COLS;
2211         return draw_field(view, LINE_DATE, date, cols, FALSE);
2214 static bool
2215 draw_author(struct view *view, const char *author)
2217         bool trim = opt_author_cols == 0 || opt_author_cols > 5 || !author;
2219         if (!trim) {
2220                 static char initials[10];
2221                 size_t pos;
2223 #define is_initial_sep(c) (isspace(c) || ispunct(c) || (c) == '@')
2225                 memset(initials, 0, sizeof(initials));
2226                 for (pos = 0; *author && pos < opt_author_cols - 1; author++, pos++) {
2227                         while (is_initial_sep(*author))
2228                                 author++;
2229                         strncpy(&initials[pos], author, sizeof(initials) - 1 - pos);
2230                         while (*author && !is_initial_sep(author[1]))
2231                                 author++;
2232                 }
2234                 author = initials;
2235         }
2237         return draw_field(view, LINE_AUTHOR, author, opt_author_cols, trim);
2240 static bool
2241 draw_mode(struct view *view, mode_t mode)
2243         const char *str;
2245         if (S_ISDIR(mode))
2246                 str = "drwxr-xr-x";
2247         else if (S_ISLNK(mode))
2248                 str = "lrwxrwxrwx";
2249         else if (S_ISGITLINK(mode))
2250                 str = "m---------";
2251         else if (S_ISREG(mode) && mode & S_IXUSR)
2252                 str = "-rwxr-xr-x";
2253         else if (S_ISREG(mode))
2254                 str = "-rw-r--r--";
2255         else
2256                 str = "----------";
2258         return draw_field(view, LINE_MODE, str, STRING_SIZE("-rw-r--r-- "), FALSE);
2261 static bool
2262 draw_lineno(struct view *view, unsigned int lineno)
2264         char number[10];
2265         int digits3 = view->digits < 3 ? 3 : view->digits;
2266         int max = MIN(view->width + view->yoffset - view->col, digits3);
2267         char *text = NULL;
2269         lineno += view->offset + 1;
2270         if (lineno == 1 || (lineno % opt_num_interval) == 0) {
2271                 static char fmt[] = "%1ld";
2273                 fmt[1] = '0' + (view->digits <= 9 ? digits3 : 1);
2274                 if (string_format(number, fmt, lineno))
2275                         text = number;
2276         }
2277         if (text)
2278                 view->col += draw_chars(view, LINE_LINE_NUMBER, text, max, TRUE);
2279         else
2280                 view->col += draw_space(view, LINE_LINE_NUMBER, max, digits3);
2281         return draw_graphic(view, LINE_DEFAULT, &line_graphics[LINE_GRAPHIC_VLINE], 1);
2284 static bool
2285 draw_view_line(struct view *view, unsigned int lineno)
2287         struct line *line;
2288         bool selected = (view->offset + lineno == view->lineno);
2290         assert(view_is_displayed(view));
2292         if (view->offset + lineno >= view->lines)
2293                 return FALSE;
2295         line = &view->line[view->offset + lineno];
2297         wmove(view->win, lineno, 0);
2298         if (line->cleareol)
2299                 wclrtoeol(view->win);
2300         view->col = 0;
2301         view->curline = line;
2302         view->curtype = LINE_NONE;
2303         line->selected = FALSE;
2304         line->dirty = line->cleareol = 0;
2306         if (selected) {
2307                 set_view_attr(view, LINE_CURSOR);
2308                 line->selected = TRUE;
2309                 view->ops->select(view, line);
2310         }
2312         return view->ops->draw(view, line, lineno);
2315 static void
2316 redraw_view_dirty(struct view *view)
2318         bool dirty = FALSE;
2319         int lineno;
2321         for (lineno = 0; lineno < view->height; lineno++) {
2322                 if (view->offset + lineno >= view->lines)
2323                         break;
2324                 if (!view->line[view->offset + lineno].dirty)
2325                         continue;
2326                 dirty = TRUE;
2327                 if (!draw_view_line(view, lineno))
2328                         break;
2329         }
2331         if (!dirty)
2332                 return;
2333         wnoutrefresh(view->win);
2336 static void
2337 redraw_view_from(struct view *view, int lineno)
2339         assert(0 <= lineno && lineno < view->height);
2341         for (; lineno < view->height; lineno++) {
2342                 if (!draw_view_line(view, lineno))
2343                         break;
2344         }
2346         wnoutrefresh(view->win);
2349 static void
2350 redraw_view(struct view *view)
2352         werase(view->win);
2353         redraw_view_from(view, 0);
2357 static void
2358 update_view_title(struct view *view)
2360         char buf[SIZEOF_STR];
2361         char state[SIZEOF_STR];
2362         size_t bufpos = 0, statelen = 0;
2364         assert(view_is_displayed(view));
2366         if (view != VIEW(REQ_VIEW_STATUS) && view->lines) {
2367                 unsigned int view_lines = view->offset + view->height;
2368                 unsigned int lines = view->lines
2369                                    ? MIN(view_lines, view->lines) * 100 / view->lines
2370                                    : 0;
2372                 string_format_from(state, &statelen, " - %s %d of %d (%d%%)",
2373                                    view->ops->type,
2374                                    view->lineno + 1,
2375                                    view->lines,
2376                                    lines);
2378         }
2380         if (view->pipe) {
2381                 time_t secs = time(NULL) - view->start_time;
2383                 /* Three git seconds are a long time ... */
2384                 if (secs > 2)
2385                         string_format_from(state, &statelen, " loading %lds", secs);
2386         }
2388         string_format_from(buf, &bufpos, "[%s]", view->name);
2389         if (*view->ref && bufpos < view->width) {
2390                 size_t refsize = strlen(view->ref);
2391                 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2393                 if (minsize < view->width)
2394                         refsize = view->width - minsize + 7;
2395                 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2396         }
2398         if (statelen && bufpos < view->width) {
2399                 string_format_from(buf, &bufpos, "%s", state);
2400         }
2402         if (view == display[current_view])
2403                 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2404         else
2405                 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2407         mvwaddnstr(view->title, 0, 0, buf, bufpos);
2408         wclrtoeol(view->title);
2409         wnoutrefresh(view->title);
2412 static int
2413 apply_step(double step, int value)
2415         if (step >= 1)
2416                 return (int) step;
2417         value *= step + 0.01;
2418         return value ? value : 1;
2421 static void
2422 resize_display(void)
2424         int offset, i;
2425         struct view *base = display[0];
2426         struct view *view = display[1] ? display[1] : display[0];
2428         /* Setup window dimensions */
2430         getmaxyx(stdscr, base->height, base->width);
2432         /* Make room for the status window. */
2433         base->height -= 1;
2435         if (view != base) {
2436                 /* Horizontal split. */
2437                 view->width   = base->width;
2438                 view->height  = apply_step(opt_scale_split_view, base->height);
2439                 view->height  = MAX(view->height, MIN_VIEW_HEIGHT);
2440                 view->height  = MIN(view->height, base->height - MIN_VIEW_HEIGHT);
2441                 base->height -= view->height;
2443                 /* Make room for the title bar. */
2444                 view->height -= 1;
2445         }
2447         /* Make room for the title bar. */
2448         base->height -= 1;
2450         offset = 0;
2452         foreach_displayed_view (view, i) {
2453                 if (!view->win) {
2454                         view->win = newwin(view->height, 0, offset, 0);
2455                         if (!view->win)
2456                                 die("Failed to create %s view", view->name);
2458                         scrollok(view->win, FALSE);
2460                         view->title = newwin(1, 0, offset + view->height, 0);
2461                         if (!view->title)
2462                                 die("Failed to create title window");
2464                 } else {
2465                         wresize(view->win, view->height, view->width);
2466                         mvwin(view->win,   offset, 0);
2467                         mvwin(view->title, offset + view->height, 0);
2468                 }
2470                 offset += view->height + 1;
2471         }
2474 static void
2475 redraw_display(bool clear)
2477         struct view *view;
2478         int i;
2480         foreach_displayed_view (view, i) {
2481                 if (clear)
2482                         wclear(view->win);
2483                 redraw_view(view);
2484                 update_view_title(view);
2485         }
2488 static void
2489 toggle_enum_option_do(unsigned int *opt, const char *help,
2490                       const struct enum_map *map, size_t size)
2492         *opt = (*opt + 1) % size;
2493         redraw_display(FALSE);
2494         report("Displaying %s %s", enum_name(map[*opt]), help);
2497 #define toggle_enum_option(opt, help, map) \
2498         toggle_enum_option_do(opt, help, map, ARRAY_SIZE(map))
2500 #define toggle_date() toggle_enum_option(&opt_date, "dates", date_map)
2502 static void
2503 toggle_view_option(bool *option, const char *help)
2505         *option = !*option;
2506         redraw_display(FALSE);
2507         report("%sabling %s", *option ? "En" : "Dis", help);
2510 static void
2511 open_option_menu(void)
2513         const struct menu_item menu[] = {
2514                 { '.', "line numbers", &opt_line_number },
2515                 { 'D', "date display", &opt_date },
2516                 { 'A', "author display", &opt_author },
2517                 { 'g', "revision graph display", &opt_rev_graph },
2518                 { 'F', "reference display", &opt_show_refs },
2519                 { 0 }
2520         };
2521         int selected = 0;
2523         if (prompt_menu("Toggle option", menu, &selected)) {
2524                 if (menu[selected].data == &opt_date)
2525                         toggle_date();
2526                 else
2527                         toggle_view_option(menu[selected].data, menu[selected].text);
2528         }
2531 static void
2532 maximize_view(struct view *view)
2534         memset(display, 0, sizeof(display));
2535         current_view = 0;
2536         display[current_view] = view;
2537         resize_display();
2538         redraw_display(FALSE);
2539         report("");
2543 /*
2544  * Navigation
2545  */
2547 static bool
2548 goto_view_line(struct view *view, unsigned long offset, unsigned long lineno)
2550         if (lineno >= view->lines)
2551                 lineno = view->lines > 0 ? view->lines - 1 : 0;
2553         if (offset > lineno || offset + view->height <= lineno) {
2554                 unsigned long half = view->height / 2;
2556                 if (lineno > half)
2557                         offset = lineno - half;
2558                 else
2559                         offset = 0;
2560         }
2562         if (offset != view->offset || lineno != view->lineno) {
2563                 view->offset = offset;
2564                 view->lineno = lineno;
2565                 return TRUE;
2566         }
2568         return FALSE;
2571 /* Scrolling backend */
2572 static void
2573 do_scroll_view(struct view *view, int lines)
2575         bool redraw_current_line = FALSE;
2577         /* The rendering expects the new offset. */
2578         view->offset += lines;
2580         assert(0 <= view->offset && view->offset < view->lines);
2581         assert(lines);
2583         /* Move current line into the view. */
2584         if (view->lineno < view->offset) {
2585                 view->lineno = view->offset;
2586                 redraw_current_line = TRUE;
2587         } else if (view->lineno >= view->offset + view->height) {
2588                 view->lineno = view->offset + view->height - 1;
2589                 redraw_current_line = TRUE;
2590         }
2592         assert(view->offset <= view->lineno && view->lineno < view->lines);
2594         /* Redraw the whole screen if scrolling is pointless. */
2595         if (view->height < ABS(lines)) {
2596                 redraw_view(view);
2598         } else {
2599                 int line = lines > 0 ? view->height - lines : 0;
2600                 int end = line + ABS(lines);
2602                 scrollok(view->win, TRUE);
2603                 wscrl(view->win, lines);
2604                 scrollok(view->win, FALSE);
2606                 while (line < end && draw_view_line(view, line))
2607                         line++;
2609                 if (redraw_current_line)
2610                         draw_view_line(view, view->lineno - view->offset);
2611                 wnoutrefresh(view->win);
2612         }
2614         view->has_scrolled = TRUE;
2615         report("");
2618 /* Scroll frontend */
2619 static void
2620 scroll_view(struct view *view, enum request request)
2622         int lines = 1;
2624         assert(view_is_displayed(view));
2626         switch (request) {
2627         case REQ_SCROLL_LEFT:
2628                 if (view->yoffset == 0) {
2629                         report("Cannot scroll beyond the first column");
2630                         return;
2631                 }
2632                 if (view->yoffset <= apply_step(opt_hscroll, view->width))
2633                         view->yoffset = 0;
2634                 else
2635                         view->yoffset -= apply_step(opt_hscroll, view->width);
2636                 redraw_view_from(view, 0);
2637                 report("");
2638                 return;
2639         case REQ_SCROLL_RIGHT:
2640                 view->yoffset += apply_step(opt_hscroll, view->width);
2641                 redraw_view(view);
2642                 report("");
2643                 return;
2644         case REQ_SCROLL_PAGE_DOWN:
2645                 lines = view->height;
2646         case REQ_SCROLL_LINE_DOWN:
2647                 if (view->offset + lines > view->lines)
2648                         lines = view->lines - view->offset;
2650                 if (lines == 0 || view->offset + view->height >= view->lines) {
2651                         report("Cannot scroll beyond the last line");
2652                         return;
2653                 }
2654                 break;
2656         case REQ_SCROLL_PAGE_UP:
2657                 lines = view->height;
2658         case REQ_SCROLL_LINE_UP:
2659                 if (lines > view->offset)
2660                         lines = view->offset;
2662                 if (lines == 0) {
2663                         report("Cannot scroll beyond the first line");
2664                         return;
2665                 }
2667                 lines = -lines;
2668                 break;
2670         default:
2671                 die("request %d not handled in switch", request);
2672         }
2674         do_scroll_view(view, lines);
2677 /* Cursor moving */
2678 static void
2679 move_view(struct view *view, enum request request)
2681         int scroll_steps = 0;
2682         int steps;
2684         switch (request) {
2685         case REQ_MOVE_FIRST_LINE:
2686                 steps = -view->lineno;
2687                 break;
2689         case REQ_MOVE_LAST_LINE:
2690                 steps = view->lines - view->lineno - 1;
2691                 break;
2693         case REQ_MOVE_PAGE_UP:
2694                 steps = view->height > view->lineno
2695                       ? -view->lineno : -view->height;
2696                 break;
2698         case REQ_MOVE_PAGE_DOWN:
2699                 steps = view->lineno + view->height >= view->lines
2700                       ? view->lines - view->lineno - 1 : view->height;
2701                 break;
2703         case REQ_MOVE_UP:
2704                 steps = -1;
2705                 break;
2707         case REQ_MOVE_DOWN:
2708                 steps = 1;
2709                 break;
2711         default:
2712                 die("request %d not handled in switch", request);
2713         }
2715         if (steps <= 0 && view->lineno == 0) {
2716                 report("Cannot move beyond the first line");
2717                 return;
2719         } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2720                 report("Cannot move beyond the last line");
2721                 return;
2722         }
2724         /* Move the current line */
2725         view->lineno += steps;
2726         assert(0 <= view->lineno && view->lineno < view->lines);
2728         /* Check whether the view needs to be scrolled */
2729         if (view->lineno < view->offset ||
2730             view->lineno >= view->offset + view->height) {
2731                 scroll_steps = steps;
2732                 if (steps < 0 && -steps > view->offset) {
2733                         scroll_steps = -view->offset;
2735                 } else if (steps > 0) {
2736                         if (view->lineno == view->lines - 1 &&
2737                             view->lines > view->height) {
2738                                 scroll_steps = view->lines - view->offset - 1;
2739                                 if (scroll_steps >= view->height)
2740                                         scroll_steps -= view->height - 1;
2741                         }
2742                 }
2743         }
2745         if (!view_is_displayed(view)) {
2746                 view->offset += scroll_steps;
2747                 assert(0 <= view->offset && view->offset < view->lines);
2748                 view->ops->select(view, &view->line[view->lineno]);
2749                 return;
2750         }
2752         /* Repaint the old "current" line if we be scrolling */
2753         if (ABS(steps) < view->height)
2754                 draw_view_line(view, view->lineno - steps - view->offset);
2756         if (scroll_steps) {
2757                 do_scroll_view(view, scroll_steps);
2758                 return;
2759         }
2761         /* Draw the current line */
2762         draw_view_line(view, view->lineno - view->offset);
2764         wnoutrefresh(view->win);
2765         report("");
2769 /*
2770  * Searching
2771  */
2773 static void search_view(struct view *view, enum request request);
2775 static bool
2776 grep_text(struct view *view, const char *text[])
2778         regmatch_t pmatch;
2779         size_t i;
2781         for (i = 0; text[i]; i++)
2782                 if (*text[i] &&
2783                     regexec(view->regex, text[i], 1, &pmatch, 0) != REG_NOMATCH)
2784                         return TRUE;
2785         return FALSE;
2788 static void
2789 select_view_line(struct view *view, unsigned long lineno)
2791         unsigned long old_lineno = view->lineno;
2792         unsigned long old_offset = view->offset;
2794         if (goto_view_line(view, view->offset, lineno)) {
2795                 if (view_is_displayed(view)) {
2796                         if (old_offset != view->offset) {
2797                                 redraw_view(view);
2798                         } else {
2799                                 draw_view_line(view, old_lineno - view->offset);
2800                                 draw_view_line(view, view->lineno - view->offset);
2801                                 wnoutrefresh(view->win);
2802                         }
2803                 } else {
2804                         view->ops->select(view, &view->line[view->lineno]);
2805                 }
2806         }
2809 static void
2810 find_next(struct view *view, enum request request)
2812         unsigned long lineno = view->lineno;
2813         int direction;
2815         if (!*view->grep) {
2816                 if (!*opt_search)
2817                         report("No previous search");
2818                 else
2819                         search_view(view, request);
2820                 return;
2821         }
2823         switch (request) {
2824         case REQ_SEARCH:
2825         case REQ_FIND_NEXT:
2826                 direction = 1;
2827                 break;
2829         case REQ_SEARCH_BACK:
2830         case REQ_FIND_PREV:
2831                 direction = -1;
2832                 break;
2834         default:
2835                 return;
2836         }
2838         if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
2839                 lineno += direction;
2841         /* Note, lineno is unsigned long so will wrap around in which case it
2842          * will become bigger than view->lines. */
2843         for (; lineno < view->lines; lineno += direction) {
2844                 if (view->ops->grep(view, &view->line[lineno])) {
2845                         select_view_line(view, lineno);
2846                         report("Line %ld matches '%s'", lineno + 1, view->grep);
2847                         return;
2848                 }
2849         }
2851         report("No match found for '%s'", view->grep);
2854 static void
2855 search_view(struct view *view, enum request request)
2857         int regex_err;
2859         if (view->regex) {
2860                 regfree(view->regex);
2861                 *view->grep = 0;
2862         } else {
2863                 view->regex = calloc(1, sizeof(*view->regex));
2864                 if (!view->regex)
2865                         return;
2866         }
2868         regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
2869         if (regex_err != 0) {
2870                 char buf[SIZEOF_STR] = "unknown error";
2872                 regerror(regex_err, view->regex, buf, sizeof(buf));
2873                 report("Search failed: %s", buf);
2874                 return;
2875         }
2877         string_copy(view->grep, opt_search);
2879         find_next(view, request);
2882 /*
2883  * Incremental updating
2884  */
2886 static void
2887 reset_view(struct view *view)
2889         int i;
2891         for (i = 0; i < view->lines; i++)
2892                 free(view->line[i].data);
2893         free(view->line);
2895         view->p_offset = view->offset;
2896         view->p_yoffset = view->yoffset;
2897         view->p_lineno = view->lineno;
2899         view->line = NULL;
2900         view->offset = 0;
2901         view->yoffset = 0;
2902         view->lines  = 0;
2903         view->lineno = 0;
2904         view->vid[0] = 0;
2905         view->update_secs = 0;
2908 static void
2909 free_argv(const char *argv[])
2911         int argc;
2913         for (argc = 0; argv[argc]; argc++)
2914                 free((void *) argv[argc]);
2917 static bool
2918 format_argv(const char *dst_argv[], const char *src_argv[], enum format_flags flags)
2920         char buf[SIZEOF_STR];
2921         int argc;
2922         bool noreplace = flags == FORMAT_NONE;
2924         free_argv(dst_argv);
2926         for (argc = 0; src_argv[argc]; argc++) {
2927                 const char *arg = src_argv[argc];
2928                 size_t bufpos = 0;
2930                 while (arg) {
2931                         char *next = strstr(arg, "%(");
2932                         int len = next - arg;
2933                         const char *value;
2935                         if (!next || noreplace) {
2936                                 if (flags == FORMAT_DASH && !strcmp(arg, "--"))
2937                                         noreplace = TRUE;
2938                                 len = strlen(arg);
2939                                 value = "";
2941                         } else if (!prefixcmp(next, "%(directory)")) {
2942                                 value = opt_path;
2944                         } else if (!prefixcmp(next, "%(file)")) {
2945                                 value = opt_file;
2947                         } else if (!prefixcmp(next, "%(ref)")) {
2948                                 value = *opt_ref ? opt_ref : "HEAD";
2950                         } else if (!prefixcmp(next, "%(head)")) {
2951                                 value = ref_head;
2953                         } else if (!prefixcmp(next, "%(commit)")) {
2954                                 value = ref_commit;
2956                         } else if (!prefixcmp(next, "%(blob)")) {
2957                                 value = ref_blob;
2959                         } else {
2960                                 report("Unknown replacement: `%s`", next);
2961                                 return FALSE;
2962                         }
2964                         if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
2965                                 return FALSE;
2967                         arg = next && !noreplace ? strchr(next, ')') + 1 : NULL;
2968                 }
2970                 dst_argv[argc] = strdup(buf);
2971                 if (!dst_argv[argc])
2972                         break;
2973         }
2975         dst_argv[argc] = NULL;
2977         return src_argv[argc] == NULL;
2980 static bool
2981 restore_view_position(struct view *view)
2983         if (!view->p_restore || (view->pipe && view->lines <= view->p_lineno))
2984                 return FALSE;
2986         /* Changing the view position cancels the restoring. */
2987         /* FIXME: Changing back to the first line is not detected. */
2988         if (view->offset != 0 || view->lineno != 0) {
2989                 view->p_restore = FALSE;
2990                 return FALSE;
2991         }
2993         if (goto_view_line(view, view->p_offset, view->p_lineno) &&
2994             view_is_displayed(view))
2995                 werase(view->win);
2997         view->yoffset = view->p_yoffset;
2998         view->p_restore = FALSE;
3000         return TRUE;
3003 static void
3004 end_update(struct view *view, bool force)
3006         if (!view->pipe)
3007                 return;
3008         while (!view->ops->read(view, NULL))
3009                 if (!force)
3010                         return;
3011         set_nonblocking_input(FALSE);
3012         if (force)
3013                 kill_io(view->pipe);
3014         done_io(view->pipe);
3015         view->pipe = NULL;
3018 static void
3019 setup_update(struct view *view, const char *vid)
3021         set_nonblocking_input(TRUE);
3022         reset_view(view);
3023         string_copy_rev(view->vid, vid);
3024         view->pipe = &view->io;
3025         view->start_time = time(NULL);
3028 static bool
3029 prepare_update(struct view *view, const char *argv[], const char *dir,
3030                enum format_flags flags)
3032         if (view->pipe)
3033                 end_update(view, TRUE);
3034         return init_io_rd(&view->io, argv, dir, flags);
3037 static bool
3038 prepare_update_file(struct view *view, const char *name)
3040         if (view->pipe)
3041                 end_update(view, TRUE);
3042         return io_open(&view->io, "%s", name);
3045 static bool
3046 begin_update(struct view *view, bool refresh)
3048         if (view->pipe)
3049                 end_update(view, TRUE);
3051         if (!refresh) {
3052                 if (view->ops->prepare) {
3053                         if (!view->ops->prepare(view))
3054                                 return FALSE;
3055                 } else if (!init_io_rd(&view->io, view->ops->argv, NULL, FORMAT_ALL)) {
3056                         return FALSE;
3057                 }
3059                 /* Put the current ref_* value to the view title ref
3060                  * member. This is needed by the blob view. Most other
3061                  * views sets it automatically after loading because the
3062                  * first line is a commit line. */
3063                 string_copy_rev(view->ref, view->id);
3064         }
3066         if (!start_io(&view->io))
3067                 return FALSE;
3069         setup_update(view, view->id);
3071         return TRUE;
3074 static bool
3075 update_view(struct view *view)
3077         char out_buffer[BUFSIZ * 2];
3078         char *line;
3079         /* Clear the view and redraw everything since the tree sorting
3080          * might have rearranged things. */
3081         bool redraw = view->lines == 0;
3082         bool can_read = TRUE;
3084         if (!view->pipe)
3085                 return TRUE;
3087         if (!io_can_read(view->pipe)) {
3088                 if (view->lines == 0 && view_is_displayed(view)) {
3089                         time_t secs = time(NULL) - view->start_time;
3091                         if (secs > 1 && secs > view->update_secs) {
3092                                 if (view->update_secs == 0)
3093                                         redraw_view(view);
3094                                 update_view_title(view);
3095                                 view->update_secs = secs;
3096                         }
3097                 }
3098                 return TRUE;
3099         }
3101         for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
3102                 if (opt_iconv_in != ICONV_NONE) {
3103                         ICONV_CONST char *inbuf = line;
3104                         size_t inlen = strlen(line) + 1;
3106                         char *outbuf = out_buffer;
3107                         size_t outlen = sizeof(out_buffer);
3109                         size_t ret;
3111                         ret = iconv(opt_iconv_in, &inbuf, &inlen, &outbuf, &outlen);
3112                         if (ret != (size_t) -1)
3113                                 line = out_buffer;
3114                 }
3116                 if (!view->ops->read(view, line)) {
3117                         report("Allocation failure");
3118                         end_update(view, TRUE);
3119                         return FALSE;
3120                 }
3121         }
3123         {
3124                 unsigned long lines = view->lines;
3125                 int digits;
3127                 for (digits = 0; lines; digits++)
3128                         lines /= 10;
3130                 /* Keep the displayed view in sync with line number scaling. */
3131                 if (digits != view->digits) {
3132                         view->digits = digits;
3133                         if (opt_line_number || view == VIEW(REQ_VIEW_BLAME))
3134                                 redraw = TRUE;
3135                 }
3136         }
3138         if (io_error(view->pipe)) {
3139                 report("Failed to read: %s", io_strerror(view->pipe));
3140                 end_update(view, TRUE);
3142         } else if (io_eof(view->pipe)) {
3143                 report("");
3144                 end_update(view, FALSE);
3145         }
3147         if (restore_view_position(view))
3148                 redraw = TRUE;
3150         if (!view_is_displayed(view))
3151                 return TRUE;
3153         if (redraw)
3154                 redraw_view_from(view, 0);
3155         else
3156                 redraw_view_dirty(view);
3158         /* Update the title _after_ the redraw so that if the redraw picks up a
3159          * commit reference in view->ref it'll be available here. */
3160         update_view_title(view);
3161         return TRUE;
3164 DEFINE_ALLOCATOR(realloc_lines, struct line, 256)
3166 static struct line *
3167 add_line_data(struct view *view, void *data, enum line_type type)
3169         struct line *line;
3171         if (!realloc_lines(&view->line, view->lines, 1))
3172                 return NULL;
3174         line = &view->line[view->lines++];
3175         memset(line, 0, sizeof(*line));
3176         line->type = type;
3177         line->data = data;
3178         line->dirty = 1;
3180         return line;
3183 static struct line *
3184 add_line_text(struct view *view, const char *text, enum line_type type)
3186         char *data = text ? strdup(text) : NULL;
3188         return data ? add_line_data(view, data, type) : NULL;
3191 static struct line *
3192 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
3194         char buf[SIZEOF_STR];
3195         va_list args;
3197         va_start(args, fmt);
3198         if (vsnprintf(buf, sizeof(buf), fmt, args) >= sizeof(buf))
3199                 buf[0] = 0;
3200         va_end(args);
3202         return buf[0] ? add_line_text(view, buf, type) : NULL;
3205 /*
3206  * View opening
3207  */
3209 enum open_flags {
3210         OPEN_DEFAULT = 0,       /* Use default view switching. */
3211         OPEN_SPLIT = 1,         /* Split current view. */
3212         OPEN_RELOAD = 4,        /* Reload view even if it is the current. */
3213         OPEN_REFRESH = 16,      /* Refresh view using previous command. */
3214         OPEN_PREPARED = 32,     /* Open already prepared command. */
3215 };
3217 static void
3218 open_view(struct view *prev, enum request request, enum open_flags flags)
3220         bool split = !!(flags & OPEN_SPLIT);
3221         bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
3222         bool nomaximize = !!(flags & OPEN_REFRESH);
3223         struct view *view = VIEW(request);
3224         int nviews = displayed_views();
3225         struct view *base_view = display[0];
3227         if (view == prev && nviews == 1 && !reload) {
3228                 report("Already in %s view", view->name);
3229                 return;
3230         }
3232         if (view->git_dir && !opt_git_dir[0]) {
3233                 report("The %s view is disabled in pager view", view->name);
3234                 return;
3235         }
3237         if (split) {
3238                 display[1] = view;
3239                 current_view = 1;
3240         } else if (!nomaximize) {
3241                 /* Maximize the current view. */
3242                 memset(display, 0, sizeof(display));
3243                 current_view = 0;
3244                 display[current_view] = view;
3245         }
3247         /* No parent signals that this is the first loaded view. */
3248         if (prev && view != prev) {
3249                 view->parent = prev;
3250         }
3252         /* Resize the view when switching between split- and full-screen,
3253          * or when switching between two different full-screen views. */
3254         if (nviews != displayed_views() ||
3255             (nviews == 1 && base_view != display[0]))
3256                 resize_display();
3258         if (view->ops->open) {
3259                 if (view->pipe)
3260                         end_update(view, TRUE);
3261                 if (!view->ops->open(view)) {
3262                         report("Failed to load %s view", view->name);
3263                         return;
3264                 }
3265                 restore_view_position(view);
3267         } else if ((reload || strcmp(view->vid, view->id)) &&
3268                    !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
3269                 report("Failed to load %s view", view->name);
3270                 return;
3271         }
3273         if (split && prev->lineno - prev->offset >= prev->height) {
3274                 /* Take the title line into account. */
3275                 int lines = prev->lineno - prev->offset - prev->height + 1;
3277                 /* Scroll the view that was split if the current line is
3278                  * outside the new limited view. */
3279                 do_scroll_view(prev, lines);
3280         }
3282         if (prev && view != prev && split && view_is_displayed(prev)) {
3283                 /* "Blur" the previous view. */
3284                 update_view_title(prev);
3285         }
3287         if (view->pipe && view->lines == 0) {
3288                 /* Clear the old view and let the incremental updating refill
3289                  * the screen. */
3290                 werase(view->win);
3291                 view->p_restore = flags & (OPEN_RELOAD | OPEN_REFRESH);
3292                 report("");
3293         } else if (view_is_displayed(view)) {
3294                 redraw_view(view);
3295                 report("");
3296         }
3299 static void
3300 open_external_viewer(const char *argv[], const char *dir)
3302         def_prog_mode();           /* save current tty modes */
3303         endwin();                  /* restore original tty modes */
3304         run_io_fg(argv, dir);
3305         fprintf(stderr, "Press Enter to continue");
3306         getc(opt_tty);
3307         reset_prog_mode();
3308         redraw_display(TRUE);
3311 static void
3312 open_mergetool(const char *file)
3314         const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
3316         open_external_viewer(mergetool_argv, opt_cdup);
3319 static void
3320 open_editor(bool from_root, const char *file)
3322         const char *editor_argv[] = { "vi", file, NULL };
3323         const char *editor;
3325         editor = getenv("GIT_EDITOR");
3326         if (!editor && *opt_editor)
3327                 editor = opt_editor;
3328         if (!editor)
3329                 editor = getenv("VISUAL");
3330         if (!editor)
3331                 editor = getenv("EDITOR");
3332         if (!editor)
3333                 editor = "vi";
3335         editor_argv[0] = editor;
3336         open_external_viewer(editor_argv, from_root ? opt_cdup : NULL);
3339 static void
3340 open_run_request(enum request request)
3342         struct run_request *req = get_run_request(request);
3343         const char *argv[ARRAY_SIZE(req->argv)] = { NULL };
3345         if (!req) {
3346                 report("Unknown run request");
3347                 return;
3348         }
3350         if (format_argv(argv, req->argv, FORMAT_ALL))
3351                 open_external_viewer(argv, NULL);
3352         free_argv(argv);
3355 /*
3356  * User request switch noodle
3357  */
3359 static int
3360 view_driver(struct view *view, enum request request)
3362         int i;
3364         if (request == REQ_NONE)
3365                 return TRUE;
3367         if (request > REQ_NONE) {
3368                 open_run_request(request);
3369                 /* FIXME: When all views can refresh always do this. */
3370                 if (view == VIEW(REQ_VIEW_STATUS) ||
3371                     view == VIEW(REQ_VIEW_MAIN) ||
3372                     view == VIEW(REQ_VIEW_LOG) ||
3373                     view == VIEW(REQ_VIEW_BRANCH) ||
3374                     view == VIEW(REQ_VIEW_STAGE))
3375                         request = REQ_REFRESH;
3376                 else
3377                         return TRUE;
3378         }
3380         if (view && view->lines) {
3381                 request = view->ops->request(view, request, &view->line[view->lineno]);
3382                 if (request == REQ_NONE)
3383                         return TRUE;
3384         }
3386         switch (request) {
3387         case REQ_MOVE_UP:
3388         case REQ_MOVE_DOWN:
3389         case REQ_MOVE_PAGE_UP:
3390         case REQ_MOVE_PAGE_DOWN:
3391         case REQ_MOVE_FIRST_LINE:
3392         case REQ_MOVE_LAST_LINE:
3393                 move_view(view, request);
3394                 break;
3396         case REQ_SCROLL_LEFT:
3397         case REQ_SCROLL_RIGHT:
3398         case REQ_SCROLL_LINE_DOWN:
3399         case REQ_SCROLL_LINE_UP:
3400         case REQ_SCROLL_PAGE_DOWN:
3401         case REQ_SCROLL_PAGE_UP:
3402                 scroll_view(view, request);
3403                 break;
3405         case REQ_VIEW_BLAME:
3406                 if (!opt_file[0]) {
3407                         report("No file chosen, press %s to open tree view",
3408                                get_key(view->keymap, REQ_VIEW_TREE));
3409                         break;
3410                 }
3411                 open_view(view, request, OPEN_DEFAULT);
3412                 break;
3414         case REQ_VIEW_BLOB:
3415                 if (!ref_blob[0]) {
3416                         report("No file chosen, press %s to open tree view",
3417                                get_key(view->keymap, REQ_VIEW_TREE));
3418                         break;
3419                 }
3420                 open_view(view, request, OPEN_DEFAULT);
3421                 break;
3423         case REQ_VIEW_PAGER:
3424                 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3425                         report("No pager content, press %s to run command from prompt",
3426                                get_key(view->keymap, REQ_PROMPT));
3427                         break;
3428                 }
3429                 open_view(view, request, OPEN_DEFAULT);
3430                 break;
3432         case REQ_VIEW_STAGE:
3433                 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3434                         report("No stage content, press %s to open the status view and choose file",
3435                                get_key(view->keymap, REQ_VIEW_STATUS));
3436                         break;
3437                 }
3438                 open_view(view, request, OPEN_DEFAULT);
3439                 break;
3441         case REQ_VIEW_STATUS:
3442                 if (opt_is_inside_work_tree == FALSE) {
3443                         report("The status view requires a working tree");
3444                         break;
3445                 }
3446                 open_view(view, request, OPEN_DEFAULT);
3447                 break;
3449         case REQ_VIEW_MAIN:
3450         case REQ_VIEW_DIFF:
3451         case REQ_VIEW_LOG:
3452         case REQ_VIEW_TREE:
3453         case REQ_VIEW_HELP:
3454         case REQ_VIEW_BRANCH:
3455                 open_view(view, request, OPEN_DEFAULT);
3456                 break;
3458         case REQ_NEXT:
3459         case REQ_PREVIOUS:
3460                 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3462                 if ((view == VIEW(REQ_VIEW_DIFF) &&
3463                      view->parent == VIEW(REQ_VIEW_MAIN)) ||
3464                    (view == VIEW(REQ_VIEW_DIFF) &&
3465                      view->parent == VIEW(REQ_VIEW_BLAME)) ||
3466                    (view == VIEW(REQ_VIEW_STAGE) &&
3467                      view->parent == VIEW(REQ_VIEW_STATUS)) ||
3468                    (view == VIEW(REQ_VIEW_BLOB) &&
3469                      view->parent == VIEW(REQ_VIEW_TREE)) ||
3470                    (view == VIEW(REQ_VIEW_MAIN) &&
3471                      view->parent == VIEW(REQ_VIEW_BRANCH))) {
3472                         int line;
3474                         view = view->parent;
3475                         line = view->lineno;
3476                         move_view(view, request);
3477                         if (view_is_displayed(view))
3478                                 update_view_title(view);
3479                         if (line != view->lineno)
3480                                 view->ops->request(view, REQ_ENTER,
3481                                                    &view->line[view->lineno]);
3483                 } else {
3484                         move_view(view, request);
3485                 }
3486                 break;
3488         case REQ_VIEW_NEXT:
3489         {
3490                 int nviews = displayed_views();
3491                 int next_view = (current_view + 1) % nviews;
3493                 if (next_view == current_view) {
3494                         report("Only one view is displayed");
3495                         break;
3496                 }
3498                 current_view = next_view;
3499                 /* Blur out the title of the previous view. */
3500                 update_view_title(view);
3501                 report("");
3502                 break;
3503         }
3504         case REQ_REFRESH:
3505                 report("Refreshing is not yet supported for the %s view", view->name);
3506                 break;
3508         case REQ_MAXIMIZE:
3509                 if (displayed_views() == 2)
3510                         maximize_view(view);
3511                 break;
3513         case REQ_OPTIONS:
3514                 open_option_menu();
3515                 break;
3517         case REQ_TOGGLE_LINENO:
3518                 toggle_view_option(&opt_line_number, "line numbers");
3519                 break;
3521         case REQ_TOGGLE_DATE:
3522                 toggle_date();
3523                 break;
3525         case REQ_TOGGLE_AUTHOR:
3526                 toggle_view_option(&opt_author, "author display");
3527                 break;
3529         case REQ_TOGGLE_REV_GRAPH:
3530                 toggle_view_option(&opt_rev_graph, "revision graph display");
3531                 break;
3533         case REQ_TOGGLE_REFS:
3534                 toggle_view_option(&opt_show_refs, "reference display");
3535                 break;
3537         case REQ_TOGGLE_SORT_FIELD:
3538         case REQ_TOGGLE_SORT_ORDER:
3539                 report("Sorting is not yet supported for the %s view", view->name);
3540                 break;
3542         case REQ_SEARCH:
3543         case REQ_SEARCH_BACK:
3544                 search_view(view, request);
3545                 break;
3547         case REQ_FIND_NEXT:
3548         case REQ_FIND_PREV:
3549                 find_next(view, request);
3550                 break;
3552         case REQ_STOP_LOADING:
3553                 for (i = 0; i < ARRAY_SIZE(views); i++) {
3554                         view = &views[i];
3555                         if (view->pipe)
3556                                 report("Stopped loading the %s view", view->name),
3557                         end_update(view, TRUE);
3558                 }
3559                 break;
3561         case REQ_SHOW_VERSION:
3562                 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3563                 return TRUE;
3565         case REQ_SCREEN_REDRAW:
3566                 redraw_display(TRUE);
3567                 break;
3569         case REQ_EDIT:
3570                 report("Nothing to edit");
3571                 break;
3573         case REQ_ENTER:
3574                 report("Nothing to enter");
3575                 break;
3577         case REQ_VIEW_CLOSE:
3578                 /* XXX: Mark closed views by letting view->parent point to the
3579                  * view itself. Parents to closed view should never be
3580                  * followed. */
3581                 if (view->parent &&
3582                     view->parent->parent != view->parent) {
3583                         maximize_view(view->parent);
3584                         view->parent = view;
3585                         break;
3586                 }
3587                 /* Fall-through */
3588         case REQ_QUIT:
3589                 return FALSE;
3591         default:
3592                 report("Unknown key, press %s for help",
3593                        get_key(view->keymap, REQ_VIEW_HELP));
3594                 return TRUE;
3595         }
3597         return TRUE;
3601 /*
3602  * View backend utilities
3603  */
3605 enum sort_field {
3606         ORDERBY_NAME,
3607         ORDERBY_DATE,
3608         ORDERBY_AUTHOR,
3609 };
3611 struct sort_state {
3612         const enum sort_field *fields;
3613         size_t size, current;
3614         bool reverse;
3615 };
3617 #define SORT_STATE(fields) { fields, ARRAY_SIZE(fields), 0 }
3618 #define get_sort_field(state) ((state).fields[(state).current])
3619 #define sort_order(state, result) ((state).reverse ? -(result) : (result))
3621 static void
3622 sort_view(struct view *view, enum request request, struct sort_state *state,
3623           int (*compare)(const void *, const void *))
3625         switch (request) {
3626         case REQ_TOGGLE_SORT_FIELD:
3627                 state->current = (state->current + 1) % state->size;
3628                 break;
3630         case REQ_TOGGLE_SORT_ORDER:
3631                 state->reverse = !state->reverse;
3632                 break;
3633         default:
3634                 die("Not a sort request");
3635         }
3637         qsort(view->line, view->lines, sizeof(*view->line), compare);
3638         redraw_view(view);
3641 DEFINE_ALLOCATOR(realloc_authors, const char *, 256)
3643 /* Small author cache to reduce memory consumption. It uses binary
3644  * search to lookup or find place to position new entries. No entries
3645  * are ever freed. */
3646 static const char *
3647 get_author(const char *name)
3649         static const char **authors;
3650         static size_t authors_size;
3651         int from = 0, to = authors_size - 1;
3653         while (from <= to) {
3654                 size_t pos = (to + from) / 2;
3655                 int cmp = strcmp(name, authors[pos]);
3657                 if (!cmp)
3658                         return authors[pos];
3660                 if (cmp < 0)
3661                         to = pos - 1;
3662                 else
3663                         from = pos + 1;
3664         }
3666         if (!realloc_authors(&authors, authors_size, 1))
3667                 return NULL;
3668         name = strdup(name);
3669         if (!name)
3670                 return NULL;
3672         memmove(authors + from + 1, authors + from, (authors_size - from) * sizeof(*authors));
3673         authors[from] = name;
3674         authors_size++;
3676         return name;
3679 static void
3680 parse_timezone(time_t *time, const char *zone)
3682         long tz;
3684         tz  = ('0' - zone[1]) * 60 * 60 * 10;
3685         tz += ('0' - zone[2]) * 60 * 60;
3686         tz += ('0' - zone[3]) * 60;
3687         tz += ('0' - zone[4]);
3689         if (zone[0] == '-')
3690                 tz = -tz;
3692         *time -= tz;
3695 /* Parse author lines where the name may be empty:
3696  *      author  <email@address.tld> 1138474660 +0100
3697  */
3698 static void
3699 parse_author_line(char *ident, const char **author, time_t *time)
3701         char *nameend = strchr(ident, '<');
3702         char *emailend = strchr(ident, '>');
3704         if (nameend && emailend)
3705                 *nameend = *emailend = 0;
3706         ident = chomp_string(ident);
3707         if (!*ident) {
3708                 if (nameend)
3709                         ident = chomp_string(nameend + 1);
3710                 if (!*ident)
3711                         ident = "Unknown";
3712         }
3714         *author = get_author(ident);
3716         /* Parse epoch and timezone */
3717         if (emailend && emailend[1] == ' ') {
3718                 char *secs = emailend + 2;
3719                 char *zone = strchr(secs, ' ');
3721                 *time = (time_t) atol(secs);
3723                 if (zone && strlen(zone) == STRING_SIZE(" +0700"))
3724                         parse_timezone(time, zone + 1);
3725         }
3728 static bool
3729 open_commit_parent_menu(char buf[SIZEOF_STR], int *parents)
3731         char rev[SIZEOF_REV];
3732         const char *revlist_argv[] = {
3733                 "git", "log", "--no-color", "-1", "--pretty=format:%s", rev, NULL
3734         };
3735         struct menu_item *items;
3736         char text[SIZEOF_STR];
3737         bool ok = TRUE;
3738         int i;
3740         items = calloc(*parents + 1, sizeof(*items));
3741         if (!items)
3742                 return FALSE;
3744         for (i = 0; i < *parents; i++) {
3745                 string_copy_rev(rev, &buf[SIZEOF_REV * i]);
3746                 if (!run_io_buf(revlist_argv, text, sizeof(text)) ||
3747                     !(items[i].text = strdup(text))) {
3748                         ok = FALSE;
3749                         break;
3750                 }
3751         }
3753         if (ok) {
3754                 *parents = 0;
3755                 ok = prompt_menu("Select parent", items, parents);
3756         }
3757         for (i = 0; items[i].text; i++)
3758                 free((char *) items[i].text);
3759         free(items);
3760         return ok;
3763 static bool
3764 select_commit_parent(const char *id, char rev[SIZEOF_REV], const char *path)
3766         char buf[SIZEOF_STR * 4];
3767         const char *revlist_argv[] = {
3768                 "git", "log", "--no-color", "-1",
3769                         "--pretty=format:%P", id, "--", path, NULL
3770         };
3771         int parents;
3773         if (!run_io_buf(revlist_argv, buf, sizeof(buf)) ||
3774             (parents = strlen(buf) / 40) < 0) {
3775                 report("Failed to get parent information");
3776                 return FALSE;
3778         } else if (parents == 0) {
3779                 if (path)
3780                         report("Path '%s' does not exist in the parent", path);
3781                 else
3782                         report("The selected commit has no parents");
3783                 return FALSE;
3784         }
3786         if (parents > 1 && !open_commit_parent_menu(buf, &parents))
3787                 return FALSE;
3789         string_copy_rev(rev, &buf[41 * parents]);
3790         return TRUE;
3793 /*
3794  * Pager backend
3795  */
3797 static bool
3798 pager_draw(struct view *view, struct line *line, unsigned int lineno)
3800         char text[SIZEOF_STR];
3802         if (opt_line_number && draw_lineno(view, lineno))
3803                 return TRUE;
3805         string_expand(text, sizeof(text), line->data, opt_tab_size);
3806         draw_text(view, line->type, text, TRUE);
3807         return TRUE;
3810 static bool
3811 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
3813         const char *describe_argv[] = { "git", "describe", commit_id, NULL };
3814         char ref[SIZEOF_STR];
3816         if (!run_io_buf(describe_argv, ref, sizeof(ref)) || !*ref)
3817                 return TRUE;
3819         /* This is the only fatal call, since it can "corrupt" the buffer. */
3820         if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
3821                 return FALSE;
3823         return TRUE;
3826 static void
3827 add_pager_refs(struct view *view, struct line *line)
3829         char buf[SIZEOF_STR];
3830         char *commit_id = (char *)line->data + STRING_SIZE("commit ");
3831         struct ref_list *list;
3832         size_t bufpos = 0, i;
3833         const char *sep = "Refs: ";
3834         bool is_tag = FALSE;
3836         assert(line->type == LINE_COMMIT);
3838         list = get_ref_list(commit_id);
3839         if (!list) {
3840                 if (view == VIEW(REQ_VIEW_DIFF))
3841                         goto try_add_describe_ref;
3842                 return;
3843         }
3845         for (i = 0; i < list->size; i++) {
3846                 struct ref *ref = list->refs[i];
3847                 const char *fmt = ref->tag    ? "%s[%s]" :
3848                                   ref->remote ? "%s<%s>" : "%s%s";
3850                 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
3851                         return;
3852                 sep = ", ";
3853                 if (ref->tag)
3854                         is_tag = TRUE;
3855         }
3857         if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
3858 try_add_describe_ref:
3859                 /* Add <tag>-g<commit_id> "fake" reference. */
3860                 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
3861                         return;
3862         }
3864         if (bufpos == 0)
3865                 return;
3867         add_line_text(view, buf, LINE_PP_REFS);
3870 static bool
3871 pager_read(struct view *view, char *data)
3873         struct line *line;
3875         if (!data)
3876                 return TRUE;
3878         line = add_line_text(view, data, get_line_type(data));
3879         if (!line)
3880                 return FALSE;
3882         if (line->type == LINE_COMMIT &&
3883             (view == VIEW(REQ_VIEW_DIFF) ||
3884              view == VIEW(REQ_VIEW_LOG)))
3885                 add_pager_refs(view, line);
3887         return TRUE;
3890 static enum request
3891 pager_request(struct view *view, enum request request, struct line *line)
3893         int split = 0;
3895         if (request != REQ_ENTER)
3896                 return request;
3898         if (line->type == LINE_COMMIT &&
3899            (view == VIEW(REQ_VIEW_LOG) ||
3900             view == VIEW(REQ_VIEW_PAGER))) {
3901                 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
3902                 split = 1;
3903         }
3905         /* Always scroll the view even if it was split. That way
3906          * you can use Enter to scroll through the log view and
3907          * split open each commit diff. */
3908         scroll_view(view, REQ_SCROLL_LINE_DOWN);
3910         /* FIXME: A minor workaround. Scrolling the view will call report("")
3911          * but if we are scrolling a non-current view this won't properly
3912          * update the view title. */
3913         if (split)
3914                 update_view_title(view);
3916         return REQ_NONE;
3919 static bool
3920 pager_grep(struct view *view, struct line *line)
3922         const char *text[] = { line->data, NULL };
3924         return grep_text(view, text);
3927 static void
3928 pager_select(struct view *view, struct line *line)
3930         if (line->type == LINE_COMMIT) {
3931                 char *text = (char *)line->data + STRING_SIZE("commit ");
3933                 if (view != VIEW(REQ_VIEW_PAGER))
3934                         string_copy_rev(view->ref, text);
3935                 string_copy_rev(ref_commit, text);
3936         }
3939 static struct view_ops pager_ops = {
3940         "line",
3941         NULL,
3942         NULL,
3943         pager_read,
3944         pager_draw,
3945         pager_request,
3946         pager_grep,
3947         pager_select,
3948 };
3950 static const char *log_argv[SIZEOF_ARG] = {
3951         "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
3952 };
3954 static enum request
3955 log_request(struct view *view, enum request request, struct line *line)
3957         switch (request) {
3958         case REQ_REFRESH:
3959                 load_refs();
3960                 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
3961                 return REQ_NONE;
3962         default:
3963                 return pager_request(view, request, line);
3964         }
3967 static struct view_ops log_ops = {
3968         "line",
3969         log_argv,
3970         NULL,
3971         pager_read,
3972         pager_draw,
3973         log_request,
3974         pager_grep,
3975         pager_select,
3976 };
3978 static const char *diff_argv[SIZEOF_ARG] = {
3979         "git", "show", "--pretty=fuller", "--no-color", "--root",
3980                 "--patch-with-stat", "--find-copies-harder", "-C", "%(commit)", NULL
3981 };
3983 static struct view_ops diff_ops = {
3984         "line",
3985         diff_argv,
3986         NULL,
3987         pager_read,
3988         pager_draw,
3989         pager_request,
3990         pager_grep,
3991         pager_select,
3992 };
3994 /*
3995  * Help backend
3996  */
3998 static bool help_keymap_hidden[ARRAY_SIZE(keymap_table)];
4000 static bool
4001 help_open_keymap_title(struct view *view, enum keymap keymap)
4003         struct line *line;
4005         line = add_line_format(view, LINE_HELP_KEYMAP, "[%c] %s bindings",
4006                                help_keymap_hidden[keymap] ? '+' : '-',
4007                                enum_name(keymap_table[keymap]));
4008         if (line)
4009                 line->other = keymap;
4011         return help_keymap_hidden[keymap];
4014 static void
4015 help_open_keymap(struct view *view, enum keymap keymap)
4017         const char *group = NULL;
4018         char buf[SIZEOF_STR];
4019         size_t bufpos;
4020         bool add_title = TRUE;
4021         int i;
4023         for (i = 0; i < ARRAY_SIZE(req_info); i++) {
4024                 const char *key = NULL;
4026                 if (req_info[i].request == REQ_NONE)
4027                         continue;
4029                 if (!req_info[i].request) {
4030                         group = req_info[i].help;
4031                         continue;
4032                 }
4034                 key = get_keys(keymap, req_info[i].request, TRUE);
4035                 if (!key || !*key)
4036                         continue;
4038                 if (add_title && help_open_keymap_title(view, keymap))
4039                         return;
4040                 add_title = false;
4042                 if (group) {
4043                         add_line_text(view, group, LINE_HELP_GROUP);
4044                         group = NULL;
4045                 }
4047                 add_line_format(view, LINE_DEFAULT, "    %-25s %-20s %s", key,
4048                                 enum_name(req_info[i]), req_info[i].help);
4049         }
4051         group = "External commands:";
4053         for (i = 0; i < run_requests; i++) {
4054                 struct run_request *req = get_run_request(REQ_NONE + i + 1);
4055                 const char *key;
4056                 int argc;
4058                 if (!req || req->keymap != keymap)
4059                         continue;
4061                 key = get_key_name(req->key);
4062                 if (!*key)
4063                         key = "(no key defined)";
4065                 if (add_title && help_open_keymap_title(view, keymap))
4066                         return;
4067                 if (group) {
4068                         add_line_text(view, group, LINE_HELP_GROUP);
4069                         group = NULL;
4070                 }
4072                 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
4073                         if (!string_format_from(buf, &bufpos, "%s%s",
4074                                                 argc ? " " : "", req->argv[argc]))
4075                                 return;
4077                 add_line_format(view, LINE_DEFAULT, "    %-25s `%s`", key, buf);
4078         }
4081 static bool
4082 help_open(struct view *view)
4084         enum keymap keymap;
4086         reset_view(view);
4087         add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
4088         add_line_text(view, "", LINE_DEFAULT);
4090         for (keymap = 0; keymap < ARRAY_SIZE(keymap_table); keymap++)
4091                 help_open_keymap(view, keymap);
4093         return TRUE;
4096 static enum request
4097 help_request(struct view *view, enum request request, struct line *line)
4099         switch (request) {
4100         case REQ_ENTER:
4101                 if (line->type == LINE_HELP_KEYMAP) {
4102                         help_keymap_hidden[line->other] =
4103                                 !help_keymap_hidden[line->other];
4104                         view->p_restore = TRUE;
4105                         open_view(view, REQ_VIEW_HELP, OPEN_REFRESH);
4106                 }
4108                 return REQ_NONE;
4109         default:
4110                 return pager_request(view, request, line);
4111         }
4114 static struct view_ops help_ops = {
4115         "line",
4116         NULL,
4117         help_open,
4118         NULL,
4119         pager_draw,
4120         help_request,
4121         pager_grep,
4122         pager_select,
4123 };
4126 /*
4127  * Tree backend
4128  */
4130 struct tree_stack_entry {
4131         struct tree_stack_entry *prev;  /* Entry below this in the stack */
4132         unsigned long lineno;           /* Line number to restore */
4133         char *name;                     /* Position of name in opt_path */
4134 };
4136 /* The top of the path stack. */
4137 static struct tree_stack_entry *tree_stack = NULL;
4138 unsigned long tree_lineno = 0;
4140 static void
4141 pop_tree_stack_entry(void)
4143         struct tree_stack_entry *entry = tree_stack;
4145         tree_lineno = entry->lineno;
4146         entry->name[0] = 0;
4147         tree_stack = entry->prev;
4148         free(entry);
4151 static void
4152 push_tree_stack_entry(const char *name, unsigned long lineno)
4154         struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
4155         size_t pathlen = strlen(opt_path);
4157         if (!entry)
4158                 return;
4160         entry->prev = tree_stack;
4161         entry->name = opt_path + pathlen;
4162         tree_stack = entry;
4164         if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
4165                 pop_tree_stack_entry();
4166                 return;
4167         }
4169         /* Move the current line to the first tree entry. */
4170         tree_lineno = 1;
4171         entry->lineno = lineno;
4174 /* Parse output from git-ls-tree(1):
4175  *
4176  * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
4177  */
4179 #define SIZEOF_TREE_ATTR \
4180         STRING_SIZE("100644 blob f931e1d229c3e185caad4449bf5b66ed72462657\t")
4182 #define SIZEOF_TREE_MODE \
4183         STRING_SIZE("100644 ")
4185 #define TREE_ID_OFFSET \
4186         STRING_SIZE("100644 blob ")
4188 struct tree_entry {
4189         char id[SIZEOF_REV];
4190         mode_t mode;
4191         time_t time;                    /* Date from the author ident. */
4192         const char *author;             /* Author of the commit. */
4193         char name[1];
4194 };
4196 static const char *
4197 tree_path(const struct line *line)
4199         return ((struct tree_entry *) line->data)->name;
4202 static int
4203 tree_compare_entry(const struct line *line1, const struct line *line2)
4205         if (line1->type != line2->type)
4206                 return line1->type == LINE_TREE_DIR ? -1 : 1;
4207         return strcmp(tree_path(line1), tree_path(line2));
4210 static const enum sort_field tree_sort_fields[] = {
4211         ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
4212 };
4213 static struct sort_state tree_sort_state = SORT_STATE(tree_sort_fields);
4215 static int
4216 tree_compare(const void *l1, const void *l2)
4218         const struct line *line1 = (const struct line *) l1;
4219         const struct line *line2 = (const struct line *) l2;
4220         const struct tree_entry *entry1 = ((const struct line *) l1)->data;
4221         const struct tree_entry *entry2 = ((const struct line *) l2)->data;
4223         if (line1->type == LINE_TREE_HEAD)
4224                 return -1;
4225         if (line2->type == LINE_TREE_HEAD)
4226                 return 1;
4228         switch (get_sort_field(tree_sort_state)) {
4229         case ORDERBY_DATE:
4230                 return sort_order(tree_sort_state, entry1->time - entry2->time);
4232         case ORDERBY_AUTHOR:
4233                 return sort_order(tree_sort_state, strcmp(entry1->author, entry2->author));
4235         case ORDERBY_NAME:
4236         default:
4237                 return sort_order(tree_sort_state, tree_compare_entry(line1, line2));
4238         }
4242 static struct line *
4243 tree_entry(struct view *view, enum line_type type, const char *path,
4244            const char *mode, const char *id)
4246         struct tree_entry *entry = calloc(1, sizeof(*entry) + strlen(path));
4247         struct line *line = entry ? add_line_data(view, entry, type) : NULL;
4249         if (!entry || !line) {
4250                 free(entry);
4251                 return NULL;
4252         }
4254         strncpy(entry->name, path, strlen(path));
4255         if (mode)
4256                 entry->mode = strtoul(mode, NULL, 8);
4257         if (id)
4258                 string_copy_rev(entry->id, id);
4260         return line;
4263 static bool
4264 tree_read_date(struct view *view, char *text, bool *read_date)
4266         static const char *author_name;
4267         static time_t author_time;
4269         if (!text && *read_date) {
4270                 *read_date = FALSE;
4271                 return TRUE;
4273         } else if (!text) {
4274                 char *path = *opt_path ? opt_path : ".";
4275                 /* Find next entry to process */
4276                 const char *log_file[] = {
4277                         "git", "log", "--no-color", "--pretty=raw",
4278                                 "--cc", "--raw", view->id, "--", path, NULL
4279                 };
4280                 struct io io = {};
4282                 if (!view->lines) {
4283                         tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL);
4284                         report("Tree is empty");
4285                         return TRUE;
4286                 }
4288                 if (!run_io_rd(&io, log_file, opt_cdup, FORMAT_NONE)) {
4289                         report("Failed to load tree data");
4290                         return TRUE;
4291                 }
4293                 done_io(view->pipe);
4294                 view->io = io;
4295                 *read_date = TRUE;
4296                 return FALSE;
4298         } else if (*text == 'a' && get_line_type(text) == LINE_AUTHOR) {
4299                 parse_author_line(text + STRING_SIZE("author "),
4300                                   &author_name, &author_time);
4302         } else if (*text == ':') {
4303                 char *pos;
4304                 size_t annotated = 1;
4305                 size_t i;
4307                 pos = strchr(text, '\t');
4308                 if (!pos)
4309                         return TRUE;
4310                 text = pos + 1;
4311                 if (*opt_path && !strncmp(text, opt_path, strlen(opt_path)))
4312                         text += strlen(opt_path);
4313                 pos = strchr(text, '/');
4314                 if (pos)
4315                         *pos = 0;
4317                 for (i = 1; i < view->lines; i++) {
4318                         struct line *line = &view->line[i];
4319                         struct tree_entry *entry = line->data;
4321                         annotated += !!entry->author;
4322                         if (entry->author || strcmp(entry->name, text))
4323                                 continue;
4325                         entry->author = author_name;
4326                         entry->time = author_time;
4327                         line->dirty = 1;
4328                         break;
4329                 }
4331                 if (annotated == view->lines)
4332                         kill_io(view->pipe);
4333         }
4334         return TRUE;
4337 static bool
4338 tree_read(struct view *view, char *text)
4340         static bool read_date = FALSE;
4341         struct tree_entry *data;
4342         struct line *entry, *line;
4343         enum line_type type;
4344         size_t textlen = text ? strlen(text) : 0;
4345         char *path = text + SIZEOF_TREE_ATTR;
4347         if (read_date || !text)
4348                 return tree_read_date(view, text, &read_date);
4350         if (textlen <= SIZEOF_TREE_ATTR)
4351                 return FALSE;
4352         if (view->lines == 0 &&
4353             !tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL))
4354                 return FALSE;
4356         /* Strip the path part ... */
4357         if (*opt_path) {
4358                 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
4359                 size_t striplen = strlen(opt_path);
4361                 if (pathlen > striplen)
4362                         memmove(path, path + striplen,
4363                                 pathlen - striplen + 1);
4365                 /* Insert "link" to parent directory. */
4366                 if (view->lines == 1 &&
4367                     !tree_entry(view, LINE_TREE_DIR, "..", "040000", view->ref))
4368                         return FALSE;
4369         }
4371         type = text[SIZEOF_TREE_MODE] == 't' ? LINE_TREE_DIR : LINE_TREE_FILE;
4372         entry = tree_entry(view, type, path, text, text + TREE_ID_OFFSET);
4373         if (!entry)
4374                 return FALSE;
4375         data = entry->data;
4377         /* Skip "Directory ..." and ".." line. */
4378         for (line = &view->line[1 + !!*opt_path]; line < entry; line++) {
4379                 if (tree_compare_entry(line, entry) <= 0)
4380                         continue;
4382                 memmove(line + 1, line, (entry - line) * sizeof(*entry));
4384                 line->data = data;
4385                 line->type = type;
4386                 for (; line <= entry; line++)
4387                         line->dirty = line->cleareol = 1;
4388                 return TRUE;
4389         }
4391         if (tree_lineno > view->lineno) {
4392                 view->lineno = tree_lineno;
4393                 tree_lineno = 0;
4394         }
4396         return TRUE;
4399 static bool
4400 tree_draw(struct view *view, struct line *line, unsigned int lineno)
4402         struct tree_entry *entry = line->data;
4404         if (line->type == LINE_TREE_HEAD) {
4405                 if (draw_text(view, line->type, "Directory path /", TRUE))
4406                         return TRUE;
4407         } else {
4408                 if (draw_mode(view, entry->mode))
4409                         return TRUE;
4411                 if (opt_author && draw_author(view, entry->author))
4412                         return TRUE;
4414                 if (opt_date && draw_date(view, entry->author ? &entry->time : NULL))
4415                         return TRUE;
4416         }
4417         if (draw_text(view, line->type, entry->name, TRUE))
4418                 return TRUE;
4419         return TRUE;
4422 static void
4423 open_blob_editor()
4425         char file[SIZEOF_STR] = "/tmp/tigblob.XXXXXX";
4426         int fd = mkstemp(file);
4428         if (fd == -1)
4429                 report("Failed to create temporary file");
4430         else if (!run_io_append(blob_ops.argv, FORMAT_ALL, fd))
4431                 report("Failed to save blob data to file");
4432         else
4433                 open_editor(FALSE, file);
4434         if (fd != -1)
4435                 unlink(file);
4438 static enum request
4439 tree_request(struct view *view, enum request request, struct line *line)
4441         enum open_flags flags;
4443         switch (request) {
4444         case REQ_VIEW_BLAME:
4445                 if (line->type != LINE_TREE_FILE) {
4446                         report("Blame only supported for files");
4447                         return REQ_NONE;
4448                 }
4450                 string_copy(opt_ref, view->vid);
4451                 return request;
4453         case REQ_EDIT:
4454                 if (line->type != LINE_TREE_FILE) {
4455                         report("Edit only supported for files");
4456                 } else if (!is_head_commit(view->vid)) {
4457                         open_blob_editor();
4458                 } else {
4459                         open_editor(TRUE, opt_file);
4460                 }
4461                 return REQ_NONE;
4463         case REQ_TOGGLE_SORT_FIELD:
4464         case REQ_TOGGLE_SORT_ORDER:
4465                 sort_view(view, request, &tree_sort_state, tree_compare);
4466                 return REQ_NONE;
4468         case REQ_PARENT:
4469                 if (!*opt_path) {
4470                         /* quit view if at top of tree */
4471                         return REQ_VIEW_CLOSE;
4472                 }
4473                 /* fake 'cd  ..' */
4474                 line = &view->line[1];
4475                 break;
4477         case REQ_ENTER:
4478                 break;
4480         default:
4481                 return request;
4482         }
4484         /* Cleanup the stack if the tree view is at a different tree. */
4485         while (!*opt_path && tree_stack)
4486                 pop_tree_stack_entry();
4488         switch (line->type) {
4489         case LINE_TREE_DIR:
4490                 /* Depending on whether it is a subdirectory or parent link
4491                  * mangle the path buffer. */
4492                 if (line == &view->line[1] && *opt_path) {
4493                         pop_tree_stack_entry();
4495                 } else {
4496                         const char *basename = tree_path(line);
4498                         push_tree_stack_entry(basename, view->lineno);
4499                 }
4501                 /* Trees and subtrees share the same ID, so they are not not
4502                  * unique like blobs. */
4503                 flags = OPEN_RELOAD;
4504                 request = REQ_VIEW_TREE;
4505                 break;
4507         case LINE_TREE_FILE:
4508                 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4509                 request = REQ_VIEW_BLOB;
4510                 break;
4512         default:
4513                 return REQ_NONE;
4514         }
4516         open_view(view, request, flags);
4517         if (request == REQ_VIEW_TREE)
4518                 view->lineno = tree_lineno;
4520         return REQ_NONE;
4523 static bool
4524 tree_grep(struct view *view, struct line *line)
4526         struct tree_entry *entry = line->data;
4527         const char *text[] = {
4528                 entry->name,
4529                 opt_author ? entry->author : "",
4530                 opt_date ? mkdate(&entry->time) : "",
4531                 NULL
4532         };
4534         return grep_text(view, text);
4537 static void
4538 tree_select(struct view *view, struct line *line)
4540         struct tree_entry *entry = line->data;
4542         if (line->type == LINE_TREE_FILE) {
4543                 string_copy_rev(ref_blob, entry->id);
4544                 string_format(opt_file, "%s%s", opt_path, tree_path(line));
4546         } else if (line->type != LINE_TREE_DIR) {
4547                 return;
4548         }
4550         string_copy_rev(view->ref, entry->id);
4553 static bool
4554 tree_prepare(struct view *view)
4556         if (view->lines == 0 && opt_prefix[0]) {
4557                 char *pos = opt_prefix;
4559                 while (pos && *pos) {
4560                         char *end = strchr(pos, '/');
4562                         if (end)
4563                                 *end = 0;
4564                         push_tree_stack_entry(pos, 0);
4565                         pos = end;
4566                         if (end) {
4567                                 *end = '/';
4568                                 pos++;
4569                         }
4570                 }
4572         } else if (strcmp(view->vid, view->id)) {
4573                 opt_path[0] = 0;
4574         }
4576         return init_io_rd(&view->io, view->ops->argv, opt_cdup, FORMAT_ALL);
4579 static const char *tree_argv[SIZEOF_ARG] = {
4580         "git", "ls-tree", "%(commit)", "%(directory)", NULL
4581 };
4583 static struct view_ops tree_ops = {
4584         "file",
4585         tree_argv,
4586         NULL,
4587         tree_read,
4588         tree_draw,
4589         tree_request,
4590         tree_grep,
4591         tree_select,
4592         tree_prepare,
4593 };
4595 static bool
4596 blob_read(struct view *view, char *line)
4598         if (!line)
4599                 return TRUE;
4600         return add_line_text(view, line, LINE_DEFAULT) != NULL;
4603 static enum request
4604 blob_request(struct view *view, enum request request, struct line *line)
4606         switch (request) {
4607         case REQ_EDIT:
4608                 open_blob_editor();
4609                 return REQ_NONE;
4610         default:
4611                 return pager_request(view, request, line);
4612         }
4615 static const char *blob_argv[SIZEOF_ARG] = {
4616         "git", "cat-file", "blob", "%(blob)", NULL
4617 };
4619 static struct view_ops blob_ops = {
4620         "line",
4621         blob_argv,
4622         NULL,
4623         blob_read,
4624         pager_draw,
4625         blob_request,
4626         pager_grep,
4627         pager_select,
4628 };
4630 /*
4631  * Blame backend
4632  *
4633  * Loading the blame view is a two phase job:
4634  *
4635  *  1. File content is read either using opt_file from the
4636  *     filesystem or using git-cat-file.
4637  *  2. Then blame information is incrementally added by
4638  *     reading output from git-blame.
4639  */
4641 static const char *blame_head_argv[] = {
4642         "git", "blame", "--incremental", "--", "%(file)", NULL
4643 };
4645 static const char *blame_ref_argv[] = {
4646         "git", "blame", "--incremental", "%(ref)", "--", "%(file)", NULL
4647 };
4649 static const char *blame_cat_file_argv[] = {
4650         "git", "cat-file", "blob", "%(ref):%(file)", NULL
4651 };
4653 struct blame_commit {
4654         char id[SIZEOF_REV];            /* SHA1 ID. */
4655         char title[128];                /* First line of the commit message. */
4656         const char *author;             /* Author of the commit. */
4657         time_t time;                    /* Date from the author ident. */
4658         char filename[128];             /* Name of file. */
4659         bool has_previous;              /* Was a "previous" line detected. */
4660 };
4662 struct blame {
4663         struct blame_commit *commit;
4664         unsigned long lineno;
4665         char text[1];
4666 };
4668 static bool
4669 blame_open(struct view *view)
4671         char path[SIZEOF_STR];
4673         if (!view->parent && *opt_prefix) {
4674                 string_copy(path, opt_file);
4675                 if (!string_format(opt_file, "%s%s", opt_prefix, path))
4676                         return FALSE;
4677         }
4679         if (*opt_ref || !io_open(&view->io, "%s%s", opt_cdup, opt_file)) {
4680                 if (!run_io_rd(&view->io, blame_cat_file_argv, opt_cdup, FORMAT_ALL))
4681                         return FALSE;
4682         }
4684         setup_update(view, opt_file);
4685         string_format(view->ref, "%s ...", opt_file);
4687         return TRUE;
4690 static struct blame_commit *
4691 get_blame_commit(struct view *view, const char *id)
4693         size_t i;
4695         for (i = 0; i < view->lines; i++) {
4696                 struct blame *blame = view->line[i].data;
4698                 if (!blame->commit)
4699                         continue;
4701                 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
4702                         return blame->commit;
4703         }
4705         {
4706                 struct blame_commit *commit = calloc(1, sizeof(*commit));
4708                 if (commit)
4709                         string_ncopy(commit->id, id, SIZEOF_REV);
4710                 return commit;
4711         }
4714 static bool
4715 parse_number(const char **posref, size_t *number, size_t min, size_t max)
4717         const char *pos = *posref;
4719         *posref = NULL;
4720         pos = strchr(pos + 1, ' ');
4721         if (!pos || !isdigit(pos[1]))
4722                 return FALSE;
4723         *number = atoi(pos + 1);
4724         if (*number < min || *number > max)
4725                 return FALSE;
4727         *posref = pos;
4728         return TRUE;
4731 static struct blame_commit *
4732 parse_blame_commit(struct view *view, const char *text, int *blamed)
4734         struct blame_commit *commit;
4735         struct blame *blame;
4736         const char *pos = text + SIZEOF_REV - 2;
4737         size_t orig_lineno = 0;
4738         size_t lineno;
4739         size_t group;
4741         if (strlen(text) <= SIZEOF_REV || pos[1] != ' ')
4742                 return NULL;
4744         if (!parse_number(&pos, &orig_lineno, 1, 9999999) ||
4745             !parse_number(&pos, &lineno, 1, view->lines) ||
4746             !parse_number(&pos, &group, 1, view->lines - lineno + 1))
4747                 return NULL;
4749         commit = get_blame_commit(view, text);
4750         if (!commit)
4751                 return NULL;
4753         *blamed += group;
4754         while (group--) {
4755                 struct line *line = &view->line[lineno + group - 1];
4757                 blame = line->data;
4758                 blame->commit = commit;
4759                 blame->lineno = orig_lineno + group - 1;
4760                 line->dirty = 1;
4761         }
4763         return commit;
4766 static bool
4767 blame_read_file(struct view *view, const char *line, bool *read_file)
4769         if (!line) {
4770                 const char **argv = *opt_ref ? blame_ref_argv : blame_head_argv;
4771                 struct io io = {};
4773                 if (view->lines == 0 && !view->parent)
4774                         die("No blame exist for %s", view->vid);
4776                 if (view->lines == 0 || !run_io_rd(&io, argv, opt_cdup, FORMAT_ALL)) {
4777                         report("Failed to load blame data");
4778                         return TRUE;
4779                 }
4781                 done_io(view->pipe);
4782                 view->io = io;
4783                 *read_file = FALSE;
4784                 return FALSE;
4786         } else {
4787                 size_t linelen = strlen(line);
4788                 struct blame *blame = malloc(sizeof(*blame) + linelen);
4790                 if (!blame)
4791                         return FALSE;
4793                 blame->commit = NULL;
4794                 strncpy(blame->text, line, linelen);
4795                 blame->text[linelen] = 0;
4796                 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
4797         }
4800 static bool
4801 match_blame_header(const char *name, char **line)
4803         size_t namelen = strlen(name);
4804         bool matched = !strncmp(name, *line, namelen);
4806         if (matched)
4807                 *line += namelen;
4809         return matched;
4812 static bool
4813 blame_read(struct view *view, char *line)
4815         static struct blame_commit *commit = NULL;
4816         static int blamed = 0;
4817         static bool read_file = TRUE;
4819         if (read_file)
4820                 return blame_read_file(view, line, &read_file);
4822         if (!line) {
4823                 /* Reset all! */
4824                 commit = NULL;
4825                 blamed = 0;
4826                 read_file = TRUE;
4827                 string_format(view->ref, "%s", view->vid);
4828                 if (view_is_displayed(view)) {
4829                         update_view_title(view);
4830                         redraw_view_from(view, 0);
4831                 }
4832                 return TRUE;
4833         }
4835         if (!commit) {
4836                 commit = parse_blame_commit(view, line, &blamed);
4837                 string_format(view->ref, "%s %2d%%", view->vid,
4838                               view->lines ? blamed * 100 / view->lines : 0);
4840         } else if (match_blame_header("author ", &line)) {
4841                 commit->author = get_author(line);
4843         } else if (match_blame_header("author-time ", &line)) {
4844                 commit->time = (time_t) atol(line);
4846         } else if (match_blame_header("author-tz ", &line)) {
4847                 parse_timezone(&commit->time, line);
4849         } else if (match_blame_header("summary ", &line)) {
4850                 string_ncopy(commit->title, line, strlen(line));
4852         } else if (match_blame_header("previous ", &line)) {
4853                 commit->has_previous = TRUE;
4855         } else if (match_blame_header("filename ", &line)) {
4856                 string_ncopy(commit->filename, line, strlen(line));
4857                 commit = NULL;
4858         }
4860         return TRUE;
4863 static bool
4864 blame_draw(struct view *view, struct line *line, unsigned int lineno)
4866         struct blame *blame = line->data;
4867         time_t *time = NULL;
4868         const char *id = NULL, *author = NULL;
4869         char text[SIZEOF_STR];
4871         if (blame->commit && *blame->commit->filename) {
4872                 id = blame->commit->id;
4873                 author = blame->commit->author;
4874                 time = &blame->commit->time;
4875         }
4877         if (opt_date && draw_date(view, time))
4878                 return TRUE;
4880         if (opt_author && draw_author(view, author))
4881                 return TRUE;
4883         if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
4884                 return TRUE;
4886         if (draw_lineno(view, lineno))
4887                 return TRUE;
4889         string_expand(text, sizeof(text), blame->text, opt_tab_size);
4890         draw_text(view, LINE_DEFAULT, text, TRUE);
4891         return TRUE;
4894 static bool
4895 check_blame_commit(struct blame *blame, bool check_null_id)
4897         if (!blame->commit)
4898                 report("Commit data not loaded yet");
4899         else if (check_null_id && !strcmp(blame->commit->id, NULL_ID))
4900                 report("No commit exist for the selected line");
4901         else
4902                 return TRUE;
4903         return FALSE;
4906 static void
4907 setup_blame_parent_line(struct view *view, struct blame *blame)
4909         const char *diff_tree_argv[] = {
4910                 "git", "diff-tree", "-U0", blame->commit->id,
4911                         "--", blame->commit->filename, NULL
4912         };
4913         struct io io = {};
4914         int parent_lineno = -1;
4915         int blamed_lineno = -1;
4916         char *line;
4918         if (!run_io(&io, diff_tree_argv, NULL, IO_RD))
4919                 return;
4921         while ((line = io_get(&io, '\n', TRUE))) {
4922                 if (*line == '@') {
4923                         char *pos = strchr(line, '+');
4925                         parent_lineno = atoi(line + 4);
4926                         if (pos)
4927                                 blamed_lineno = atoi(pos + 1);
4929                 } else if (*line == '+' && parent_lineno != -1) {
4930                         if (blame->lineno == blamed_lineno - 1 &&
4931                             !strcmp(blame->text, line + 1)) {
4932                                 view->lineno = parent_lineno ? parent_lineno - 1 : 0;
4933                                 break;
4934                         }
4935                         blamed_lineno++;
4936                 }
4937         }
4939         done_io(&io);
4942 static enum request
4943 blame_request(struct view *view, enum request request, struct line *line)
4945         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4946         struct blame *blame = line->data;
4948         switch (request) {
4949         case REQ_VIEW_BLAME:
4950                 if (check_blame_commit(blame, TRUE)) {
4951                         string_copy(opt_ref, blame->commit->id);
4952                         string_copy(opt_file, blame->commit->filename);
4953                         if (blame->lineno)
4954                                 view->lineno = blame->lineno;
4955                         open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4956                 }
4957                 break;
4959         case REQ_PARENT:
4960                 if (check_blame_commit(blame, TRUE) &&
4961                     select_commit_parent(blame->commit->id, opt_ref,
4962                                          blame->commit->filename)) {
4963                         string_copy(opt_file, blame->commit->filename);
4964                         setup_blame_parent_line(view, blame);
4965                         open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4966                 }
4967                 break;
4969         case REQ_ENTER:
4970                 if (!check_blame_commit(blame, FALSE))
4971                         break;
4973                 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
4974                     !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
4975                         break;
4977                 if (!strcmp(blame->commit->id, NULL_ID)) {
4978                         struct view *diff = VIEW(REQ_VIEW_DIFF);
4979                         const char *diff_index_argv[] = {
4980                                 "git", "diff-index", "--root", "--patch-with-stat",
4981                                         "-C", "-M", "HEAD", "--", view->vid, NULL
4982                         };
4984                         if (!blame->commit->has_previous) {
4985                                 diff_index_argv[1] = "diff";
4986                                 diff_index_argv[2] = "--no-color";
4987                                 diff_index_argv[6] = "--";
4988                                 diff_index_argv[7] = "/dev/null";
4989                         }
4991                         if (!prepare_update(diff, diff_index_argv, NULL, FORMAT_DASH)) {
4992                                 report("Failed to allocate diff command");
4993                                 break;
4994                         }
4995                         flags |= OPEN_PREPARED;
4996                 }
4998                 open_view(view, REQ_VIEW_DIFF, flags);
4999                 if (VIEW(REQ_VIEW_DIFF)->pipe && !strcmp(blame->commit->id, NULL_ID))
5000                         string_copy_rev(VIEW(REQ_VIEW_DIFF)->ref, NULL_ID);
5001                 break;
5003         default:
5004                 return request;
5005         }
5007         return REQ_NONE;
5010 static bool
5011 blame_grep(struct view *view, struct line *line)
5013         struct blame *blame = line->data;
5014         struct blame_commit *commit = blame->commit;
5015         const char *text[] = {
5016                 blame->text,
5017                 commit ? commit->title : "",
5018                 commit ? commit->id : "",
5019                 commit && opt_author ? commit->author : "",
5020                 commit && opt_date ? mkdate(&commit->time) : "",
5021                 NULL
5022         };
5024         return grep_text(view, text);
5027 static void
5028 blame_select(struct view *view, struct line *line)
5030         struct blame *blame = line->data;
5031         struct blame_commit *commit = blame->commit;
5033         if (!commit)
5034                 return;
5036         if (!strcmp(commit->id, NULL_ID))
5037                 string_ncopy(ref_commit, "HEAD", 4);
5038         else
5039                 string_copy_rev(ref_commit, commit->id);
5042 static struct view_ops blame_ops = {
5043         "line",
5044         NULL,
5045         blame_open,
5046         blame_read,
5047         blame_draw,
5048         blame_request,
5049         blame_grep,
5050         blame_select,
5051 };
5053 /*
5054  * Branch backend
5055  */
5057 struct branch {
5058         const char *author;             /* Author of the last commit. */
5059         time_t time;                    /* Date of the last activity. */
5060         const struct ref *ref;          /* Name and commit ID information. */
5061 };
5063 static const struct ref branch_all;
5065 static const enum sort_field branch_sort_fields[] = {
5066         ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
5067 };
5068 static struct sort_state branch_sort_state = SORT_STATE(branch_sort_fields);
5070 static int
5071 branch_compare(const void *l1, const void *l2)
5073         const struct branch *branch1 = ((const struct line *) l1)->data;
5074         const struct branch *branch2 = ((const struct line *) l2)->data;
5076         switch (get_sort_field(branch_sort_state)) {
5077         case ORDERBY_DATE:
5078                 return sort_order(branch_sort_state, branch1->time - branch2->time);
5080         case ORDERBY_AUTHOR:
5081                 return sort_order(branch_sort_state, strcmp(branch1->author, branch2->author));
5083         case ORDERBY_NAME:
5084         default:
5085                 return sort_order(branch_sort_state, strcmp(branch1->ref->name, branch2->ref->name));
5086         }
5089 static bool
5090 branch_draw(struct view *view, struct line *line, unsigned int lineno)
5092         struct branch *branch = line->data;
5093         enum line_type type = branch->ref->head ? LINE_MAIN_HEAD : LINE_DEFAULT;
5095         if (opt_date && draw_date(view, branch->author ? &branch->time : NULL))
5096                 return TRUE;
5098         if (opt_author && draw_author(view, branch->author))
5099                 return TRUE;
5101         draw_text(view, type, branch->ref == &branch_all ? "All branches" : branch->ref->name, TRUE);
5102         return TRUE;
5105 static enum request
5106 branch_request(struct view *view, enum request request, struct line *line)
5108         struct branch *branch = line->data;
5110         switch (request) {
5111         case REQ_REFRESH:
5112                 load_refs();
5113                 open_view(view, REQ_VIEW_BRANCH, OPEN_REFRESH);
5114                 return REQ_NONE;
5116         case REQ_TOGGLE_SORT_FIELD:
5117         case REQ_TOGGLE_SORT_ORDER:
5118                 sort_view(view, request, &branch_sort_state, branch_compare);
5119                 return REQ_NONE;
5121         case REQ_ENTER:
5122                 if (branch->ref == &branch_all) {
5123                         const char *all_branches_argv[] = {
5124                                 "git", "log", "--no-color", "--pretty=raw", "--parents",
5125                                       "--topo-order", "--all", NULL
5126                         };
5127                         struct view *main_view = VIEW(REQ_VIEW_MAIN);
5129                         if (!prepare_update(main_view, all_branches_argv, NULL, FORMAT_NONE)) {
5130                                 report("Failed to load view of all branches");
5131                                 return REQ_NONE;
5132                         }
5133                         open_view(view, REQ_VIEW_MAIN, OPEN_PREPARED | OPEN_SPLIT);
5134                 } else {
5135                         open_view(view, REQ_VIEW_MAIN, OPEN_SPLIT);
5136                 }
5137                 return REQ_NONE;
5139         default:
5140                 return request;
5141         }
5144 static bool
5145 branch_read(struct view *view, char *line)
5147         static char id[SIZEOF_REV];
5148         struct branch *reference;
5149         size_t i;
5151         if (!line)
5152                 return TRUE;
5154         switch (get_line_type(line)) {
5155         case LINE_COMMIT:
5156                 string_copy_rev(id, line + STRING_SIZE("commit "));
5157                 return TRUE;
5159         case LINE_AUTHOR:
5160                 for (i = 0, reference = NULL; i < view->lines; i++) {
5161                         struct branch *branch = view->line[i].data;
5163                         if (strcmp(branch->ref->id, id))
5164                                 continue;
5166                         view->line[i].dirty = TRUE;
5167                         if (reference) {
5168                                 branch->author = reference->author;
5169                                 branch->time = reference->time;
5170                                 continue;
5171                         }
5173                         parse_author_line(line + STRING_SIZE("author "),
5174                                           &branch->author, &branch->time);
5175                         reference = branch;
5176                 }
5177                 return TRUE;
5179         default:
5180                 return TRUE;
5181         }
5185 static bool
5186 branch_open_visitor(void *data, const struct ref *ref)
5188         struct view *view = data;
5189         struct branch *branch;
5191         if (ref->tag || ref->ltag || ref->remote)
5192                 return TRUE;
5194         branch = calloc(1, sizeof(*branch));
5195         if (!branch)
5196                 return FALSE;
5198         branch->ref = ref;
5199         return !!add_line_data(view, branch, LINE_DEFAULT);
5202 static bool
5203 branch_open(struct view *view)
5205         const char *branch_log[] = {
5206                 "git", "log", "--no-color", "--pretty=raw",
5207                         "--simplify-by-decoration", "--all", NULL
5208         };
5210         if (!run_io_rd(&view->io, branch_log, NULL, FORMAT_NONE)) {
5211                 report("Failed to load branch data");
5212                 return TRUE;
5213         }
5215         setup_update(view, view->id);
5216         branch_open_visitor(view, &branch_all);
5217         foreach_ref(branch_open_visitor, view);
5218         view->p_restore = TRUE;
5220         return TRUE;
5223 static bool
5224 branch_grep(struct view *view, struct line *line)
5226         struct branch *branch = line->data;
5227         const char *text[] = {
5228                 branch->ref->name,
5229                 branch->author,
5230                 NULL
5231         };
5233         return grep_text(view, text);
5236 static void
5237 branch_select(struct view *view, struct line *line)
5239         struct branch *branch = line->data;
5241         string_copy_rev(view->ref, branch->ref->id);
5242         string_copy_rev(ref_commit, branch->ref->id);
5243         string_copy_rev(ref_head, branch->ref->id);
5246 static struct view_ops branch_ops = {
5247         "branch",
5248         NULL,
5249         branch_open,
5250         branch_read,
5251         branch_draw,
5252         branch_request,
5253         branch_grep,
5254         branch_select,
5255 };
5257 /*
5258  * Status backend
5259  */
5261 struct status {
5262         char status;
5263         struct {
5264                 mode_t mode;
5265                 char rev[SIZEOF_REV];
5266                 char name[SIZEOF_STR];
5267         } old;
5268         struct {
5269                 mode_t mode;
5270                 char rev[SIZEOF_REV];
5271                 char name[SIZEOF_STR];
5272         } new;
5273 };
5275 static char status_onbranch[SIZEOF_STR];
5276 static struct status stage_status;
5277 static enum line_type stage_line_type;
5278 static size_t stage_chunks;
5279 static int *stage_chunk;
5281 DEFINE_ALLOCATOR(realloc_ints, int, 32)
5283 /* This should work even for the "On branch" line. */
5284 static inline bool
5285 status_has_none(struct view *view, struct line *line)
5287         return line < view->line + view->lines && !line[1].data;
5290 /* Get fields from the diff line:
5291  * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
5292  */
5293 static inline bool
5294 status_get_diff(struct status *file, const char *buf, size_t bufsize)
5296         const char *old_mode = buf +  1;
5297         const char *new_mode = buf +  8;
5298         const char *old_rev  = buf + 15;
5299         const char *new_rev  = buf + 56;
5300         const char *status   = buf + 97;
5302         if (bufsize < 98 ||
5303             old_mode[-1] != ':' ||
5304             new_mode[-1] != ' ' ||
5305             old_rev[-1]  != ' ' ||
5306             new_rev[-1]  != ' ' ||
5307             status[-1]   != ' ')
5308                 return FALSE;
5310         file->status = *status;
5312         string_copy_rev(file->old.rev, old_rev);
5313         string_copy_rev(file->new.rev, new_rev);
5315         file->old.mode = strtoul(old_mode, NULL, 8);
5316         file->new.mode = strtoul(new_mode, NULL, 8);
5318         file->old.name[0] = file->new.name[0] = 0;
5320         return TRUE;
5323 static bool
5324 status_run(struct view *view, const char *argv[], char status, enum line_type type)
5326         struct status *unmerged = NULL;
5327         char *buf;
5328         struct io io = {};
5330         if (!run_io(&io, argv, opt_cdup, IO_RD))
5331                 return FALSE;
5333         add_line_data(view, NULL, type);
5335         while ((buf = io_get(&io, 0, TRUE))) {
5336                 struct status *file = unmerged;
5338                 if (!file) {
5339                         file = calloc(1, sizeof(*file));
5340                         if (!file || !add_line_data(view, file, type))
5341                                 goto error_out;
5342                 }
5344                 /* Parse diff info part. */
5345                 if (status) {
5346                         file->status = status;
5347                         if (status == 'A')
5348                                 string_copy(file->old.rev, NULL_ID);
5350                 } else if (!file->status || file == unmerged) {
5351                         if (!status_get_diff(file, buf, strlen(buf)))
5352                                 goto error_out;
5354                         buf = io_get(&io, 0, TRUE);
5355                         if (!buf)
5356                                 break;
5358                         /* Collapse all modified entries that follow an
5359                          * associated unmerged entry. */
5360                         if (unmerged == file) {
5361                                 unmerged->status = 'U';
5362                                 unmerged = NULL;
5363                         } else if (file->status == 'U') {
5364                                 unmerged = file;
5365                         }
5366                 }
5368                 /* Grab the old name for rename/copy. */
5369                 if (!*file->old.name &&
5370                     (file->status == 'R' || file->status == 'C')) {
5371                         string_ncopy(file->old.name, buf, strlen(buf));
5373                         buf = io_get(&io, 0, TRUE);
5374                         if (!buf)
5375                                 break;
5376                 }
5378                 /* git-ls-files just delivers a NUL separated list of
5379                  * file names similar to the second half of the
5380                  * git-diff-* output. */
5381                 string_ncopy(file->new.name, buf, strlen(buf));
5382                 if (!*file->old.name)
5383                         string_copy(file->old.name, file->new.name);
5384                 file = NULL;
5385         }
5387         if (io_error(&io)) {
5388 error_out:
5389                 done_io(&io);
5390                 return FALSE;
5391         }
5393         if (!view->line[view->lines - 1].data)
5394                 add_line_data(view, NULL, LINE_STAT_NONE);
5396         done_io(&io);
5397         return TRUE;
5400 /* Don't show unmerged entries in the staged section. */
5401 static const char *status_diff_index_argv[] = {
5402         "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
5403                              "--cached", "-M", "HEAD", NULL
5404 };
5406 static const char *status_diff_files_argv[] = {
5407         "git", "diff-files", "-z", NULL
5408 };
5410 static const char *status_list_other_argv[] = {
5411         "git", "ls-files", "-z", "--others", "--exclude-standard", NULL
5412 };
5414 static const char *status_list_no_head_argv[] = {
5415         "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
5416 };
5418 static const char *update_index_argv[] = {
5419         "git", "update-index", "-q", "--unmerged", "--refresh", NULL
5420 };
5422 /* Restore the previous line number to stay in the context or select a
5423  * line with something that can be updated. */
5424 static void
5425 status_restore(struct view *view)
5427         if (view->p_lineno >= view->lines)
5428                 view->p_lineno = view->lines - 1;
5429         while (view->p_lineno < view->lines && !view->line[view->p_lineno].data)
5430                 view->p_lineno++;
5431         while (view->p_lineno > 0 && !view->line[view->p_lineno].data)
5432                 view->p_lineno--;
5434         /* If the above fails, always skip the "On branch" line. */
5435         if (view->p_lineno < view->lines)
5436                 view->lineno = view->p_lineno;
5437         else
5438                 view->lineno = 1;
5440         if (view->lineno < view->offset)
5441                 view->offset = view->lineno;
5442         else if (view->offset + view->height <= view->lineno)
5443                 view->offset = view->lineno - view->height + 1;
5445         view->p_restore = FALSE;
5448 static void
5449 status_update_onbranch(void)
5451         static const char *paths[][2] = {
5452                 { "rebase-apply/rebasing",      "Rebasing" },
5453                 { "rebase-apply/applying",      "Applying mailbox" },
5454                 { "rebase-apply/",              "Rebasing mailbox" },
5455                 { "rebase-merge/interactive",   "Interactive rebase" },
5456                 { "rebase-merge/",              "Rebase merge" },
5457                 { "MERGE_HEAD",                 "Merging" },
5458                 { "BISECT_LOG",                 "Bisecting" },
5459                 { "HEAD",                       "On branch" },
5460         };
5461         char buf[SIZEOF_STR];
5462         struct stat stat;
5463         int i;
5465         if (is_initial_commit()) {
5466                 string_copy(status_onbranch, "Initial commit");
5467                 return;
5468         }
5470         for (i = 0; i < ARRAY_SIZE(paths); i++) {
5471                 char *head = opt_head;
5473                 if (!string_format(buf, "%s/%s", opt_git_dir, paths[i][0]) ||
5474                     lstat(buf, &stat) < 0)
5475                         continue;
5477                 if (!*opt_head) {
5478                         struct io io = {};
5480                         if (io_open(&io, "%s/rebase-merge/head-name", opt_git_dir) &&
5481                             io_read_buf(&io, buf, sizeof(buf))) {
5482                                 head = buf;
5483                                 if (!prefixcmp(head, "refs/heads/"))
5484                                         head += STRING_SIZE("refs/heads/");
5485                         }
5486                 }
5488                 if (!string_format(status_onbranch, "%s %s", paths[i][1], head))
5489                         string_copy(status_onbranch, opt_head);
5490                 return;
5491         }
5493         string_copy(status_onbranch, "Not currently on any branch");
5496 /* First parse staged info using git-diff-index(1), then parse unstaged
5497  * info using git-diff-files(1), and finally untracked files using
5498  * git-ls-files(1). */
5499 static bool
5500 status_open(struct view *view)
5502         reset_view(view);
5504         add_line_data(view, NULL, LINE_STAT_HEAD);
5505         status_update_onbranch();
5507         run_io_bg(update_index_argv);
5509         if (is_initial_commit()) {
5510                 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
5511                         return FALSE;
5512         } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
5513                 return FALSE;
5514         }
5516         if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
5517             !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
5518                 return FALSE;
5520         /* Restore the exact position or use the specialized restore
5521          * mode? */
5522         if (!view->p_restore)
5523                 status_restore(view);
5524         return TRUE;
5527 static bool
5528 status_draw(struct view *view, struct line *line, unsigned int lineno)
5530         struct status *status = line->data;
5531         enum line_type type;
5532         const char *text;
5534         if (!status) {
5535                 switch (line->type) {
5536                 case LINE_STAT_STAGED:
5537                         type = LINE_STAT_SECTION;
5538                         text = "Changes to be committed:";
5539                         break;
5541                 case LINE_STAT_UNSTAGED:
5542                         type = LINE_STAT_SECTION;
5543                         text = "Changed but not updated:";
5544                         break;
5546                 case LINE_STAT_UNTRACKED:
5547                         type = LINE_STAT_SECTION;
5548                         text = "Untracked files:";
5549                         break;
5551                 case LINE_STAT_NONE:
5552                         type = LINE_DEFAULT;
5553                         text = "  (no files)";
5554                         break;
5556                 case LINE_STAT_HEAD:
5557                         type = LINE_STAT_HEAD;
5558                         text = status_onbranch;
5559                         break;
5561                 default:
5562                         return FALSE;
5563                 }
5564         } else {
5565                 static char buf[] = { '?', ' ', ' ', ' ', 0 };
5567                 buf[0] = status->status;
5568                 if (draw_text(view, line->type, buf, TRUE))
5569                         return TRUE;
5570                 type = LINE_DEFAULT;
5571                 text = status->new.name;
5572         }
5574         draw_text(view, type, text, TRUE);
5575         return TRUE;
5578 static enum request
5579 status_load_error(struct view *view, struct view *stage, const char *path)
5581         if (displayed_views() == 2 || display[current_view] != view)
5582                 maximize_view(view);
5583         report("Failed to load '%s': %s", path, io_strerror(&stage->io));
5584         return REQ_NONE;
5587 static enum request
5588 status_enter(struct view *view, struct line *line)
5590         struct status *status = line->data;
5591         const char *oldpath = status ? status->old.name : NULL;
5592         /* Diffs for unmerged entries are empty when passing the new
5593          * path, so leave it empty. */
5594         const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
5595         const char *info;
5596         enum open_flags split;
5597         struct view *stage = VIEW(REQ_VIEW_STAGE);
5599         if (line->type == LINE_STAT_NONE ||
5600             (!status && line[1].type == LINE_STAT_NONE)) {
5601                 report("No file to diff");
5602                 return REQ_NONE;
5603         }
5605         switch (line->type) {
5606         case LINE_STAT_STAGED:
5607                 if (is_initial_commit()) {
5608                         const char *no_head_diff_argv[] = {
5609                                 "git", "diff", "--no-color", "--patch-with-stat",
5610                                         "--", "/dev/null", newpath, NULL
5611                         };
5613                         if (!prepare_update(stage, no_head_diff_argv, opt_cdup, FORMAT_DASH))
5614                                 return status_load_error(view, stage, newpath);
5615                 } else {
5616                         const char *index_show_argv[] = {
5617                                 "git", "diff-index", "--root", "--patch-with-stat",
5618                                         "-C", "-M", "--cached", "HEAD", "--",
5619                                         oldpath, newpath, NULL
5620                         };
5622                         if (!prepare_update(stage, index_show_argv, opt_cdup, FORMAT_DASH))
5623                                 return status_load_error(view, stage, newpath);
5624                 }
5626                 if (status)
5627                         info = "Staged changes to %s";
5628                 else
5629                         info = "Staged changes";
5630                 break;
5632         case LINE_STAT_UNSTAGED:
5633         {
5634                 const char *files_show_argv[] = {
5635                         "git", "diff-files", "--root", "--patch-with-stat",
5636                                 "-C", "-M", "--", oldpath, newpath, NULL
5637                 };
5639                 if (!prepare_update(stage, files_show_argv, opt_cdup, FORMAT_DASH))
5640                         return status_load_error(view, stage, newpath);
5641                 if (status)
5642                         info = "Unstaged changes to %s";
5643                 else
5644                         info = "Unstaged changes";
5645                 break;
5646         }
5647         case LINE_STAT_UNTRACKED:
5648                 if (!newpath) {
5649                         report("No file to show");
5650                         return REQ_NONE;
5651                 }
5653                 if (!suffixcmp(status->new.name, -1, "/")) {
5654                         report("Cannot display a directory");
5655                         return REQ_NONE;
5656                 }
5658                 if (!prepare_update_file(stage, newpath))
5659                         return status_load_error(view, stage, newpath);
5660                 info = "Untracked file %s";
5661                 break;
5663         case LINE_STAT_HEAD:
5664                 return REQ_NONE;
5666         default:
5667                 die("line type %d not handled in switch", line->type);
5668         }
5670         split = view_is_displayed(view) ? OPEN_SPLIT : 0;
5671         open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split);
5672         if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
5673                 if (status) {
5674                         stage_status = *status;
5675                 } else {
5676                         memset(&stage_status, 0, sizeof(stage_status));
5677                 }
5679                 stage_line_type = line->type;
5680                 stage_chunks = 0;
5681                 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
5682         }
5684         return REQ_NONE;
5687 static bool
5688 status_exists(struct status *status, enum line_type type)
5690         struct view *view = VIEW(REQ_VIEW_STATUS);
5691         unsigned long lineno;
5693         for (lineno = 0; lineno < view->lines; lineno++) {
5694                 struct line *line = &view->line[lineno];
5695                 struct status *pos = line->data;
5697                 if (line->type != type)
5698                         continue;
5699                 if (!pos && (!status || !status->status) && line[1].data) {
5700                         select_view_line(view, lineno);
5701                         return TRUE;
5702                 }
5703                 if (pos && !strcmp(status->new.name, pos->new.name)) {
5704                         select_view_line(view, lineno);
5705                         return TRUE;
5706                 }
5707         }
5709         return FALSE;
5713 static bool
5714 status_update_prepare(struct io *io, enum line_type type)
5716         const char *staged_argv[] = {
5717                 "git", "update-index", "-z", "--index-info", NULL
5718         };
5719         const char *others_argv[] = {
5720                 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
5721         };
5723         switch (type) {
5724         case LINE_STAT_STAGED:
5725                 return run_io(io, staged_argv, opt_cdup, IO_WR);
5727         case LINE_STAT_UNSTAGED:
5728         case LINE_STAT_UNTRACKED:
5729                 return run_io(io, others_argv, opt_cdup, IO_WR);
5731         default:
5732                 die("line type %d not handled in switch", type);
5733                 return FALSE;
5734         }
5737 static bool
5738 status_update_write(struct io *io, struct status *status, enum line_type type)
5740         char buf[SIZEOF_STR];
5741         size_t bufsize = 0;
5743         switch (type) {
5744         case LINE_STAT_STAGED:
5745                 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
5746                                         status->old.mode,
5747                                         status->old.rev,
5748                                         status->old.name, 0))
5749                         return FALSE;
5750                 break;
5752         case LINE_STAT_UNSTAGED:
5753         case LINE_STAT_UNTRACKED:
5754                 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
5755                         return FALSE;
5756                 break;
5758         default:
5759                 die("line type %d not handled in switch", type);
5760         }
5762         return io_write(io, buf, bufsize);
5765 static bool
5766 status_update_file(struct status *status, enum line_type type)
5768         struct io io = {};
5769         bool result;
5771         if (!status_update_prepare(&io, type))
5772                 return FALSE;
5774         result = status_update_write(&io, status, type);
5775         return done_io(&io) && result;
5778 static bool
5779 status_update_files(struct view *view, struct line *line)
5781         char buf[sizeof(view->ref)];
5782         struct io io = {};
5783         bool result = TRUE;
5784         struct line *pos = view->line + view->lines;
5785         int files = 0;
5786         int file, done;
5787         int cursor_y = -1, cursor_x = -1;
5789         if (!status_update_prepare(&io, line->type))
5790                 return FALSE;
5792         for (pos = line; pos < view->line + view->lines && pos->data; pos++)
5793                 files++;
5795         string_copy(buf, view->ref);
5796         getsyx(cursor_y, cursor_x);
5797         for (file = 0, done = 5; result && file < files; line++, file++) {
5798                 int almost_done = file * 100 / files;
5800                 if (almost_done > done) {
5801                         done = almost_done;
5802                         string_format(view->ref, "updating file %u of %u (%d%% done)",
5803                                       file, files, done);
5804                         update_view_title(view);
5805                         setsyx(cursor_y, cursor_x);
5806                         doupdate();
5807                 }
5808                 result = status_update_write(&io, line->data, line->type);
5809         }
5810         string_copy(view->ref, buf);
5812         return done_io(&io) && result;
5815 static bool
5816 status_update(struct view *view)
5818         struct line *line = &view->line[view->lineno];
5820         assert(view->lines);
5822         if (!line->data) {
5823                 /* This should work even for the "On branch" line. */
5824                 if (line < view->line + view->lines && !line[1].data) {
5825                         report("Nothing to update");
5826                         return FALSE;
5827                 }
5829                 if (!status_update_files(view, line + 1)) {
5830                         report("Failed to update file status");
5831                         return FALSE;
5832                 }
5834         } else if (!status_update_file(line->data, line->type)) {
5835                 report("Failed to update file status");
5836                 return FALSE;
5837         }
5839         return TRUE;
5842 static bool
5843 status_revert(struct status *status, enum line_type type, bool has_none)
5845         if (!status || type != LINE_STAT_UNSTAGED) {
5846                 if (type == LINE_STAT_STAGED) {
5847                         report("Cannot revert changes to staged files");
5848                 } else if (type == LINE_STAT_UNTRACKED) {
5849                         report("Cannot revert changes to untracked files");
5850                 } else if (has_none) {
5851                         report("Nothing to revert");
5852                 } else {
5853                         report("Cannot revert changes to multiple files");
5854                 }
5856         } else if (prompt_yesno("Are you sure you want to revert changes?")) {
5857                 char mode[10] = "100644";
5858                 const char *reset_argv[] = {
5859                         "git", "update-index", "--cacheinfo", mode,
5860                                 status->old.rev, status->old.name, NULL
5861                 };
5862                 const char *checkout_argv[] = {
5863                         "git", "checkout", "--", status->old.name, NULL
5864                 };
5866                 if (status->status == 'U') {
5867                         string_format(mode, "%5o", status->old.mode);
5869                         if (status->old.mode == 0 && status->new.mode == 0) {
5870                                 reset_argv[2] = "--force-remove";
5871                                 reset_argv[3] = status->old.name;
5872                                 reset_argv[4] = NULL;
5873                         }
5875                         if (!run_io_fg(reset_argv, opt_cdup))
5876                                 return FALSE;
5877                         if (status->old.mode == 0 && status->new.mode == 0)
5878                                 return TRUE;
5879                 }
5881                 return run_io_fg(checkout_argv, opt_cdup);
5882         }
5884         return FALSE;
5887 static enum request
5888 status_request(struct view *view, enum request request, struct line *line)
5890         struct status *status = line->data;
5892         switch (request) {
5893         case REQ_STATUS_UPDATE:
5894                 if (!status_update(view))
5895                         return REQ_NONE;
5896                 break;
5898         case REQ_STATUS_REVERT:
5899                 if (!status_revert(status, line->type, status_has_none(view, line)))
5900                         return REQ_NONE;
5901                 break;
5903         case REQ_STATUS_MERGE:
5904                 if (!status || status->status != 'U') {
5905                         report("Merging only possible for files with unmerged status ('U').");
5906                         return REQ_NONE;
5907                 }
5908                 open_mergetool(status->new.name);
5909                 break;
5911         case REQ_EDIT:
5912                 if (!status)
5913                         return request;
5914                 if (status->status == 'D') {
5915                         report("File has been deleted.");
5916                         return REQ_NONE;
5917                 }
5919                 open_editor(status->status != '?', status->new.name);
5920                 break;
5922         case REQ_VIEW_BLAME:
5923                 if (status)
5924                         opt_ref[0] = 0;
5925                 return request;
5927         case REQ_ENTER:
5928                 /* After returning the status view has been split to
5929                  * show the stage view. No further reloading is
5930                  * necessary. */
5931                 return status_enter(view, line);
5933         case REQ_REFRESH:
5934                 /* Simply reload the view. */
5935                 break;
5937         default:
5938                 return request;
5939         }
5941         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
5943         return REQ_NONE;
5946 static void
5947 status_select(struct view *view, struct line *line)
5949         struct status *status = line->data;
5950         char file[SIZEOF_STR] = "all files";
5951         const char *text;
5952         const char *key;
5954         if (status && !string_format(file, "'%s'", status->new.name))
5955                 return;
5957         if (!status && line[1].type == LINE_STAT_NONE)
5958                 line++;
5960         switch (line->type) {
5961         case LINE_STAT_STAGED:
5962                 text = "Press %s to unstage %s for commit";
5963                 break;
5965         case LINE_STAT_UNSTAGED:
5966                 text = "Press %s to stage %s for commit";
5967                 break;
5969         case LINE_STAT_UNTRACKED:
5970                 text = "Press %s to stage %s for addition";
5971                 break;
5973         case LINE_STAT_HEAD:
5974         case LINE_STAT_NONE:
5975                 text = "Nothing to update";
5976                 break;
5978         default:
5979                 die("line type %d not handled in switch", line->type);
5980         }
5982         if (status && status->status == 'U') {
5983                 text = "Press %s to resolve conflict in %s";
5984                 key = get_key(KEYMAP_STATUS, REQ_STATUS_MERGE);
5986         } else {
5987                 key = get_key(KEYMAP_STATUS, REQ_STATUS_UPDATE);
5988         }
5990         string_format(view->ref, text, key, file);
5991         if (status)
5992                 string_copy(opt_file, status->new.name);
5995 static bool
5996 status_grep(struct view *view, struct line *line)
5998         struct status *status = line->data;
6000         if (status) {
6001                 const char buf[2] = { status->status, 0 };
6002                 const char *text[] = { status->new.name, buf, NULL };
6004                 return grep_text(view, text);
6005         }
6007         return FALSE;
6010 static struct view_ops status_ops = {
6011         "file",
6012         NULL,
6013         status_open,
6014         NULL,
6015         status_draw,
6016         status_request,
6017         status_grep,
6018         status_select,
6019 };
6022 static bool
6023 stage_diff_write(struct io *io, struct line *line, struct line *end)
6025         while (line < end) {
6026                 if (!io_write(io, line->data, strlen(line->data)) ||
6027                     !io_write(io, "\n", 1))
6028                         return FALSE;
6029                 line++;
6030                 if (line->type == LINE_DIFF_CHUNK ||
6031                     line->type == LINE_DIFF_HEADER)
6032                         break;
6033         }
6035         return TRUE;
6038 static struct line *
6039 stage_diff_find(struct view *view, struct line *line, enum line_type type)
6041         for (; view->line < line; line--)
6042                 if (line->type == type)
6043                         return line;
6045         return NULL;
6048 static bool
6049 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
6051         const char *apply_argv[SIZEOF_ARG] = {
6052                 "git", "apply", "--whitespace=nowarn", NULL
6053         };
6054         struct line *diff_hdr;
6055         struct io io = {};
6056         int argc = 3;
6058         diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
6059         if (!diff_hdr)
6060                 return FALSE;
6062         if (!revert)
6063                 apply_argv[argc++] = "--cached";
6064         if (revert || stage_line_type == LINE_STAT_STAGED)
6065                 apply_argv[argc++] = "-R";
6066         apply_argv[argc++] = "-";
6067         apply_argv[argc++] = NULL;
6068         if (!run_io(&io, apply_argv, opt_cdup, IO_WR))
6069                 return FALSE;
6071         if (!stage_diff_write(&io, diff_hdr, chunk) ||
6072             !stage_diff_write(&io, chunk, view->line + view->lines))
6073                 chunk = NULL;
6075         done_io(&io);
6076         run_io_bg(update_index_argv);
6078         return chunk ? TRUE : FALSE;
6081 static bool
6082 stage_update(struct view *view, struct line *line)
6084         struct line *chunk = NULL;
6086         if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
6087                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6089         if (chunk) {
6090                 if (!stage_apply_chunk(view, chunk, FALSE)) {
6091                         report("Failed to apply chunk");
6092                         return FALSE;
6093                 }
6095         } else if (!stage_status.status) {
6096                 view = VIEW(REQ_VIEW_STATUS);
6098                 for (line = view->line; line < view->line + view->lines; line++)
6099                         if (line->type == stage_line_type)
6100                                 break;
6102                 if (!status_update_files(view, line + 1)) {
6103                         report("Failed to update files");
6104                         return FALSE;
6105                 }
6107         } else if (!status_update_file(&stage_status, stage_line_type)) {
6108                 report("Failed to update file");
6109                 return FALSE;
6110         }
6112         return TRUE;
6115 static bool
6116 stage_revert(struct view *view, struct line *line)
6118         struct line *chunk = NULL;
6120         if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
6121                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6123         if (chunk) {
6124                 if (!prompt_yesno("Are you sure you want to revert changes?"))
6125                         return FALSE;
6127                 if (!stage_apply_chunk(view, chunk, TRUE)) {
6128                         report("Failed to revert chunk");
6129                         return FALSE;
6130                 }
6131                 return TRUE;
6133         } else {
6134                 return status_revert(stage_status.status ? &stage_status : NULL,
6135                                      stage_line_type, FALSE);
6136         }
6140 static void
6141 stage_next(struct view *view, struct line *line)
6143         int i;
6145         if (!stage_chunks) {
6146                 for (line = view->line; line < view->line + view->lines; line++) {
6147                         if (line->type != LINE_DIFF_CHUNK)
6148                                 continue;
6150                         if (!realloc_ints(&stage_chunk, stage_chunks, 1)) {
6151                                 report("Allocation failure");
6152                                 return;
6153                         }
6155                         stage_chunk[stage_chunks++] = line - view->line;
6156                 }
6157         }
6159         for (i = 0; i < stage_chunks; i++) {
6160                 if (stage_chunk[i] > view->lineno) {
6161                         do_scroll_view(view, stage_chunk[i] - view->lineno);
6162                         report("Chunk %d of %d", i + 1, stage_chunks);
6163                         return;
6164                 }
6165         }
6167         report("No next chunk found");
6170 static enum request
6171 stage_request(struct view *view, enum request request, struct line *line)
6173         switch (request) {
6174         case REQ_STATUS_UPDATE:
6175                 if (!stage_update(view, line))
6176                         return REQ_NONE;
6177                 break;
6179         case REQ_STATUS_REVERT:
6180                 if (!stage_revert(view, line))
6181                         return REQ_NONE;
6182                 break;
6184         case REQ_STAGE_NEXT:
6185                 if (stage_line_type == LINE_STAT_UNTRACKED) {
6186                         report("File is untracked; press %s to add",
6187                                get_key(KEYMAP_STAGE, REQ_STATUS_UPDATE));
6188                         return REQ_NONE;
6189                 }
6190                 stage_next(view, line);
6191                 return REQ_NONE;
6193         case REQ_EDIT:
6194                 if (!stage_status.new.name[0])
6195                         return request;
6196                 if (stage_status.status == 'D') {
6197                         report("File has been deleted.");
6198                         return REQ_NONE;
6199                 }
6201                 open_editor(stage_status.status != '?', stage_status.new.name);
6202                 break;
6204         case REQ_REFRESH:
6205                 /* Reload everything ... */
6206                 break;
6208         case REQ_VIEW_BLAME:
6209                 if (stage_status.new.name[0]) {
6210                         string_copy(opt_file, stage_status.new.name);
6211                         opt_ref[0] = 0;
6212                 }
6213                 return request;
6215         case REQ_ENTER:
6216                 return pager_request(view, request, line);
6218         default:
6219                 return request;
6220         }
6222         VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
6223         open_view(view, REQ_VIEW_STATUS, OPEN_REFRESH);
6225         /* Check whether the staged entry still exists, and close the
6226          * stage view if it doesn't. */
6227         if (!status_exists(&stage_status, stage_line_type)) {
6228                 status_restore(VIEW(REQ_VIEW_STATUS));
6229                 return REQ_VIEW_CLOSE;
6230         }
6232         if (stage_line_type == LINE_STAT_UNTRACKED) {
6233                 if (!suffixcmp(stage_status.new.name, -1, "/")) {
6234                         report("Cannot display a directory");
6235                         return REQ_NONE;
6236                 }
6238                 if (!prepare_update_file(view, stage_status.new.name)) {
6239                         report("Failed to open file: %s", strerror(errno));
6240                         return REQ_NONE;
6241                 }
6242         }
6243         open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
6245         return REQ_NONE;
6248 static struct view_ops stage_ops = {
6249         "line",
6250         NULL,
6251         NULL,
6252         pager_read,
6253         pager_draw,
6254         stage_request,
6255         pager_grep,
6256         pager_select,
6257 };
6260 /*
6261  * Revision graph
6262  */
6264 struct commit {
6265         char id[SIZEOF_REV];            /* SHA1 ID. */
6266         char title[128];                /* First line of the commit message. */
6267         const char *author;             /* Author of the commit. */
6268         time_t time;                    /* Date from the author ident. */
6269         struct ref_list *refs;          /* Repository references. */
6270         chtype graph[SIZEOF_REVGRAPH];  /* Ancestry chain graphics. */
6271         size_t graph_size;              /* The width of the graph array. */
6272         bool has_parents;               /* Rewritten --parents seen. */
6273 };
6275 /* Size of rev graph with no  "padding" columns */
6276 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
6278 struct rev_graph {
6279         struct rev_graph *prev, *next, *parents;
6280         char rev[SIZEOF_REVITEMS][SIZEOF_REV];
6281         size_t size;
6282         struct commit *commit;
6283         size_t pos;
6284         unsigned int boundary:1;
6285 };
6287 /* Parents of the commit being visualized. */
6288 static struct rev_graph graph_parents[4];
6290 /* The current stack of revisions on the graph. */
6291 static struct rev_graph graph_stacks[4] = {
6292         { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
6293         { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
6294         { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
6295         { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
6296 };
6298 static inline bool
6299 graph_parent_is_merge(struct rev_graph *graph)
6301         return graph->parents->size > 1;
6304 static inline void
6305 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
6307         struct commit *commit = graph->commit;
6309         if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
6310                 commit->graph[commit->graph_size++] = symbol;
6313 static void
6314 clear_rev_graph(struct rev_graph *graph)
6316         graph->boundary = 0;
6317         graph->size = graph->pos = 0;
6318         graph->commit = NULL;
6319         memset(graph->parents, 0, sizeof(*graph->parents));
6322 static void
6323 done_rev_graph(struct rev_graph *graph)
6325         if (graph_parent_is_merge(graph) &&
6326             graph->pos < graph->size - 1 &&
6327             graph->next->size == graph->size + graph->parents->size - 1) {
6328                 size_t i = graph->pos + graph->parents->size - 1;
6330                 graph->commit->graph_size = i * 2;
6331                 while (i < graph->next->size - 1) {
6332                         append_to_rev_graph(graph, ' ');
6333                         append_to_rev_graph(graph, '\\');
6334                         i++;
6335                 }
6336         }
6338         clear_rev_graph(graph);
6341 static void
6342 push_rev_graph(struct rev_graph *graph, const char *parent)
6344         int i;
6346         /* "Collapse" duplicate parents lines.
6347          *
6348          * FIXME: This needs to also update update the drawn graph but
6349          * for now it just serves as a method for pruning graph lines. */
6350         for (i = 0; i < graph->size; i++)
6351                 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
6352                         return;
6354         if (graph->size < SIZEOF_REVITEMS) {
6355                 string_copy_rev(graph->rev[graph->size++], parent);
6356         }
6359 static chtype
6360 get_rev_graph_symbol(struct rev_graph *graph)
6362         chtype symbol;
6364         if (graph->boundary)
6365                 symbol = REVGRAPH_BOUND;
6366         else if (graph->parents->size == 0)
6367                 symbol = REVGRAPH_INIT;
6368         else if (graph_parent_is_merge(graph))
6369                 symbol = REVGRAPH_MERGE;
6370         else if (graph->pos >= graph->size)
6371                 symbol = REVGRAPH_BRANCH;
6372         else
6373                 symbol = REVGRAPH_COMMIT;
6375         return symbol;
6378 static void
6379 draw_rev_graph(struct rev_graph *graph)
6381         struct rev_filler {
6382                 chtype separator, line;
6383         };
6384         enum { DEFAULT, RSHARP, RDIAG, LDIAG };
6385         static struct rev_filler fillers[] = {
6386                 { ' ',  '|' },
6387                 { '`',  '.' },
6388                 { '\'', ' ' },
6389                 { '/',  ' ' },
6390         };
6391         chtype symbol = get_rev_graph_symbol(graph);
6392         struct rev_filler *filler;
6393         size_t i;
6395         if (opt_line_graphics)
6396                 fillers[DEFAULT].line = line_graphics[LINE_GRAPHIC_VLINE];
6398         filler = &fillers[DEFAULT];
6400         for (i = 0; i < graph->pos; i++) {
6401                 append_to_rev_graph(graph, filler->line);
6402                 if (graph_parent_is_merge(graph->prev) &&
6403                     graph->prev->pos == i)
6404                         filler = &fillers[RSHARP];
6406                 append_to_rev_graph(graph, filler->separator);
6407         }
6409         /* Place the symbol for this revision. */
6410         append_to_rev_graph(graph, symbol);
6412         if (graph->prev->size > graph->size)
6413                 filler = &fillers[RDIAG];
6414         else
6415                 filler = &fillers[DEFAULT];
6417         i++;
6419         for (; i < graph->size; i++) {
6420                 append_to_rev_graph(graph, filler->separator);
6421                 append_to_rev_graph(graph, filler->line);
6422                 if (graph_parent_is_merge(graph->prev) &&
6423                     i < graph->prev->pos + graph->parents->size)
6424                         filler = &fillers[RSHARP];
6425                 if (graph->prev->size > graph->size)
6426                         filler = &fillers[LDIAG];
6427         }
6429         if (graph->prev->size > graph->size) {
6430                 append_to_rev_graph(graph, filler->separator);
6431                 if (filler->line != ' ')
6432                         append_to_rev_graph(graph, filler->line);
6433         }
6436 /* Prepare the next rev graph */
6437 static void
6438 prepare_rev_graph(struct rev_graph *graph)
6440         size_t i;
6442         /* First, traverse all lines of revisions up to the active one. */
6443         for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
6444                 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
6445                         break;
6447                 push_rev_graph(graph->next, graph->rev[graph->pos]);
6448         }
6450         /* Interleave the new revision parent(s). */
6451         for (i = 0; !graph->boundary && i < graph->parents->size; i++)
6452                 push_rev_graph(graph->next, graph->parents->rev[i]);
6454         /* Lastly, put any remaining revisions. */
6455         for (i = graph->pos + 1; i < graph->size; i++)
6456                 push_rev_graph(graph->next, graph->rev[i]);
6459 static void
6460 update_rev_graph(struct view *view, struct rev_graph *graph)
6462         /* If this is the finalizing update ... */
6463         if (graph->commit)
6464                 prepare_rev_graph(graph);
6466         /* Graph visualization needs a one rev look-ahead,
6467          * so the first update doesn't visualize anything. */
6468         if (!graph->prev->commit)
6469                 return;
6471         if (view->lines > 2)
6472                 view->line[view->lines - 3].dirty = 1;
6473         if (view->lines > 1)
6474                 view->line[view->lines - 2].dirty = 1;
6475         draw_rev_graph(graph->prev);
6476         done_rev_graph(graph->prev->prev);
6480 /*
6481  * Main view backend
6482  */
6484 static const char *main_argv[SIZEOF_ARG] = {
6485         "git", "log", "--no-color", "--pretty=raw", "--parents",
6486                       "--topo-order", "%(head)", NULL
6487 };
6489 static bool
6490 main_draw(struct view *view, struct line *line, unsigned int lineno)
6492         struct commit *commit = line->data;
6494         if (!commit->author)
6495                 return FALSE;
6497         if (opt_date && draw_date(view, &commit->time))
6498                 return TRUE;
6500         if (opt_author && draw_author(view, commit->author))
6501                 return TRUE;
6503         if (opt_rev_graph && commit->graph_size &&
6504             draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
6505                 return TRUE;
6507         if (opt_show_refs && commit->refs) {
6508                 size_t i;
6510                 for (i = 0; i < commit->refs->size; i++) {
6511                         struct ref *ref = commit->refs->refs[i];
6512                         enum line_type type;
6514                         if (ref->head)
6515                                 type = LINE_MAIN_HEAD;
6516                         else if (ref->ltag)
6517                                 type = LINE_MAIN_LOCAL_TAG;
6518                         else if (ref->tag)
6519                                 type = LINE_MAIN_TAG;
6520                         else if (ref->tracked)
6521                                 type = LINE_MAIN_TRACKED;
6522                         else if (ref->remote)
6523                                 type = LINE_MAIN_REMOTE;
6524                         else
6525                                 type = LINE_MAIN_REF;
6527                         if (draw_text(view, type, "[", TRUE) ||
6528                             draw_text(view, type, ref->name, TRUE) ||
6529                             draw_text(view, type, "]", TRUE))
6530                                 return TRUE;
6532                         if (draw_text(view, LINE_DEFAULT, " ", TRUE))
6533                                 return TRUE;
6534                 }
6535         }
6537         draw_text(view, LINE_DEFAULT, commit->title, TRUE);
6538         return TRUE;
6541 /* Reads git log --pretty=raw output and parses it into the commit struct. */
6542 static bool
6543 main_read(struct view *view, char *line)
6545         static struct rev_graph *graph = graph_stacks;
6546         enum line_type type;
6547         struct commit *commit;
6549         if (!line) {
6550                 int i;
6552                 if (!view->lines && !view->parent)
6553                         die("No revisions match the given arguments.");
6554                 if (view->lines > 0) {
6555                         commit = view->line[view->lines - 1].data;
6556                         view->line[view->lines - 1].dirty = 1;
6557                         if (!commit->author) {
6558                                 view->lines--;
6559                                 free(commit);
6560                                 graph->commit = NULL;
6561                         }
6562                 }
6563                 update_rev_graph(view, graph);
6565                 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
6566                         clear_rev_graph(&graph_stacks[i]);
6567                 return TRUE;
6568         }
6570         type = get_line_type(line);
6571         if (type == LINE_COMMIT) {
6572                 commit = calloc(1, sizeof(struct commit));
6573                 if (!commit)
6574                         return FALSE;
6576                 line += STRING_SIZE("commit ");
6577                 if (*line == '-') {
6578                         graph->boundary = 1;
6579                         line++;
6580                 }
6582                 string_copy_rev(commit->id, line);
6583                 commit->refs = get_ref_list(commit->id);
6584                 graph->commit = commit;
6585                 add_line_data(view, commit, LINE_MAIN_COMMIT);
6587                 while ((line = strchr(line, ' '))) {
6588                         line++;
6589                         push_rev_graph(graph->parents, line);
6590                         commit->has_parents = TRUE;
6591                 }
6592                 return TRUE;
6593         }
6595         if (!view->lines)
6596                 return TRUE;
6597         commit = view->line[view->lines - 1].data;
6599         switch (type) {
6600         case LINE_PARENT:
6601                 if (commit->has_parents)
6602                         break;
6603                 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
6604                 break;
6606         case LINE_AUTHOR:
6607                 parse_author_line(line + STRING_SIZE("author "),
6608                                   &commit->author, &commit->time);
6609                 update_rev_graph(view, graph);
6610                 graph = graph->next;
6611                 break;
6613         default:
6614                 /* Fill in the commit title if it has not already been set. */
6615                 if (commit->title[0])
6616                         break;
6618                 /* Require titles to start with a non-space character at the
6619                  * offset used by git log. */
6620                 if (strncmp(line, "    ", 4))
6621                         break;
6622                 line += 4;
6623                 /* Well, if the title starts with a whitespace character,
6624                  * try to be forgiving.  Otherwise we end up with no title. */
6625                 while (isspace(*line))
6626                         line++;
6627                 if (*line == '\0')
6628                         break;
6629                 /* FIXME: More graceful handling of titles; append "..." to
6630                  * shortened titles, etc. */
6632                 string_expand(commit->title, sizeof(commit->title), line, 1);
6633                 view->line[view->lines - 1].dirty = 1;
6634         }
6636         return TRUE;
6639 static enum request
6640 main_request(struct view *view, enum request request, struct line *line)
6642         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
6644         switch (request) {
6645         case REQ_ENTER:
6646                 open_view(view, REQ_VIEW_DIFF, flags);
6647                 break;
6648         case REQ_REFRESH:
6649                 load_refs();
6650                 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
6651                 break;
6652         default:
6653                 return request;
6654         }
6656         return REQ_NONE;
6659 static bool
6660 grep_refs(struct ref_list *list, regex_t *regex)
6662         regmatch_t pmatch;
6663         size_t i;
6665         if (!opt_show_refs || !list)
6666                 return FALSE;
6668         for (i = 0; i < list->size; i++) {
6669                 if (regexec(regex, list->refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
6670                         return TRUE;
6671         }
6673         return FALSE;
6676 static bool
6677 main_grep(struct view *view, struct line *line)
6679         struct commit *commit = line->data;
6680         const char *text[] = {
6681                 commit->title,
6682                 opt_author ? commit->author : "",
6683                 opt_date ? mkdate(&commit->time) : "",
6684                 NULL
6685         };
6687         return grep_text(view, text) || grep_refs(commit->refs, view->regex);
6690 static void
6691 main_select(struct view *view, struct line *line)
6693         struct commit *commit = line->data;
6695         string_copy_rev(view->ref, commit->id);
6696         string_copy_rev(ref_commit, view->ref);
6699 static struct view_ops main_ops = {
6700         "commit",
6701         main_argv,
6702         NULL,
6703         main_read,
6704         main_draw,
6705         main_request,
6706         main_grep,
6707         main_select,
6708 };
6711 /*
6712  * Unicode / UTF-8 handling
6713  *
6714  * NOTE: Much of the following code for dealing with Unicode is derived from
6715  * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
6716  * src/intl/charset.c from the UTF-8 branch commit elinks-0.11.0-g31f2c28.
6717  */
6719 static inline int
6720 unicode_width(unsigned long c)
6722         if (c >= 0x1100 &&
6723            (c <= 0x115f                         /* Hangul Jamo */
6724             || c == 0x2329
6725             || c == 0x232a
6726             || (c >= 0x2e80  && c <= 0xa4cf && c != 0x303f)
6727                                                 /* CJK ... Yi */
6728             || (c >= 0xac00  && c <= 0xd7a3)    /* Hangul Syllables */
6729             || (c >= 0xf900  && c <= 0xfaff)    /* CJK Compatibility Ideographs */
6730             || (c >= 0xfe30  && c <= 0xfe6f)    /* CJK Compatibility Forms */
6731             || (c >= 0xff00  && c <= 0xff60)    /* Fullwidth Forms */
6732             || (c >= 0xffe0  && c <= 0xffe6)
6733             || (c >= 0x20000 && c <= 0x2fffd)
6734             || (c >= 0x30000 && c <= 0x3fffd)))
6735                 return 2;
6737         if (c == '\t')
6738                 return opt_tab_size;
6740         return 1;
6743 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
6744  * Illegal bytes are set one. */
6745 static const unsigned char utf8_bytes[256] = {
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         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,
6752         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,
6753         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,
6754 };
6756 /* Decode UTF-8 multi-byte representation into a Unicode character. */
6757 static inline unsigned long
6758 utf8_to_unicode(const char *string, size_t length)
6760         unsigned long unicode;
6762         switch (length) {
6763         case 1:
6764                 unicode  =   string[0];
6765                 break;
6766         case 2:
6767                 unicode  =  (string[0] & 0x1f) << 6;
6768                 unicode +=  (string[1] & 0x3f);
6769                 break;
6770         case 3:
6771                 unicode  =  (string[0] & 0x0f) << 12;
6772                 unicode += ((string[1] & 0x3f) << 6);
6773                 unicode +=  (string[2] & 0x3f);
6774                 break;
6775         case 4:
6776                 unicode  =  (string[0] & 0x0f) << 18;
6777                 unicode += ((string[1] & 0x3f) << 12);
6778                 unicode += ((string[2] & 0x3f) << 6);
6779                 unicode +=  (string[3] & 0x3f);
6780                 break;
6781         case 5:
6782                 unicode  =  (string[0] & 0x0f) << 24;
6783                 unicode += ((string[1] & 0x3f) << 18);
6784                 unicode += ((string[2] & 0x3f) << 12);
6785                 unicode += ((string[3] & 0x3f) << 6);
6786                 unicode +=  (string[4] & 0x3f);
6787                 break;
6788         case 6:
6789                 unicode  =  (string[0] & 0x01) << 30;
6790                 unicode += ((string[1] & 0x3f) << 24);
6791                 unicode += ((string[2] & 0x3f) << 18);
6792                 unicode += ((string[3] & 0x3f) << 12);
6793                 unicode += ((string[4] & 0x3f) << 6);
6794                 unicode +=  (string[5] & 0x3f);
6795                 break;
6796         default:
6797                 die("Invalid Unicode length");
6798         }
6800         /* Invalid characters could return the special 0xfffd value but NUL
6801          * should be just as good. */
6802         return unicode > 0xffff ? 0 : unicode;
6805 /* Calculates how much of string can be shown within the given maximum width
6806  * and sets trimmed parameter to non-zero value if all of string could not be
6807  * shown. If the reserve flag is TRUE, it will reserve at least one
6808  * trailing character, which can be useful when drawing a delimiter.
6809  *
6810  * Returns the number of bytes to output from string to satisfy max_width. */
6811 static size_t
6812 utf8_length(const char **start, size_t skip, int *width, size_t max_width, int *trimmed, bool reserve)
6814         const char *string = *start;
6815         const char *end = strchr(string, '\0');
6816         unsigned char last_bytes = 0;
6817         size_t last_ucwidth = 0;
6819         *width = 0;
6820         *trimmed = 0;
6822         while (string < end) {
6823                 int c = *(unsigned char *) string;
6824                 unsigned char bytes = utf8_bytes[c];
6825                 size_t ucwidth;
6826                 unsigned long unicode;
6828                 if (string + bytes > end)
6829                         break;
6831                 /* Change representation to figure out whether
6832                  * it is a single- or double-width character. */
6834                 unicode = utf8_to_unicode(string, bytes);
6835                 /* FIXME: Graceful handling of invalid Unicode character. */
6836                 if (!unicode)
6837                         break;
6839                 ucwidth = unicode_width(unicode);
6840                 if (skip > 0) {
6841                         skip -= ucwidth <= skip ? ucwidth : skip;
6842                         *start += bytes;
6843                 }
6844                 *width  += ucwidth;
6845                 if (*width > max_width) {
6846                         *trimmed = 1;
6847                         *width -= ucwidth;
6848                         if (reserve && *width == max_width) {
6849                                 string -= last_bytes;
6850                                 *width -= last_ucwidth;
6851                         }
6852                         break;
6853                 }
6855                 string  += bytes;
6856                 last_bytes = ucwidth ? bytes : 0;
6857                 last_ucwidth = ucwidth;
6858         }
6860         return string - *start;
6864 /*
6865  * Status management
6866  */
6868 /* Whether or not the curses interface has been initialized. */
6869 static bool cursed = FALSE;
6871 /* Terminal hacks and workarounds. */
6872 static bool use_scroll_redrawwin;
6873 static bool use_scroll_status_wclear;
6875 /* The status window is used for polling keystrokes. */
6876 static WINDOW *status_win;
6878 /* Reading from the prompt? */
6879 static bool input_mode = FALSE;
6881 static bool status_empty = FALSE;
6883 /* Update status and title window. */
6884 static void
6885 report(const char *msg, ...)
6887         struct view *view = display[current_view];
6889         if (input_mode)
6890                 return;
6892         if (!view) {
6893                 char buf[SIZEOF_STR];
6894                 va_list args;
6896                 va_start(args, msg);
6897                 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
6898                         buf[sizeof(buf) - 1] = 0;
6899                         buf[sizeof(buf) - 2] = '.';
6900                         buf[sizeof(buf) - 3] = '.';
6901                         buf[sizeof(buf) - 4] = '.';
6902                 }
6903                 va_end(args);
6904                 die("%s", buf);
6905         }
6907         if (!status_empty || *msg) {
6908                 va_list args;
6910                 va_start(args, msg);
6912                 wmove(status_win, 0, 0);
6913                 if (view->has_scrolled && use_scroll_status_wclear)
6914                         wclear(status_win);
6915                 if (*msg) {
6916                         vwprintw(status_win, msg, args);
6917                         status_empty = FALSE;
6918                 } else {
6919                         status_empty = TRUE;
6920                 }
6921                 wclrtoeol(status_win);
6922                 wnoutrefresh(status_win);
6924                 va_end(args);
6925         }
6927         update_view_title(view);
6930 /* Controls when nodelay should be in effect when polling user input. */
6931 static void
6932 set_nonblocking_input(bool loading)
6934         static unsigned int loading_views;
6936         if ((loading == FALSE && loading_views-- == 1) ||
6937             (loading == TRUE  && loading_views++ == 0))
6938                 nodelay(status_win, loading);
6941 static void
6942 init_display(void)
6944         const char *term;
6945         int x, y;
6947         /* Initialize the curses library */
6948         if (isatty(STDIN_FILENO)) {
6949                 cursed = !!initscr();
6950                 opt_tty = stdin;
6951         } else {
6952                 /* Leave stdin and stdout alone when acting as a pager. */
6953                 opt_tty = fopen("/dev/tty", "r+");
6954                 if (!opt_tty)
6955                         die("Failed to open /dev/tty");
6956                 cursed = !!newterm(NULL, opt_tty, opt_tty);
6957         }
6959         if (!cursed)
6960                 die("Failed to initialize curses");
6962         nonl();         /* Disable conversion and detect newlines from input. */
6963         cbreak();       /* Take input chars one at a time, no wait for \n */
6964         noecho();       /* Don't echo input */
6965         leaveok(stdscr, FALSE);
6967         if (has_colors())
6968                 init_colors();
6970         getmaxyx(stdscr, y, x);
6971         status_win = newwin(1, 0, y - 1, 0);
6972         if (!status_win)
6973                 die("Failed to create status window");
6975         /* Enable keyboard mapping */
6976         keypad(status_win, TRUE);
6977         wbkgdset(status_win, get_line_attr(LINE_STATUS));
6979         TABSIZE = opt_tab_size;
6980         if (opt_line_graphics) {
6981                 line_graphics[LINE_GRAPHIC_VLINE] = ACS_VLINE;
6982         }
6984         term = getenv("XTERM_VERSION") ? NULL : getenv("COLORTERM");
6985         if (term && !strcmp(term, "gnome-terminal")) {
6986                 /* In the gnome-terminal-emulator, the message from
6987                  * scrolling up one line when impossible followed by
6988                  * scrolling down one line causes corruption of the
6989                  * status line. This is fixed by calling wclear. */
6990                 use_scroll_status_wclear = TRUE;
6991                 use_scroll_redrawwin = FALSE;
6993         } else if (term && !strcmp(term, "xrvt-xpm")) {
6994                 /* No problems with full optimizations in xrvt-(unicode)
6995                  * and aterm. */
6996                 use_scroll_status_wclear = use_scroll_redrawwin = FALSE;
6998         } else {
6999                 /* When scrolling in (u)xterm the last line in the
7000                  * scrolling direction will update slowly. */
7001                 use_scroll_redrawwin = TRUE;
7002                 use_scroll_status_wclear = FALSE;
7003         }
7006 static int
7007 get_input(int prompt_position)
7009         struct view *view;
7010         int i, key, cursor_y, cursor_x;
7012         if (prompt_position)
7013                 input_mode = TRUE;
7015         while (TRUE) {
7016                 foreach_view (view, i) {
7017                         update_view(view);
7018                         if (view_is_displayed(view) && view->has_scrolled &&
7019                             use_scroll_redrawwin)
7020                                 redrawwin(view->win);
7021                         view->has_scrolled = FALSE;
7022                 }
7024                 /* Update the cursor position. */
7025                 if (prompt_position) {
7026                         getbegyx(status_win, cursor_y, cursor_x);
7027                         cursor_x = prompt_position;
7028                 } else {
7029                         view = display[current_view];
7030                         getbegyx(view->win, cursor_y, cursor_x);
7031                         cursor_x = view->width - 1;
7032                         cursor_y += view->lineno - view->offset;
7033                 }
7034                 setsyx(cursor_y, cursor_x);
7036                 /* Refresh, accept single keystroke of input */
7037                 doupdate();
7038                 key = wgetch(status_win);
7040                 /* wgetch() with nodelay() enabled returns ERR when
7041                  * there's no input. */
7042                 if (key == ERR) {
7044                 } else if (key == KEY_RESIZE) {
7045                         int height, width;
7047                         getmaxyx(stdscr, height, width);
7049                         wresize(status_win, 1, width);
7050                         mvwin(status_win, height - 1, 0);
7051                         wnoutrefresh(status_win);
7052                         resize_display();
7053                         redraw_display(TRUE);
7055                 } else {
7056                         input_mode = FALSE;
7057                         return key;
7058                 }
7059         }
7062 static char *
7063 prompt_input(const char *prompt, input_handler handler, void *data)
7065         enum input_status status = INPUT_OK;
7066         static char buf[SIZEOF_STR];
7067         size_t pos = 0;
7069         buf[pos] = 0;
7071         while (status == INPUT_OK || status == INPUT_SKIP) {
7072                 int key;
7074                 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
7075                 wclrtoeol(status_win);
7077                 key = get_input(pos + 1);
7078                 switch (key) {
7079                 case KEY_RETURN:
7080                 case KEY_ENTER:
7081                 case '\n':
7082                         status = pos ? INPUT_STOP : INPUT_CANCEL;
7083                         break;
7085                 case KEY_BACKSPACE:
7086                         if (pos > 0)
7087                                 buf[--pos] = 0;
7088                         else
7089                                 status = INPUT_CANCEL;
7090                         break;
7092                 case KEY_ESC:
7093                         status = INPUT_CANCEL;
7094                         break;
7096                 default:
7097                         if (pos >= sizeof(buf)) {
7098                                 report("Input string too long");
7099                                 return NULL;
7100                         }
7102                         status = handler(data, buf, key);
7103                         if (status == INPUT_OK)
7104                                 buf[pos++] = (char) key;
7105                 }
7106         }
7108         /* Clear the status window */
7109         status_empty = FALSE;
7110         report("");
7112         if (status == INPUT_CANCEL)
7113                 return NULL;
7115         buf[pos++] = 0;
7117         return buf;
7120 static enum input_status
7121 prompt_yesno_handler(void *data, char *buf, int c)
7123         if (c == 'y' || c == 'Y')
7124                 return INPUT_STOP;
7125         if (c == 'n' || c == 'N')
7126                 return INPUT_CANCEL;
7127         return INPUT_SKIP;
7130 static bool
7131 prompt_yesno(const char *prompt)
7133         char prompt2[SIZEOF_STR];
7135         if (!string_format(prompt2, "%s [Yy/Nn]", prompt))
7136                 return FALSE;
7138         return !!prompt_input(prompt2, prompt_yesno_handler, NULL);
7141 static enum input_status
7142 read_prompt_handler(void *data, char *buf, int c)
7144         return isprint(c) ? INPUT_OK : INPUT_SKIP;
7147 static char *
7148 read_prompt(const char *prompt)
7150         return prompt_input(prompt, read_prompt_handler, NULL);
7153 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected)
7155         enum input_status status = INPUT_OK;
7156         int size = 0;
7158         while (items[size].text)
7159                 size++;
7161         while (status == INPUT_OK) {
7162                 const struct menu_item *item = &items[*selected];
7163                 int key;
7164                 int i;
7166                 mvwprintw(status_win, 0, 0, "%s (%d of %d) ",
7167                           prompt, *selected + 1, size);
7168                 if (item->hotkey)
7169                         wprintw(status_win, "[%c] ", (char) item->hotkey);
7170                 wprintw(status_win, "%s", item->text);
7171                 wclrtoeol(status_win);
7173                 key = get_input(COLS - 1);
7174                 switch (key) {
7175                 case KEY_RETURN:
7176                 case KEY_ENTER:
7177                 case '\n':
7178                         status = INPUT_STOP;
7179                         break;
7181                 case KEY_LEFT:
7182                 case KEY_UP:
7183                         *selected = *selected - 1;
7184                         if (*selected < 0)
7185                                 *selected = size - 1;
7186                         break;
7188                 case KEY_RIGHT:
7189                 case KEY_DOWN:
7190                         *selected = (*selected + 1) % size;
7191                         break;
7193                 case KEY_ESC:
7194                         status = INPUT_CANCEL;
7195                         break;
7197                 default:
7198                         for (i = 0; items[i].text; i++)
7199                                 if (items[i].hotkey == key) {
7200                                         *selected = i;
7201                                         status = INPUT_STOP;
7202                                         break;
7203                                 }
7204                 }
7205         }
7207         /* Clear the status window */
7208         status_empty = FALSE;
7209         report("");
7211         return status != INPUT_CANCEL;
7214 /*
7215  * Repository properties
7216  */
7218 static struct ref **refs = NULL;
7219 static size_t refs_size = 0;
7221 static struct ref_list **ref_lists = NULL;
7222 static size_t ref_lists_size = 0;
7224 DEFINE_ALLOCATOR(realloc_refs, struct ref *, 256)
7225 DEFINE_ALLOCATOR(realloc_refs_list, struct ref *, 8)
7226 DEFINE_ALLOCATOR(realloc_ref_lists, struct ref_list *, 8)
7228 static int
7229 compare_refs(const void *ref1_, const void *ref2_)
7231         const struct ref *ref1 = *(const struct ref **)ref1_;
7232         const struct ref *ref2 = *(const struct ref **)ref2_;
7234         if (ref1->tag != ref2->tag)
7235                 return ref2->tag - ref1->tag;
7236         if (ref1->ltag != ref2->ltag)
7237                 return ref2->ltag - ref2->ltag;
7238         if (ref1->head != ref2->head)
7239                 return ref2->head - ref1->head;
7240         if (ref1->tracked != ref2->tracked)
7241                 return ref2->tracked - ref1->tracked;
7242         if (ref1->remote != ref2->remote)
7243                 return ref2->remote - ref1->remote;
7244         return strcmp(ref1->name, ref2->name);
7247 static void
7248 foreach_ref(bool (*visitor)(void *data, const struct ref *ref), void *data)
7250         size_t i;
7252         for (i = 0; i < refs_size; i++)
7253                 if (!visitor(data, refs[i]))
7254                         break;
7257 static struct ref_list *
7258 get_ref_list(const char *id)
7260         struct ref_list *list;
7261         size_t i;
7263         for (i = 0; i < ref_lists_size; i++)
7264                 if (!strcmp(id, ref_lists[i]->id))
7265                         return ref_lists[i];
7267         if (!realloc_ref_lists(&ref_lists, ref_lists_size, 1))
7268                 return NULL;
7269         list = calloc(1, sizeof(*list));
7270         if (!list)
7271                 return NULL;
7273         for (i = 0; i < refs_size; i++) {
7274                 if (!strcmp(id, refs[i]->id) &&
7275                     realloc_refs_list(&list->refs, list->size, 1))
7276                         list->refs[list->size++] = refs[i];
7277         }
7279         if (!list->refs) {
7280                 free(list);
7281                 return NULL;
7282         }
7284         qsort(list->refs, list->size, sizeof(*list->refs), compare_refs);
7285         ref_lists[ref_lists_size++] = list;
7286         return list;
7289 static int
7290 read_ref(char *id, size_t idlen, char *name, size_t namelen)
7292         struct ref *ref = NULL;
7293         bool tag = FALSE;
7294         bool ltag = FALSE;
7295         bool remote = FALSE;
7296         bool tracked = FALSE;
7297         bool head = FALSE;
7298         int from = 0, to = refs_size - 1;
7300         if (!prefixcmp(name, "refs/tags/")) {
7301                 if (!suffixcmp(name, namelen, "^{}")) {
7302                         namelen -= 3;
7303                         name[namelen] = 0;
7304                 } else {
7305                         ltag = TRUE;
7306                 }
7308                 tag = TRUE;
7309                 namelen -= STRING_SIZE("refs/tags/");
7310                 name    += STRING_SIZE("refs/tags/");
7312         } else if (!prefixcmp(name, "refs/remotes/")) {
7313                 remote = TRUE;
7314                 namelen -= STRING_SIZE("refs/remotes/");
7315                 name    += STRING_SIZE("refs/remotes/");
7316                 tracked  = !strcmp(opt_remote, name);
7318         } else if (!prefixcmp(name, "refs/heads/")) {
7319                 namelen -= STRING_SIZE("refs/heads/");
7320                 name    += STRING_SIZE("refs/heads/");
7321                 head     = !strncmp(opt_head, name, namelen);
7323         } else if (!strcmp(name, "HEAD")) {
7324                 string_ncopy(opt_head_rev, id, idlen);
7325                 return OK;
7326         }
7328         /* If we are reloading or it's an annotated tag, replace the
7329          * previous SHA1 with the resolved commit id; relies on the fact
7330          * git-ls-remote lists the commit id of an annotated tag right
7331          * before the commit id it points to. */
7332         while (from <= to) {
7333                 size_t pos = (to + from) / 2;
7334                 int cmp = strcmp(name, refs[pos]->name);
7336                 if (!cmp) {
7337                         ref = refs[pos];
7338                         break;
7339                 }
7341                 if (cmp < 0)
7342                         to = pos - 1;
7343                 else
7344                         from = pos + 1;
7345         }
7347         if (!ref) {
7348                 if (!realloc_refs(&refs, refs_size, 1))
7349                         return ERR;
7350                 ref = calloc(1, sizeof(*ref) + namelen);
7351                 if (!ref)
7352                         return ERR;
7353                 memmove(refs + from + 1, refs + from,
7354                         (refs_size - from) * sizeof(*refs));
7355                 refs[from] = ref;
7356                 strncpy(ref->name, name, namelen);
7357                 refs_size++;
7358         }
7360         ref->head = head;
7361         ref->tag = tag;
7362         ref->ltag = ltag;
7363         ref->remote = remote;
7364         ref->tracked = tracked;
7365         string_copy_rev(ref->id, id);
7367         return OK;
7370 static int
7371 load_refs(void)
7373         const char *head_argv[] = {
7374                 "git", "symbolic-ref", "HEAD", NULL
7375         };
7376         static const char *ls_remote_argv[SIZEOF_ARG] = {
7377                 "git", "ls-remote", opt_git_dir, NULL
7378         };
7379         static bool init = FALSE;
7380         size_t i;
7382         if (!init) {
7383                 argv_from_env(ls_remote_argv, "TIG_LS_REMOTE");
7384                 init = TRUE;
7385         }
7387         if (!*opt_git_dir)
7388                 return OK;
7390         if (run_io_buf(head_argv, opt_head, sizeof(opt_head)) &&
7391             !prefixcmp(opt_head, "refs/heads/")) {
7392                 char *offset = opt_head + STRING_SIZE("refs/heads/");
7394                 memmove(opt_head, offset, strlen(offset) + 1);
7395         }
7397         for (i = 0; i < refs_size; i++)
7398                 refs[i]->id[0] = 0;
7400         if (run_io_load(ls_remote_argv, "\t", read_ref) == ERR)
7401                 return ERR;
7403         /* Update the ref lists to reflect changes. */
7404         for (i = 0; i < ref_lists_size; i++) {
7405                 struct ref_list *list = ref_lists[i];
7406                 size_t old, new;
7408                 for (old = new = 0; old < list->size; old++)
7409                         if (!strcmp(list->id, list->refs[old]->id))
7410                                 list->refs[new++] = list->refs[old];
7411                 list->size = new;
7412         }
7414         return OK;
7417 static void
7418 set_remote_branch(const char *name, const char *value, size_t valuelen)
7420         if (!strcmp(name, ".remote")) {
7421                 string_ncopy(opt_remote, value, valuelen);
7423         } else if (*opt_remote && !strcmp(name, ".merge")) {
7424                 size_t from = strlen(opt_remote);
7426                 if (!prefixcmp(value, "refs/heads/"))
7427                         value += STRING_SIZE("refs/heads/");
7429                 if (!string_format_from(opt_remote, &from, "/%s", value))
7430                         opt_remote[0] = 0;
7431         }
7434 static void
7435 set_repo_config_option(char *name, char *value, int (*cmd)(int, const char **))
7437         const char *argv[SIZEOF_ARG] = { name, "=" };
7438         int argc = 1 + (cmd == option_set_command);
7439         int error = ERR;
7441         if (!argv_from_string(argv, &argc, value))
7442                 config_msg = "Too many option arguments";
7443         else
7444                 error = cmd(argc, argv);
7446         if (error == ERR)
7447                 warn("Option 'tig.%s': %s", name, config_msg);
7450 static bool
7451 set_environment_variable(const char *name, const char *value)
7453         size_t len = strlen(name) + 1 + strlen(value) + 1;
7454         char *env = malloc(len);
7456         if (env &&
7457             string_nformat(env, len, NULL, "%s=%s", name, value) &&
7458             putenv(env) == 0)
7459                 return TRUE;
7460         free(env);
7461         return FALSE;
7464 static void
7465 set_work_tree(const char *value)
7467         char cwd[SIZEOF_STR];
7469         if (!getcwd(cwd, sizeof(cwd)))
7470                 die("Failed to get cwd path: %s", strerror(errno));
7471         if (chdir(opt_git_dir) < 0)
7472                 die("Failed to chdir(%s): %s", strerror(errno));
7473         if (!getcwd(opt_git_dir, sizeof(opt_git_dir)))
7474                 die("Failed to get git path: %s", strerror(errno));
7475         if (chdir(cwd) < 0)
7476                 die("Failed to chdir(%s): %s", cwd, strerror(errno));
7477         if (chdir(value) < 0)
7478                 die("Failed to chdir(%s): %s", value, strerror(errno));
7479         if (!getcwd(cwd, sizeof(cwd)))
7480                 die("Failed to get cwd path: %s", strerror(errno));
7481         if (!set_environment_variable("GIT_WORK_TREE", cwd))
7482                 die("Failed to set GIT_WORK_TREE to '%s'", cwd);
7483         if (!set_environment_variable("GIT_DIR", opt_git_dir))
7484                 die("Failed to set GIT_DIR to '%s'", opt_git_dir);
7485         opt_is_inside_work_tree = TRUE;
7488 static int
7489 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
7491         if (!strcmp(name, "i18n.commitencoding"))
7492                 string_ncopy(opt_encoding, value, valuelen);
7494         else if (!strcmp(name, "core.editor"))
7495                 string_ncopy(opt_editor, value, valuelen);
7497         else if (!strcmp(name, "core.worktree"))
7498                 set_work_tree(value);
7500         else if (!prefixcmp(name, "tig.color."))
7501                 set_repo_config_option(name + 10, value, option_color_command);
7503         else if (!prefixcmp(name, "tig.bind."))
7504                 set_repo_config_option(name + 9, value, option_bind_command);
7506         else if (!prefixcmp(name, "tig."))
7507                 set_repo_config_option(name + 4, value, option_set_command);
7509         else if (*opt_head && !prefixcmp(name, "branch.") &&
7510                  !strncmp(name + 7, opt_head, strlen(opt_head)))
7511                 set_remote_branch(name + 7 + strlen(opt_head), value, valuelen);
7513         return OK;
7516 static int
7517 load_git_config(void)
7519         const char *config_list_argv[] = { "git", "config", "--list", NULL };
7521         return run_io_load(config_list_argv, "=", read_repo_config_option);
7524 static int
7525 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
7527         if (!opt_git_dir[0]) {
7528                 string_ncopy(opt_git_dir, name, namelen);
7530         } else if (opt_is_inside_work_tree == -1) {
7531                 /* This can be 3 different values depending on the
7532                  * version of git being used. If git-rev-parse does not
7533                  * understand --is-inside-work-tree it will simply echo
7534                  * the option else either "true" or "false" is printed.
7535                  * Default to true for the unknown case. */
7536                 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
7538         } else if (*name == '.') {
7539                 string_ncopy(opt_cdup, name, namelen);
7541         } else {
7542                 string_ncopy(opt_prefix, name, namelen);
7543         }
7545         return OK;
7548 static int
7549 load_repo_info(void)
7551         const char *rev_parse_argv[] = {
7552                 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
7553                         "--show-cdup", "--show-prefix", NULL
7554         };
7556         return run_io_load(rev_parse_argv, "=", read_repo_info);
7560 /*
7561  * Main
7562  */
7564 static const char usage[] =
7565 "tig " TIG_VERSION " (" __DATE__ ")\n"
7566 "\n"
7567 "Usage: tig        [options] [revs] [--] [paths]\n"
7568 "   or: tig show   [options] [revs] [--] [paths]\n"
7569 "   or: tig blame  [rev] path\n"
7570 "   or: tig status\n"
7571 "   or: tig <      [git command output]\n"
7572 "\n"
7573 "Options:\n"
7574 "  -v, --version   Show version and exit\n"
7575 "  -h, --help      Show help message and exit";
7577 static void __NORETURN
7578 quit(int sig)
7580         /* XXX: Restore tty modes and let the OS cleanup the rest! */
7581         if (cursed)
7582                 endwin();
7583         exit(0);
7586 static void __NORETURN
7587 die(const char *err, ...)
7589         va_list args;
7591         endwin();
7593         va_start(args, err);
7594         fputs("tig: ", stderr);
7595         vfprintf(stderr, err, args);
7596         fputs("\n", stderr);
7597         va_end(args);
7599         exit(1);
7602 static void
7603 warn(const char *msg, ...)
7605         va_list args;
7607         va_start(args, msg);
7608         fputs("tig warning: ", stderr);
7609         vfprintf(stderr, msg, args);
7610         fputs("\n", stderr);
7611         va_end(args);
7614 static enum request
7615 parse_options(int argc, const char *argv[])
7617         enum request request = REQ_VIEW_MAIN;
7618         const char *subcommand;
7619         bool seen_dashdash = FALSE;
7620         /* XXX: This is vulnerable to the user overriding options
7621          * required for the main view parser. */
7622         const char *custom_argv[SIZEOF_ARG] = {
7623                 "git", "log", "--no-color", "--pretty=raw", "--parents",
7624                         "--topo-order", NULL
7625         };
7626         int i, j = 6;
7628         if (!isatty(STDIN_FILENO)) {
7629                 io_open(&VIEW(REQ_VIEW_PAGER)->io, "");
7630                 return REQ_VIEW_PAGER;
7631         }
7633         if (argc <= 1)
7634                 return REQ_NONE;
7636         subcommand = argv[1];
7637         if (!strcmp(subcommand, "status")) {
7638                 if (argc > 2)
7639                         warn("ignoring arguments after `%s'", subcommand);
7640                 return REQ_VIEW_STATUS;
7642         } else if (!strcmp(subcommand, "blame")) {
7643                 if (argc <= 2 || argc > 4)
7644                         die("invalid number of options to blame\n\n%s", usage);
7646                 i = 2;
7647                 if (argc == 4) {
7648                         string_ncopy(opt_ref, argv[i], strlen(argv[i]));
7649                         i++;
7650                 }
7652                 string_ncopy(opt_file, argv[i], strlen(argv[i]));
7653                 return REQ_VIEW_BLAME;
7655         } else if (!strcmp(subcommand, "show")) {
7656                 request = REQ_VIEW_DIFF;
7658         } else {
7659                 subcommand = NULL;
7660         }
7662         if (subcommand) {
7663                 custom_argv[1] = subcommand;
7664                 j = 2;
7665         }
7667         for (i = 1 + !!subcommand; i < argc; i++) {
7668                 const char *opt = argv[i];
7670                 if (seen_dashdash || !strcmp(opt, "--")) {
7671                         seen_dashdash = TRUE;
7673                 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
7674                         printf("tig version %s\n", TIG_VERSION);
7675                         quit(0);
7677                 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
7678                         printf("%s\n", usage);
7679                         quit(0);
7680                 }
7682                 custom_argv[j++] = opt;
7683                 if (j >= ARRAY_SIZE(custom_argv))
7684                         die("command too long");
7685         }
7687         if (!prepare_update(VIEW(request), custom_argv, NULL, FORMAT_NONE))
7688                 die("Failed to format arguments");
7690         return request;
7693 int
7694 main(int argc, const char *argv[])
7696         enum request request = parse_options(argc, argv);
7697         struct view *view;
7698         size_t i;
7700         signal(SIGINT, quit);
7701         signal(SIGPIPE, SIG_IGN);
7703         if (setlocale(LC_ALL, "")) {
7704                 char *codeset = nl_langinfo(CODESET);
7706                 string_ncopy(opt_codeset, codeset, strlen(codeset));
7707         }
7709         if (load_repo_info() == ERR)
7710                 die("Failed to load repo info.");
7712         if (load_options() == ERR)
7713                 die("Failed to load user config.");
7715         if (load_git_config() == ERR)
7716                 die("Failed to load repo config.");
7718         /* Require a git repository unless when running in pager mode. */
7719         if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
7720                 die("Not a git repository");
7722         if (*opt_encoding && strcmp(opt_codeset, "UTF-8")) {
7723                 opt_iconv_in = iconv_open("UTF-8", opt_encoding);
7724                 if (opt_iconv_in == ICONV_NONE)
7725                         die("Failed to initialize character set conversion");
7726         }
7728         if (*opt_codeset && strcmp(opt_codeset, "UTF-8")) {
7729                 opt_iconv_out = iconv_open(opt_codeset, "UTF-8");
7730                 if (opt_iconv_out == ICONV_NONE)
7731                         die("Failed to initialize character set conversion");
7732         }
7734         if (load_refs() == ERR)
7735                 die("Failed to load refs.");
7737         foreach_view (view, i)
7738                 argv_from_env(view->ops->argv, view->cmd_env);
7740         init_display();
7742         if (request != REQ_NONE)
7743                 open_view(NULL, request, OPEN_PREPARED);
7744         request = request == REQ_NONE ? REQ_VIEW_MAIN : REQ_NONE;
7746         while (view_driver(display[current_view], request)) {
7747                 int key = get_input(0);
7749                 view = display[current_view];
7750                 request = get_keybinding(view->keymap, key);
7752                 /* Some low-level request handling. This keeps access to
7753                  * status_win restricted. */
7754                 switch (request) {
7755                 case REQ_PROMPT:
7756                 {
7757                         char *cmd = read_prompt(":");
7759                         if (cmd && isdigit(*cmd)) {
7760                                 int lineno = view->lineno + 1;
7762                                 if (parse_int(&lineno, cmd, 1, view->lines + 1) == OK) {
7763                                         select_view_line(view, lineno - 1);
7764                                         report("");
7765                                 } else {
7766                                         report("Unable to parse '%s' as a line number", cmd);
7767                                 }
7769                         } else if (cmd) {
7770                                 struct view *next = VIEW(REQ_VIEW_PAGER);
7771                                 const char *argv[SIZEOF_ARG] = { "git" };
7772                                 int argc = 1;
7774                                 /* When running random commands, initially show the
7775                                  * command in the title. However, it maybe later be
7776                                  * overwritten if a commit line is selected. */
7777                                 string_ncopy(next->ref, cmd, strlen(cmd));
7779                                 if (!argv_from_string(argv, &argc, cmd)) {
7780                                         report("Too many arguments");
7781                                 } else if (!prepare_update(next, argv, NULL, FORMAT_DASH)) {
7782                                         report("Failed to format command");
7783                                 } else {
7784                                         open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
7785                                 }
7786                         }
7788                         request = REQ_NONE;
7789                         break;
7790                 }
7791                 case REQ_SEARCH:
7792                 case REQ_SEARCH_BACK:
7793                 {
7794                         const char *prompt = request == REQ_SEARCH ? "/" : "?";
7795                         char *search = read_prompt(prompt);
7797                         if (search)
7798                                 string_ncopy(opt_search, search, strlen(search));
7799                         else if (*opt_search)
7800                                 request = request == REQ_SEARCH ?
7801                                         REQ_FIND_NEXT :
7802                                         REQ_FIND_PREV;
7803                         else
7804                                 request = REQ_NONE;
7805                         break;
7806                 }
7807                 default:
7808                         break;
7809                 }
7810         }
7812         quit(0);
7814         return 0;