Code

Mark detached heads with [HEAD]; replace opt_head_rev with a struct ref
[tig.git] / tig.c
1 /* Copyright (c) 2006-2010 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 **start, size_t skip, int *width, size_t max_width, int *trimmed, bool reserve, int tab_size);
73 static inline unsigned char utf8_char_length(const char *string, const char *end);
75 #define ABS(x)          ((x) >= 0  ? (x) : -(x))
76 #define MIN(x, y)       ((x) < (y) ? (x) :  (y))
77 #define MAX(x, y)       ((x) > (y) ? (x) :  (y))
79 #define ARRAY_SIZE(x)   (sizeof(x) / sizeof(x[0]))
80 #define STRING_SIZE(x)  (sizeof(x) - 1)
82 #define SIZEOF_STR      1024    /* Default string size. */
83 #define SIZEOF_REF      256     /* Size of symbolic or SHA1 ID. */
84 #define SIZEOF_REV      41      /* Holds a SHA-1 and an ending NUL. */
85 #define SIZEOF_ARG      32      /* Default argument array size. */
87 /* Revision graph */
89 #define REVGRAPH_INIT   'I'
90 #define REVGRAPH_MERGE  'M'
91 #define REVGRAPH_BRANCH '+'
92 #define REVGRAPH_COMMIT '*'
93 #define REVGRAPH_BOUND  '^'
95 #define SIZEOF_REVGRAPH 19      /* Size of revision ancestry graphics. */
97 /* This color name can be used to refer to the default term colors. */
98 #define COLOR_DEFAULT   (-1)
100 #define ICONV_NONE      ((iconv_t) -1)
101 #ifndef ICONV_CONST
102 #define ICONV_CONST     /* nothing */
103 #endif
105 /* The format and size of the date column in the main view. */
106 #define DATE_FORMAT     "%Y-%m-%d %H:%M"
107 #define DATE_COLS       STRING_SIZE("2006-04-29 14:21 ")
108 #define DATE_SHORT_COLS STRING_SIZE("2006-04-29 ")
110 #define ID_COLS         8
111 #define AUTHOR_COLS     19
113 #define MIN_VIEW_HEIGHT 4
115 #define NULL_ID         "0000000000000000000000000000000000000000"
117 #define S_ISGITLINK(mode) (((mode) & S_IFMT) == 0160000)
119 /* Some ASCII-shorthands fitted into the ncurses namespace. */
120 #define KEY_TAB         '\t'
121 #define KEY_RETURN      '\r'
122 #define KEY_ESC         27
125 struct ref {
126         char id[SIZEOF_REV];    /* Commit SHA1 ID */
127         unsigned int head:1;    /* Is it the current HEAD? */
128         unsigned int tag:1;     /* Is it a tag? */
129         unsigned int ltag:1;    /* If so, is the tag local? */
130         unsigned int remote:1;  /* Is it a remote ref? */
131         unsigned int tracked:1; /* Is it the remote for the current HEAD? */
132         char name[1];           /* Ref name; tag or head names are shortened. */
133 };
135 struct ref_list {
136         char id[SIZEOF_REV];    /* Commit SHA1 ID */
137         size_t size;            /* Number of refs. */
138         struct ref **refs;      /* References for this ID. */
139 };
141 static struct ref *get_ref_head();
142 static struct ref_list *get_ref_list(const char *id);
143 static void foreach_ref(bool (*visitor)(void *data, const struct ref *ref), void *data);
144 static int load_refs(void);
146 enum format_flags {
147         FORMAT_ALL,             /* Perform replacement in all arguments. */
148         FORMAT_DASH,            /* Perform replacement up until "--". */
149         FORMAT_NONE             /* No replacement should be performed. */
150 };
152 static bool format_argv(const char *dst[], const char *src[], enum format_flags flags);
154 enum input_status {
155         INPUT_OK,
156         INPUT_SKIP,
157         INPUT_STOP,
158         INPUT_CANCEL
159 };
161 typedef enum input_status (*input_handler)(void *data, char *buf, int c);
163 static char *prompt_input(const char *prompt, input_handler handler, void *data);
164 static bool prompt_yesno(const char *prompt);
166 struct menu_item {
167         int hotkey;
168         const char *text;
169         void *data;
170 };
172 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected);
174 /*
175  * Allocation helpers ... Entering macro hell to never be seen again.
176  */
178 #define DEFINE_ALLOCATOR(name, type, chunk_size)                                \
179 static type *                                                                   \
180 name(type **mem, size_t size, size_t increase)                                  \
181 {                                                                               \
182         size_t num_chunks = (size + chunk_size - 1) / chunk_size;               \
183         size_t num_chunks_new = (size + increase + chunk_size - 1) / chunk_size;\
184         type *tmp = *mem;                                                       \
185                                                                                 \
186         if (mem == NULL || num_chunks != num_chunks_new) {                      \
187                 tmp = realloc(tmp, num_chunks_new * chunk_size * sizeof(type)); \
188                 if (tmp)                                                        \
189                         *mem = tmp;                                             \
190         }                                                                       \
191                                                                                 \
192         return tmp;                                                             \
195 /*
196  * String helpers
197  */
199 static inline void
200 string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t srclen)
202         if (srclen > dstlen - 1)
203                 srclen = dstlen - 1;
205         strncpy(dst, src, srclen);
206         dst[srclen] = 0;
209 /* Shorthands for safely copying into a fixed buffer. */
211 #define string_copy(dst, src) \
212         string_ncopy_do(dst, sizeof(dst), src, sizeof(src))
214 #define string_ncopy(dst, src, srclen) \
215         string_ncopy_do(dst, sizeof(dst), src, srclen)
217 #define string_copy_rev(dst, src) \
218         string_ncopy_do(dst, SIZEOF_REV, src, SIZEOF_REV - 1)
220 #define string_add(dst, from, src) \
221         string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src))
223 static void
224 string_expand(char *dst, size_t dstlen, const char *src, int tabsize)
226         size_t size, pos;
228         for (size = pos = 0; size < dstlen - 1 && src[pos]; pos++) {
229                 if (src[pos] == '\t') {
230                         size_t expanded = tabsize - (size % tabsize);
232                         if (expanded + size >= dstlen - 1)
233                                 expanded = dstlen - size - 1;
234                         memcpy(dst + size, "        ", expanded);
235                         size += expanded;
236                 } else {
237                         dst[size++] = src[pos];
238                 }
239         }
241         dst[size] = 0;
244 static char *
245 chomp_string(char *name)
247         int namelen;
249         while (isspace(*name))
250                 name++;
252         namelen = strlen(name) - 1;
253         while (namelen > 0 && isspace(name[namelen]))
254                 name[namelen--] = 0;
256         return name;
259 static bool
260 string_nformat(char *buf, size_t bufsize, size_t *bufpos, const char *fmt, ...)
262         va_list args;
263         size_t pos = bufpos ? *bufpos : 0;
265         va_start(args, fmt);
266         pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
267         va_end(args);
269         if (bufpos)
270                 *bufpos = pos;
272         return pos >= bufsize ? FALSE : TRUE;
275 #define string_format(buf, fmt, args...) \
276         string_nformat(buf, sizeof(buf), NULL, fmt, args)
278 #define string_format_from(buf, from, fmt, args...) \
279         string_nformat(buf, sizeof(buf), from, fmt, args)
281 static int
282 string_enum_compare(const char *str1, const char *str2, int len)
284         size_t i;
286 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
288         /* Diff-Header == DIFF_HEADER */
289         for (i = 0; i < len; i++) {
290                 if (toupper(str1[i]) == toupper(str2[i]))
291                         continue;
293                 if (string_enum_sep(str1[i]) &&
294                     string_enum_sep(str2[i]))
295                         continue;
297                 return str1[i] - str2[i];
298         }
300         return 0;
303 #define enum_equals(entry, str, len) \
304         ((entry).namelen == (len) && !string_enum_compare((entry).name, str, len))
306 struct enum_map {
307         const char *name;
308         int namelen;
309         int value;
310 };
312 #define ENUM_MAP(name, value) { name, STRING_SIZE(name), value }
314 static char *
315 enum_map_name(const char *name, size_t namelen)
317         static char buf[SIZEOF_STR];
318         int bufpos;
320         for (bufpos = 0; bufpos <= namelen; bufpos++) {
321                 buf[bufpos] = tolower(name[bufpos]);
322                 if (buf[bufpos] == '_')
323                         buf[bufpos] = '-';
324         }
326         buf[bufpos] = 0;
327         return buf;
330 #define enum_name(entry) enum_map_name((entry).name, (entry).namelen)
332 static bool
333 map_enum_do(const struct enum_map *map, size_t map_size, int *value, const char *name)
335         size_t namelen = strlen(name);
336         int i;
338         for (i = 0; i < map_size; i++)
339                 if (enum_equals(map[i], name, namelen)) {
340                         *value = map[i].value;
341                         return TRUE;
342                 }
344         return FALSE;
347 #define map_enum(attr, map, name) \
348         map_enum_do(map, ARRAY_SIZE(map), attr, name)
350 #define prefixcmp(str1, str2) \
351         strncmp(str1, str2, STRING_SIZE(str2))
353 static inline int
354 suffixcmp(const char *str, int slen, const char *suffix)
356         size_t len = slen >= 0 ? slen : strlen(str);
357         size_t suffixlen = strlen(suffix);
359         return suffixlen < len ? strcmp(str + len - suffixlen, suffix) : -1;
363 #define DATE_INFO \
364         DATE_(NO), \
365         DATE_(DEFAULT), \
366         DATE_(RELATIVE), \
367         DATE_(SHORT)
369 enum date {
370 #define DATE_(name) DATE_##name
371         DATE_INFO
372 #undef  DATE_
373 };
375 static const struct enum_map date_map[] = {
376 #define DATE_(name) ENUM_MAP(#name, DATE_##name)
377         DATE_INFO
378 #undef  DATE_
379 };
381 struct time {
382         time_t sec;
383         int tz;
384 };
386 static inline int timecmp(const struct time *t1, const struct time *t2)
388         return t1->sec - t2->sec;
391 static const char *
392 string_date(const struct time *time, enum date date)
394         static char buf[DATE_COLS + 1];
395         static const struct enum_map reldate[] = {
396                 { "second", 1,                  60 * 2 },
397                 { "minute", 60,                 60 * 60 * 2 },
398                 { "hour",   60 * 60,            60 * 60 * 24 * 2 },
399                 { "day",    60 * 60 * 24,       60 * 60 * 24 * 7 * 2 },
400                 { "week",   60 * 60 * 24 * 7,   60 * 60 * 24 * 7 * 5 },
401                 { "month",  60 * 60 * 24 * 30,  60 * 60 * 24 * 30 * 12 },
402         };
403         struct tm tm;
405         if (date == DATE_RELATIVE) {
406                 struct timeval now;
407                 time_t date = time->sec + time->tz;
408                 time_t seconds;
409                 int i;
411                 gettimeofday(&now, NULL);
412                 seconds = now.tv_sec < date ? date - now.tv_sec : now.tv_sec - date;
413                 for (i = 0; i < ARRAY_SIZE(reldate); i++) {
414                         if (seconds >= reldate[i].value)
415                                 continue;
417                         seconds /= reldate[i].namelen;
418                         if (!string_format(buf, "%ld %s%s %s",
419                                            seconds, reldate[i].name,
420                                            seconds > 1 ? "s" : "",
421                                            now.tv_sec >= date ? "ago" : "ahead"))
422                                 break;
423                         return buf;
424                 }
425         }
427         gmtime_r(&time->sec, &tm);
428         return strftime(buf, sizeof(buf), DATE_FORMAT, &tm) ? buf : NULL;
432 #define AUTHOR_VALUES \
433         AUTHOR_(NO), \
434         AUTHOR_(FULL), \
435         AUTHOR_(ABBREVIATED)
437 enum author {
438 #define AUTHOR_(name) AUTHOR_##name
439         AUTHOR_VALUES,
440 #undef  AUTHOR_
441         AUTHOR_DEFAULT = AUTHOR_FULL
442 };
444 static const struct enum_map author_map[] = {
445 #define AUTHOR_(name) ENUM_MAP(#name, AUTHOR_##name)
446         AUTHOR_VALUES
447 #undef  AUTHOR_
448 };
450 static const char *
451 get_author_initials(const char *author)
453         static char initials[AUTHOR_COLS * 6 + 1];
454         size_t pos = 0;
455         const char *end = strchr(author, '\0');
457 #define is_initial_sep(c) (isspace(c) || ispunct(c) || (c) == '@' || (c) == '-')
459         memset(initials, 0, sizeof(initials));
460         while (author < end) {
461                 unsigned char bytes;
462                 size_t i;
464                 while (is_initial_sep(*author))
465                         author++;
467                 bytes = utf8_char_length(author, end);
468                 if (bytes < sizeof(initials) - 1 - pos) {
469                         while (bytes--) {
470                                 initials[pos++] = *author++;
471                         }
472                 }
474                 for (i = pos; author < end && !is_initial_sep(*author); author++) {
475                         if (i < sizeof(initials) - 1)
476                                 initials[i++] = *author;
477                 }
479                 initials[i++] = 0;
480         }
482         return initials;
486 static bool
487 argv_from_string(const char *argv[SIZEOF_ARG], int *argc, char *cmd)
489         int valuelen;
491         while (*cmd && *argc < SIZEOF_ARG && (valuelen = strcspn(cmd, " \t"))) {
492                 bool advance = cmd[valuelen] != 0;
494                 cmd[valuelen] = 0;
495                 argv[(*argc)++] = chomp_string(cmd);
496                 cmd = chomp_string(cmd + valuelen + advance);
497         }
499         if (*argc < SIZEOF_ARG)
500                 argv[*argc] = NULL;
501         return *argc < SIZEOF_ARG;
504 static void
505 argv_from_env(const char **argv, const char *name)
507         char *env = argv ? getenv(name) : NULL;
508         int argc = 0;
510         if (env && *env)
511                 env = strdup(env);
512         if (env && !argv_from_string(argv, &argc, env))
513                 die("Too many arguments in the `%s` environment variable", name);
517 /*
518  * Executing external commands.
519  */
521 enum io_type {
522         IO_FD,                  /* File descriptor based IO. */
523         IO_BG,                  /* Execute command in the background. */
524         IO_FG,                  /* Execute command with same std{in,out,err}. */
525         IO_RD,                  /* Read only fork+exec IO. */
526         IO_WR,                  /* Write only fork+exec IO. */
527         IO_AP,                  /* Append fork+exec output to file. */
528 };
530 struct io {
531         enum io_type type;      /* The requested type of pipe. */
532         const char *dir;        /* Directory from which to execute. */
533         pid_t pid;              /* Pipe for reading or writing. */
534         int pipe;               /* Pipe end for reading or writing. */
535         int error;              /* Error status. */
536         const char *argv[SIZEOF_ARG];   /* Shell command arguments. */
537         char *buf;              /* Read buffer. */
538         size_t bufalloc;        /* Allocated buffer size. */
539         size_t bufsize;         /* Buffer content size. */
540         char *bufpos;           /* Current buffer position. */
541         unsigned int eof:1;     /* Has end of file been reached. */
542 };
544 static void
545 reset_io(struct io *io)
547         io->pipe = -1;
548         io->pid = 0;
549         io->buf = io->bufpos = NULL;
550         io->bufalloc = io->bufsize = 0;
551         io->error = 0;
552         io->eof = 0;
555 static void
556 init_io(struct io *io, const char *dir, enum io_type type)
558         reset_io(io);
559         io->type = type;
560         io->dir = dir;
563 static bool
564 init_io_rd(struct io *io, const char *argv[], const char *dir,
565                 enum format_flags flags)
567         init_io(io, dir, IO_RD);
568         return format_argv(io->argv, argv, flags);
571 static bool
572 io_open(struct io *io, const char *fmt, ...)
574         char name[SIZEOF_STR] = "";
575         bool fits;
576         va_list args;
578         init_io(io, NULL, IO_FD);
580         va_start(args, fmt);
581         fits = vsnprintf(name, sizeof(name), fmt, args) < sizeof(name);
582         va_end(args);
584         if (!fits) {
585                 io->error = ENAMETOOLONG;
586                 return FALSE;
587         }
588         io->pipe = *name ? open(name, O_RDONLY) : STDIN_FILENO;
589         if (io->pipe == -1)
590                 io->error = errno;
591         return io->pipe != -1;
594 static bool
595 kill_io(struct io *io)
597         return io->pid == 0 || kill(io->pid, SIGKILL) != -1;
600 static bool
601 done_io(struct io *io)
603         pid_t pid = io->pid;
605         if (io->pipe != -1)
606                 close(io->pipe);
607         free(io->buf);
608         reset_io(io);
610         while (pid > 0) {
611                 int status;
612                 pid_t waiting = waitpid(pid, &status, 0);
614                 if (waiting < 0) {
615                         if (errno == EINTR)
616                                 continue;
617                         report("waitpid failed (%s)", strerror(errno));
618                         return FALSE;
619                 }
621                 return waiting == pid &&
622                        !WIFSIGNALED(status) &&
623                        WIFEXITED(status) &&
624                        !WEXITSTATUS(status);
625         }
627         return TRUE;
630 static bool
631 start_io(struct io *io)
633         int pipefds[2] = { -1, -1 };
635         if (io->type == IO_FD)
636                 return TRUE;
638         if ((io->type == IO_RD || io->type == IO_WR) &&
639             pipe(pipefds) < 0)
640                 return FALSE;
641         else if (io->type == IO_AP)
642                 pipefds[1] = io->pipe;
644         if ((io->pid = fork())) {
645                 if (pipefds[!(io->type == IO_WR)] != -1)
646                         close(pipefds[!(io->type == IO_WR)]);
647                 if (io->pid != -1) {
648                         io->pipe = pipefds[!!(io->type == IO_WR)];
649                         return TRUE;
650                 }
652         } else {
653                 if (io->type != IO_FG) {
654                         int devnull = open("/dev/null", O_RDWR);
655                         int readfd  = io->type == IO_WR ? pipefds[0] : devnull;
656                         int writefd = (io->type == IO_RD || io->type == IO_AP)
657                                                         ? pipefds[1] : devnull;
659                         dup2(readfd,  STDIN_FILENO);
660                         dup2(writefd, STDOUT_FILENO);
661                         dup2(devnull, STDERR_FILENO);
663                         close(devnull);
664                         if (pipefds[0] != -1)
665                                 close(pipefds[0]);
666                         if (pipefds[1] != -1)
667                                 close(pipefds[1]);
668                 }
670                 if (io->dir && *io->dir && chdir(io->dir) == -1)
671                         die("Failed to change directory: %s", strerror(errno));
673                 execvp(io->argv[0], (char *const*) io->argv);
674                 die("Failed to execute program: %s", strerror(errno));
675         }
677         if (pipefds[!!(io->type == IO_WR)] != -1)
678                 close(pipefds[!!(io->type == IO_WR)]);
679         return FALSE;
682 static bool
683 run_io(struct io *io, const char **argv, const char *dir, enum io_type type)
685         init_io(io, dir, type);
686         if (!format_argv(io->argv, argv, FORMAT_NONE))
687                 return FALSE;
688         return start_io(io);
691 static int
692 run_io_do(struct io *io)
694         return start_io(io) && done_io(io);
697 static int
698 run_io_bg(const char **argv)
700         struct io io = {};
702         init_io(&io, NULL, IO_BG);
703         if (!format_argv(io.argv, argv, FORMAT_NONE))
704                 return FALSE;
705         return run_io_do(&io);
708 static bool
709 run_io_fg(const char **argv, const char *dir)
711         struct io io = {};
713         init_io(&io, dir, IO_FG);
714         if (!format_argv(io.argv, argv, FORMAT_NONE))
715                 return FALSE;
716         return run_io_do(&io);
719 static bool
720 run_io_append(const char **argv, enum format_flags flags, int fd)
722         struct io io = {};
724         init_io(&io, NULL, IO_AP);
725         io.pipe = fd;
726         if (format_argv(io.argv, argv, flags))
727                 return run_io_do(&io);
728         close(fd);
729         return FALSE;
732 static bool
733 run_io_rd(struct io *io, const char **argv, const char *dir, enum format_flags flags)
735         return init_io_rd(io, argv, dir, flags) && start_io(io);
738 static bool
739 io_eof(struct io *io)
741         return io->eof;
744 static int
745 io_error(struct io *io)
747         return io->error;
750 static char *
751 io_strerror(struct io *io)
753         return strerror(io->error);
756 static bool
757 io_can_read(struct io *io)
759         struct timeval tv = { 0, 500 };
760         fd_set fds;
762         FD_ZERO(&fds);
763         FD_SET(io->pipe, &fds);
765         return select(io->pipe + 1, &fds, NULL, NULL, &tv) > 0;
768 static ssize_t
769 io_read(struct io *io, void *buf, size_t bufsize)
771         do {
772                 ssize_t readsize = read(io->pipe, buf, bufsize);
774                 if (readsize < 0 && (errno == EAGAIN || errno == EINTR))
775                         continue;
776                 else if (readsize == -1)
777                         io->error = errno;
778                 else if (readsize == 0)
779                         io->eof = 1;
780                 return readsize;
781         } while (1);
784 DEFINE_ALLOCATOR(realloc_io_buf, char, BUFSIZ)
786 static char *
787 io_get(struct io *io, int c, bool can_read)
789         char *eol;
790         ssize_t readsize;
792         while (TRUE) {
793                 if (io->bufsize > 0) {
794                         eol = memchr(io->bufpos, c, io->bufsize);
795                         if (eol) {
796                                 char *line = io->bufpos;
798                                 *eol = 0;
799                                 io->bufpos = eol + 1;
800                                 io->bufsize -= io->bufpos - line;
801                                 return line;
802                         }
803                 }
805                 if (io_eof(io)) {
806                         if (io->bufsize) {
807                                 io->bufpos[io->bufsize] = 0;
808                                 io->bufsize = 0;
809                                 return io->bufpos;
810                         }
811                         return NULL;
812                 }
814                 if (!can_read)
815                         return NULL;
817                 if (io->bufsize > 0 && io->bufpos > io->buf)
818                         memmove(io->buf, io->bufpos, io->bufsize);
820                 if (io->bufalloc == io->bufsize) {
821                         if (!realloc_io_buf(&io->buf, io->bufalloc, BUFSIZ))
822                                 return NULL;
823                         io->bufalloc += BUFSIZ;
824                 }
826                 io->bufpos = io->buf;
827                 readsize = io_read(io, io->buf + io->bufsize, io->bufalloc - io->bufsize);
828                 if (io_error(io))
829                         return NULL;
830                 io->bufsize += readsize;
831         }
834 static bool
835 io_write(struct io *io, const void *buf, size_t bufsize)
837         size_t written = 0;
839         while (!io_error(io) && written < bufsize) {
840                 ssize_t size;
842                 size = write(io->pipe, buf + written, bufsize - written);
843                 if (size < 0 && (errno == EAGAIN || errno == EINTR))
844                         continue;
845                 else if (size == -1)
846                         io->error = errno;
847                 else
848                         written += size;
849         }
851         return written == bufsize;
854 static bool
855 io_read_buf(struct io *io, char buf[], size_t bufsize)
857         char *result = io_get(io, '\n', TRUE);
859         if (result) {
860                 result = chomp_string(result);
861                 string_ncopy_do(buf, bufsize, result, strlen(result));
862         }
864         return done_io(io) && result;
867 static bool
868 run_io_buf(const char **argv, char buf[], size_t bufsize)
870         struct io io = {};
872         return run_io_rd(&io, argv, NULL, FORMAT_NONE)
873             && io_read_buf(&io, buf, bufsize);
876 static int
877 io_load(struct io *io, const char *separators,
878         int (*read_property)(char *, size_t, char *, size_t))
880         char *name;
881         int state = OK;
883         if (!start_io(io))
884                 return ERR;
886         while (state == OK && (name = io_get(io, '\n', TRUE))) {
887                 char *value;
888                 size_t namelen;
889                 size_t valuelen;
891                 name = chomp_string(name);
892                 namelen = strcspn(name, separators);
894                 if (name[namelen]) {
895                         name[namelen] = 0;
896                         value = chomp_string(name + namelen + 1);
897                         valuelen = strlen(value);
899                 } else {
900                         value = "";
901                         valuelen = 0;
902                 }
904                 state = read_property(name, namelen, value, valuelen);
905         }
907         if (state != ERR && io_error(io))
908                 state = ERR;
909         done_io(io);
911         return state;
914 static int
915 run_io_load(const char **argv, const char *separators,
916             int (*read_property)(char *, size_t, char *, size_t))
918         struct io io = {};
920         return init_io_rd(&io, argv, NULL, FORMAT_NONE)
921                 ? io_load(&io, separators, read_property) : ERR;
925 /*
926  * User requests
927  */
929 #define REQ_INFO \
930         /* XXX: Keep the view request first and in sync with views[]. */ \
931         REQ_GROUP("View switching") \
932         REQ_(VIEW_MAIN,         "Show main view"), \
933         REQ_(VIEW_DIFF,         "Show diff view"), \
934         REQ_(VIEW_LOG,          "Show log view"), \
935         REQ_(VIEW_TREE,         "Show tree view"), \
936         REQ_(VIEW_BLOB,         "Show blob view"), \
937         REQ_(VIEW_BLAME,        "Show blame view"), \
938         REQ_(VIEW_BRANCH,       "Show branch view"), \
939         REQ_(VIEW_HELP,         "Show help page"), \
940         REQ_(VIEW_PAGER,        "Show pager view"), \
941         REQ_(VIEW_STATUS,       "Show status view"), \
942         REQ_(VIEW_STAGE,        "Show stage view"), \
943         \
944         REQ_GROUP("View manipulation") \
945         REQ_(ENTER,             "Enter current line and scroll"), \
946         REQ_(NEXT,              "Move to next"), \
947         REQ_(PREVIOUS,          "Move to previous"), \
948         REQ_(PARENT,            "Move to parent"), \
949         REQ_(VIEW_NEXT,         "Move focus to next view"), \
950         REQ_(REFRESH,           "Reload and refresh"), \
951         REQ_(MAXIMIZE,          "Maximize the current view"), \
952         REQ_(VIEW_CLOSE,        "Close the current view"), \
953         REQ_(QUIT,              "Close all views and quit"), \
954         \
955         REQ_GROUP("View specific requests") \
956         REQ_(STATUS_UPDATE,     "Update file status"), \
957         REQ_(STATUS_REVERT,     "Revert file changes"), \
958         REQ_(STATUS_MERGE,      "Merge file using external tool"), \
959         REQ_(STAGE_NEXT,        "Find next chunk to stage"), \
960         \
961         REQ_GROUP("Cursor navigation") \
962         REQ_(MOVE_UP,           "Move cursor one line up"), \
963         REQ_(MOVE_DOWN,         "Move cursor one line down"), \
964         REQ_(MOVE_PAGE_DOWN,    "Move cursor one page down"), \
965         REQ_(MOVE_PAGE_UP,      "Move cursor one page up"), \
966         REQ_(MOVE_FIRST_LINE,   "Move cursor to first line"), \
967         REQ_(MOVE_LAST_LINE,    "Move cursor to last line"), \
968         \
969         REQ_GROUP("Scrolling") \
970         REQ_(SCROLL_LEFT,       "Scroll two columns left"), \
971         REQ_(SCROLL_RIGHT,      "Scroll two columns right"), \
972         REQ_(SCROLL_LINE_UP,    "Scroll one line up"), \
973         REQ_(SCROLL_LINE_DOWN,  "Scroll one line down"), \
974         REQ_(SCROLL_PAGE_UP,    "Scroll one page up"), \
975         REQ_(SCROLL_PAGE_DOWN,  "Scroll one page down"), \
976         \
977         REQ_GROUP("Searching") \
978         REQ_(SEARCH,            "Search the view"), \
979         REQ_(SEARCH_BACK,       "Search backwards in the view"), \
980         REQ_(FIND_NEXT,         "Find next search match"), \
981         REQ_(FIND_PREV,         "Find previous search match"), \
982         \
983         REQ_GROUP("Option manipulation") \
984         REQ_(OPTIONS,           "Open option menu"), \
985         REQ_(TOGGLE_LINENO,     "Toggle line numbers"), \
986         REQ_(TOGGLE_DATE,       "Toggle date display"), \
987         REQ_(TOGGLE_DATE_SHORT, "Toggle short (date-only) dates"), \
988         REQ_(TOGGLE_AUTHOR,     "Toggle author display"), \
989         REQ_(TOGGLE_REV_GRAPH,  "Toggle revision graph visualization"), \
990         REQ_(TOGGLE_REFS,       "Toggle reference display (tags/branches)"), \
991         REQ_(TOGGLE_SORT_ORDER, "Toggle ascending/descending sort order"), \
992         REQ_(TOGGLE_SORT_FIELD, "Toggle field to sort by"), \
993         \
994         REQ_GROUP("Misc") \
995         REQ_(PROMPT,            "Bring up the prompt"), \
996         REQ_(SCREEN_REDRAW,     "Redraw the screen"), \
997         REQ_(SHOW_VERSION,      "Show version information"), \
998         REQ_(STOP_LOADING,      "Stop all loading views"), \
999         REQ_(EDIT,              "Open in editor"), \
1000         REQ_(NONE,              "Do nothing")
1003 /* User action requests. */
1004 enum request {
1005 #define REQ_GROUP(help)
1006 #define REQ_(req, help) REQ_##req
1008         /* Offset all requests to avoid conflicts with ncurses getch values. */
1009         REQ_OFFSET = KEY_MAX + 1,
1010         REQ_INFO
1012 #undef  REQ_GROUP
1013 #undef  REQ_
1014 };
1016 struct request_info {
1017         enum request request;
1018         const char *name;
1019         int namelen;
1020         const char *help;
1021 };
1023 static const struct request_info req_info[] = {
1024 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
1025 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
1026         REQ_INFO
1027 #undef  REQ_GROUP
1028 #undef  REQ_
1029 };
1031 static enum request
1032 get_request(const char *name)
1034         int namelen = strlen(name);
1035         int i;
1037         for (i = 0; i < ARRAY_SIZE(req_info); i++)
1038                 if (enum_equals(req_info[i], name, namelen))
1039                         return req_info[i].request;
1041         return REQ_NONE;
1045 /*
1046  * Options
1047  */
1049 /* Option and state variables. */
1050 static enum date opt_date               = DATE_DEFAULT;
1051 static enum author opt_author           = AUTHOR_DEFAULT;
1052 static bool opt_line_number             = FALSE;
1053 static bool opt_line_graphics           = TRUE;
1054 static bool opt_rev_graph               = FALSE;
1055 static bool opt_show_refs               = TRUE;
1056 static int opt_num_interval             = 5;
1057 static double opt_hscroll               = 0.50;
1058 static double opt_scale_split_view      = 2.0 / 3.0;
1059 static int opt_tab_size                 = 8;
1060 static int opt_author_cols              = AUTHOR_COLS;
1061 static char opt_path[SIZEOF_STR]        = "";
1062 static char opt_file[SIZEOF_STR]        = "";
1063 static char opt_ref[SIZEOF_REF]         = "";
1064 static char opt_head[SIZEOF_REF]        = "";
1065 static char opt_remote[SIZEOF_REF]      = "";
1066 static char opt_encoding[20]            = "UTF-8";
1067 static iconv_t opt_iconv_in             = ICONV_NONE;
1068 static iconv_t opt_iconv_out            = ICONV_NONE;
1069 static char opt_search[SIZEOF_STR]      = "";
1070 static char opt_cdup[SIZEOF_STR]        = "";
1071 static char opt_prefix[SIZEOF_STR]      = "";
1072 static char opt_git_dir[SIZEOF_STR]     = "";
1073 static signed char opt_is_inside_work_tree      = -1; /* set to TRUE or FALSE */
1074 static char opt_editor[SIZEOF_STR]      = "";
1075 static FILE *opt_tty                    = NULL;
1077 #define is_initial_commit()     (!get_ref_head())
1078 #define is_head_commit(rev)     (!strcmp((rev), "HEAD") || (get_ref_head() && !strcmp(rev, get_ref_head()->id)))
1079 #define mkdate(time)            string_date(time, opt_date)
1082 /*
1083  * Line-oriented content detection.
1084  */
1086 #define LINE_INFO \
1087 LINE(DIFF_HEADER,  "diff --git ",       COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1088 LINE(DIFF_CHUNK,   "@@",                COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1089 LINE(DIFF_ADD,     "+",                 COLOR_GREEN,    COLOR_DEFAULT,  0), \
1090 LINE(DIFF_DEL,     "-",                 COLOR_RED,      COLOR_DEFAULT,  0), \
1091 LINE(DIFF_INDEX,        "index ",         COLOR_BLUE,   COLOR_DEFAULT,  0), \
1092 LINE(DIFF_OLDMODE,      "old file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
1093 LINE(DIFF_NEWMODE,      "new file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
1094 LINE(DIFF_COPY_FROM,    "copy from",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
1095 LINE(DIFF_COPY_TO,      "copy to",        COLOR_YELLOW, COLOR_DEFAULT,  0), \
1096 LINE(DIFF_RENAME_FROM,  "rename from",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
1097 LINE(DIFF_RENAME_TO,    "rename to",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
1098 LINE(DIFF_SIMILARITY,   "similarity ",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
1099 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
1100 LINE(DIFF_TREE,         "diff-tree ",     COLOR_BLUE,   COLOR_DEFAULT,  0), \
1101 LINE(PP_AUTHOR,    "Author: ",          COLOR_CYAN,     COLOR_DEFAULT,  0), \
1102 LINE(PP_COMMIT,    "Commit: ",          COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1103 LINE(PP_MERGE,     "Merge: ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
1104 LINE(PP_DATE,      "Date:   ",          COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1105 LINE(PP_ADATE,     "AuthorDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1106 LINE(PP_CDATE,     "CommitDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1107 LINE(PP_REFS,      "Refs: ",            COLOR_RED,      COLOR_DEFAULT,  0), \
1108 LINE(COMMIT,       "commit ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
1109 LINE(PARENT,       "parent ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
1110 LINE(TREE,         "tree ",             COLOR_BLUE,     COLOR_DEFAULT,  0), \
1111 LINE(AUTHOR,       "author ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
1112 LINE(COMMITTER,    "committer ",        COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1113 LINE(SIGNOFF,      "    Signed-off-by", COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1114 LINE(ACKED,        "    Acked-by",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1115 LINE(DEFAULT,      "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
1116 LINE(CURSOR,       "",                  COLOR_WHITE,    COLOR_GREEN,    A_BOLD), \
1117 LINE(STATUS,       "",                  COLOR_GREEN,    COLOR_DEFAULT,  0), \
1118 LINE(DELIMITER,    "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1119 LINE(DATE,         "",                  COLOR_BLUE,     COLOR_DEFAULT,  0), \
1120 LINE(MODE,         "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1121 LINE(LINE_NUMBER,  "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1122 LINE(TITLE_BLUR,   "",                  COLOR_WHITE,    COLOR_BLUE,     0), \
1123 LINE(TITLE_FOCUS,  "",                  COLOR_WHITE,    COLOR_BLUE,     A_BOLD), \
1124 LINE(MAIN_COMMIT,  "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
1125 LINE(MAIN_TAG,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  A_BOLD), \
1126 LINE(MAIN_LOCAL_TAG,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1127 LINE(MAIN_REMOTE,  "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1128 LINE(MAIN_TRACKED, "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_BOLD), \
1129 LINE(MAIN_REF,     "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1130 LINE(MAIN_HEAD,    "",                  COLOR_CYAN,     COLOR_DEFAULT,  A_BOLD), \
1131 LINE(MAIN_REVGRAPH,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1132 LINE(TREE_HEAD,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_BOLD), \
1133 LINE(TREE_DIR,     "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_NORMAL), \
1134 LINE(TREE_FILE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
1135 LINE(STAT_HEAD,    "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1136 LINE(STAT_SECTION, "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1137 LINE(STAT_NONE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
1138 LINE(STAT_STAGED,  "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1139 LINE(STAT_UNSTAGED,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1140 LINE(STAT_UNTRACKED,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1141 LINE(HELP_KEYMAP,  "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1142 LINE(HELP_GROUP,   "",                  COLOR_BLUE,     COLOR_DEFAULT,  0), \
1143 LINE(BLAME_ID,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0)
1145 enum line_type {
1146 #define LINE(type, line, fg, bg, attr) \
1147         LINE_##type
1148         LINE_INFO,
1149         LINE_NONE
1150 #undef  LINE
1151 };
1153 struct line_info {
1154         const char *name;       /* Option name. */
1155         int namelen;            /* Size of option name. */
1156         const char *line;       /* The start of line to match. */
1157         int linelen;            /* Size of string to match. */
1158         int fg, bg, attr;       /* Color and text attributes for the lines. */
1159 };
1161 static struct line_info line_info[] = {
1162 #define LINE(type, line, fg, bg, attr) \
1163         { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
1164         LINE_INFO
1165 #undef  LINE
1166 };
1168 static enum line_type
1169 get_line_type(const char *line)
1171         int linelen = strlen(line);
1172         enum line_type type;
1174         for (type = 0; type < ARRAY_SIZE(line_info); type++)
1175                 /* Case insensitive search matches Signed-off-by lines better. */
1176                 if (linelen >= line_info[type].linelen &&
1177                     !strncasecmp(line_info[type].line, line, line_info[type].linelen))
1178                         return type;
1180         return LINE_DEFAULT;
1183 static inline int
1184 get_line_attr(enum line_type type)
1186         assert(type < ARRAY_SIZE(line_info));
1187         return COLOR_PAIR(type) | line_info[type].attr;
1190 static struct line_info *
1191 get_line_info(const char *name)
1193         size_t namelen = strlen(name);
1194         enum line_type type;
1196         for (type = 0; type < ARRAY_SIZE(line_info); type++)
1197                 if (enum_equals(line_info[type], name, namelen))
1198                         return &line_info[type];
1200         return NULL;
1203 static void
1204 init_colors(void)
1206         int default_bg = line_info[LINE_DEFAULT].bg;
1207         int default_fg = line_info[LINE_DEFAULT].fg;
1208         enum line_type type;
1210         start_color();
1212         if (assume_default_colors(default_fg, default_bg) == ERR) {
1213                 default_bg = COLOR_BLACK;
1214                 default_fg = COLOR_WHITE;
1215         }
1217         for (type = 0; type < ARRAY_SIZE(line_info); type++) {
1218                 struct line_info *info = &line_info[type];
1219                 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
1220                 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
1222                 init_pair(type, fg, bg);
1223         }
1226 struct line {
1227         enum line_type type;
1229         /* State flags */
1230         unsigned int selected:1;
1231         unsigned int dirty:1;
1232         unsigned int cleareol:1;
1233         unsigned int other:16;
1235         void *data;             /* User data */
1236 };
1239 /*
1240  * Keys
1241  */
1243 struct keybinding {
1244         int alias;
1245         enum request request;
1246 };
1248 static const struct keybinding default_keybindings[] = {
1249         /* View switching */
1250         { 'm',          REQ_VIEW_MAIN },
1251         { 'd',          REQ_VIEW_DIFF },
1252         { 'l',          REQ_VIEW_LOG },
1253         { 't',          REQ_VIEW_TREE },
1254         { 'f',          REQ_VIEW_BLOB },
1255         { 'B',          REQ_VIEW_BLAME },
1256         { 'H',          REQ_VIEW_BRANCH },
1257         { 'p',          REQ_VIEW_PAGER },
1258         { 'h',          REQ_VIEW_HELP },
1259         { 'S',          REQ_VIEW_STATUS },
1260         { 'c',          REQ_VIEW_STAGE },
1262         /* View manipulation */
1263         { 'q',          REQ_VIEW_CLOSE },
1264         { KEY_TAB,      REQ_VIEW_NEXT },
1265         { KEY_RETURN,   REQ_ENTER },
1266         { KEY_UP,       REQ_PREVIOUS },
1267         { KEY_DOWN,     REQ_NEXT },
1268         { 'R',          REQ_REFRESH },
1269         { KEY_F(5),     REQ_REFRESH },
1270         { 'O',          REQ_MAXIMIZE },
1272         /* Cursor navigation */
1273         { 'k',          REQ_MOVE_UP },
1274         { 'j',          REQ_MOVE_DOWN },
1275         { KEY_HOME,     REQ_MOVE_FIRST_LINE },
1276         { KEY_END,      REQ_MOVE_LAST_LINE },
1277         { KEY_NPAGE,    REQ_MOVE_PAGE_DOWN },
1278         { ' ',          REQ_MOVE_PAGE_DOWN },
1279         { KEY_PPAGE,    REQ_MOVE_PAGE_UP },
1280         { 'b',          REQ_MOVE_PAGE_UP },
1281         { '-',          REQ_MOVE_PAGE_UP },
1283         /* Scrolling */
1284         { KEY_LEFT,     REQ_SCROLL_LEFT },
1285         { KEY_RIGHT,    REQ_SCROLL_RIGHT },
1286         { KEY_IC,       REQ_SCROLL_LINE_UP },
1287         { KEY_DC,       REQ_SCROLL_LINE_DOWN },
1288         { 'w',          REQ_SCROLL_PAGE_UP },
1289         { 's',          REQ_SCROLL_PAGE_DOWN },
1291         /* Searching */
1292         { '/',          REQ_SEARCH },
1293         { '?',          REQ_SEARCH_BACK },
1294         { 'n',          REQ_FIND_NEXT },
1295         { 'N',          REQ_FIND_PREV },
1297         /* Misc */
1298         { 'Q',          REQ_QUIT },
1299         { 'z',          REQ_STOP_LOADING },
1300         { 'v',          REQ_SHOW_VERSION },
1301         { 'r',          REQ_SCREEN_REDRAW },
1302         { 'o',          REQ_OPTIONS },
1303         { '.',          REQ_TOGGLE_LINENO },
1304         { 'D',          REQ_TOGGLE_DATE },
1305         { 'A',          REQ_TOGGLE_AUTHOR },
1306         { 'g',          REQ_TOGGLE_REV_GRAPH },
1307         { 'F',          REQ_TOGGLE_REFS },
1308         { 'I',          REQ_TOGGLE_SORT_ORDER },
1309         { 'i',          REQ_TOGGLE_SORT_FIELD },
1310         { ':',          REQ_PROMPT },
1311         { 'u',          REQ_STATUS_UPDATE },
1312         { '!',          REQ_STATUS_REVERT },
1313         { 'M',          REQ_STATUS_MERGE },
1314         { '@',          REQ_STAGE_NEXT },
1315         { ',',          REQ_PARENT },
1316         { 'e',          REQ_EDIT },
1317 };
1319 #define KEYMAP_INFO \
1320         KEYMAP_(GENERIC), \
1321         KEYMAP_(MAIN), \
1322         KEYMAP_(DIFF), \
1323         KEYMAP_(LOG), \
1324         KEYMAP_(TREE), \
1325         KEYMAP_(BLOB), \
1326         KEYMAP_(BLAME), \
1327         KEYMAP_(BRANCH), \
1328         KEYMAP_(PAGER), \
1329         KEYMAP_(HELP), \
1330         KEYMAP_(STATUS), \
1331         KEYMAP_(STAGE)
1333 enum keymap {
1334 #define KEYMAP_(name) KEYMAP_##name
1335         KEYMAP_INFO
1336 #undef  KEYMAP_
1337 };
1339 static const struct enum_map keymap_table[] = {
1340 #define KEYMAP_(name) ENUM_MAP(#name, KEYMAP_##name)
1341         KEYMAP_INFO
1342 #undef  KEYMAP_
1343 };
1345 #define set_keymap(map, name) map_enum(map, keymap_table, name)
1347 struct keybinding_table {
1348         struct keybinding *data;
1349         size_t size;
1350 };
1352 static struct keybinding_table keybindings[ARRAY_SIZE(keymap_table)];
1354 static void
1355 add_keybinding(enum keymap keymap, enum request request, int key)
1357         struct keybinding_table *table = &keybindings[keymap];
1359         table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data));
1360         if (!table->data)
1361                 die("Failed to allocate keybinding");
1362         table->data[table->size].alias = key;
1363         table->data[table->size++].request = request;
1366 /* Looks for a key binding first in the given map, then in the generic map, and
1367  * lastly in the default keybindings. */
1368 static enum request
1369 get_keybinding(enum keymap keymap, int key)
1371         size_t i;
1373         for (i = 0; i < keybindings[keymap].size; i++)
1374                 if (keybindings[keymap].data[i].alias == key)
1375                         return keybindings[keymap].data[i].request;
1377         for (i = 0; i < keybindings[KEYMAP_GENERIC].size; i++)
1378                 if (keybindings[KEYMAP_GENERIC].data[i].alias == key)
1379                         return keybindings[KEYMAP_GENERIC].data[i].request;
1381         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1382                 if (default_keybindings[i].alias == key)
1383                         return default_keybindings[i].request;
1385         return (enum request) key;
1389 struct key {
1390         const char *name;
1391         int value;
1392 };
1394 static const struct key key_table[] = {
1395         { "Enter",      KEY_RETURN },
1396         { "Space",      ' ' },
1397         { "Backspace",  KEY_BACKSPACE },
1398         { "Tab",        KEY_TAB },
1399         { "Escape",     KEY_ESC },
1400         { "Left",       KEY_LEFT },
1401         { "Right",      KEY_RIGHT },
1402         { "Up",         KEY_UP },
1403         { "Down",       KEY_DOWN },
1404         { "Insert",     KEY_IC },
1405         { "Delete",     KEY_DC },
1406         { "Hash",       '#' },
1407         { "Home",       KEY_HOME },
1408         { "End",        KEY_END },
1409         { "PageUp",     KEY_PPAGE },
1410         { "PageDown",   KEY_NPAGE },
1411         { "F1",         KEY_F(1) },
1412         { "F2",         KEY_F(2) },
1413         { "F3",         KEY_F(3) },
1414         { "F4",         KEY_F(4) },
1415         { "F5",         KEY_F(5) },
1416         { "F6",         KEY_F(6) },
1417         { "F7",         KEY_F(7) },
1418         { "F8",         KEY_F(8) },
1419         { "F9",         KEY_F(9) },
1420         { "F10",        KEY_F(10) },
1421         { "F11",        KEY_F(11) },
1422         { "F12",        KEY_F(12) },
1423 };
1425 static int
1426 get_key_value(const char *name)
1428         int i;
1430         for (i = 0; i < ARRAY_SIZE(key_table); i++)
1431                 if (!strcasecmp(key_table[i].name, name))
1432                         return key_table[i].value;
1434         if (strlen(name) == 1 && isprint(*name))
1435                 return (int) *name;
1437         return ERR;
1440 static const char *
1441 get_key_name(int key_value)
1443         static char key_char[] = "'X'";
1444         const char *seq = NULL;
1445         int key;
1447         for (key = 0; key < ARRAY_SIZE(key_table); key++)
1448                 if (key_table[key].value == key_value)
1449                         seq = key_table[key].name;
1451         if (seq == NULL &&
1452             key_value < 127 &&
1453             isprint(key_value)) {
1454                 key_char[1] = (char) key_value;
1455                 seq = key_char;
1456         }
1458         return seq ? seq : "(no key)";
1461 static bool
1462 append_key(char *buf, size_t *pos, const struct keybinding *keybinding)
1464         const char *sep = *pos > 0 ? ", " : "";
1465         const char *keyname = get_key_name(keybinding->alias);
1467         return string_nformat(buf, BUFSIZ, pos, "%s%s", sep, keyname);
1470 static bool
1471 append_keymap_request_keys(char *buf, size_t *pos, enum request request,
1472                            enum keymap keymap, bool all)
1474         int i;
1476         for (i = 0; i < keybindings[keymap].size; i++) {
1477                 if (keybindings[keymap].data[i].request == request) {
1478                         if (!append_key(buf, pos, &keybindings[keymap].data[i]))
1479                                 return FALSE;
1480                         if (!all)
1481                                 break;
1482                 }
1483         }
1485         return TRUE;
1488 #define get_key(keymap, request) get_keys(keymap, request, FALSE)
1490 static const char *
1491 get_keys(enum keymap keymap, enum request request, bool all)
1493         static char buf[BUFSIZ];
1494         size_t pos = 0;
1495         int i;
1497         buf[pos] = 0;
1499         if (!append_keymap_request_keys(buf, &pos, request, keymap, all))
1500                 return "Too many keybindings!";
1501         if (pos > 0 && !all)
1502                 return buf;
1504         if (keymap != KEYMAP_GENERIC) {
1505                 /* Only the generic keymap includes the default keybindings when
1506                  * listing all keys. */
1507                 if (all)
1508                         return buf;
1510                 if (!append_keymap_request_keys(buf, &pos, request, KEYMAP_GENERIC, all))
1511                         return "Too many keybindings!";
1512                 if (pos)
1513                         return buf;
1514         }
1516         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1517                 if (default_keybindings[i].request == request) {
1518                         if (!append_key(buf, &pos, &default_keybindings[i]))
1519                                 return "Too many keybindings!";
1520                         if (!all)
1521                                 return buf;
1522                 }
1523         }
1525         return buf;
1528 struct run_request {
1529         enum keymap keymap;
1530         int key;
1531         const char *argv[SIZEOF_ARG];
1532 };
1534 static struct run_request *run_request;
1535 static size_t run_requests;
1537 DEFINE_ALLOCATOR(realloc_run_requests, struct run_request, 8)
1539 static enum request
1540 add_run_request(enum keymap keymap, int key, int argc, const char **argv)
1542         struct run_request *req;
1544         if (argc >= ARRAY_SIZE(req->argv) - 1)
1545                 return REQ_NONE;
1547         if (!realloc_run_requests(&run_request, run_requests, 1))
1548                 return REQ_NONE;
1550         req = &run_request[run_requests];
1551         req->keymap = keymap;
1552         req->key = key;
1553         req->argv[0] = NULL;
1555         if (!format_argv(req->argv, argv, FORMAT_NONE))
1556                 return REQ_NONE;
1558         return REQ_NONE + ++run_requests;
1561 static struct run_request *
1562 get_run_request(enum request request)
1564         if (request <= REQ_NONE)
1565                 return NULL;
1566         return &run_request[request - REQ_NONE - 1];
1569 static void
1570 add_builtin_run_requests(void)
1572         const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1573         const char *commit[] = { "git", "commit", NULL };
1574         const char *gc[] = { "git", "gc", NULL };
1575         struct {
1576                 enum keymap keymap;
1577                 int key;
1578                 int argc;
1579                 const char **argv;
1580         } reqs[] = {
1581                 { KEYMAP_MAIN,    'C', ARRAY_SIZE(cherry_pick) - 1, cherry_pick },
1582                 { KEYMAP_STATUS,  'C', ARRAY_SIZE(commit) - 1, commit },
1583                 { KEYMAP_GENERIC, 'G', ARRAY_SIZE(gc) - 1, gc },
1584         };
1585         int i;
1587         for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1588                 enum request req;
1590                 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argc, reqs[i].argv);
1591                 if (req != REQ_NONE)
1592                         add_keybinding(reqs[i].keymap, req, reqs[i].key);
1593         }
1596 /*
1597  * User config file handling.
1598  */
1600 static int   config_lineno;
1601 static bool  config_errors;
1602 static const char *config_msg;
1604 static const struct enum_map color_map[] = {
1605 #define COLOR_MAP(name) ENUM_MAP(#name, COLOR_##name)
1606         COLOR_MAP(DEFAULT),
1607         COLOR_MAP(BLACK),
1608         COLOR_MAP(BLUE),
1609         COLOR_MAP(CYAN),
1610         COLOR_MAP(GREEN),
1611         COLOR_MAP(MAGENTA),
1612         COLOR_MAP(RED),
1613         COLOR_MAP(WHITE),
1614         COLOR_MAP(YELLOW),
1615 };
1617 static const struct enum_map attr_map[] = {
1618 #define ATTR_MAP(name) ENUM_MAP(#name, A_##name)
1619         ATTR_MAP(NORMAL),
1620         ATTR_MAP(BLINK),
1621         ATTR_MAP(BOLD),
1622         ATTR_MAP(DIM),
1623         ATTR_MAP(REVERSE),
1624         ATTR_MAP(STANDOUT),
1625         ATTR_MAP(UNDERLINE),
1626 };
1628 #define set_attribute(attr, name)       map_enum(attr, attr_map, name)
1630 static int parse_step(double *opt, const char *arg)
1632         *opt = atoi(arg);
1633         if (!strchr(arg, '%'))
1634                 return OK;
1636         /* "Shift down" so 100% and 1 does not conflict. */
1637         *opt = (*opt - 1) / 100;
1638         if (*opt >= 1.0) {
1639                 *opt = 0.99;
1640                 config_msg = "Step value larger than 100%";
1641                 return ERR;
1642         }
1643         if (*opt < 0.0) {
1644                 *opt = 1;
1645                 config_msg = "Invalid step value";
1646                 return ERR;
1647         }
1648         return OK;
1651 static int
1652 parse_int(int *opt, const char *arg, int min, int max)
1654         int value = atoi(arg);
1656         if (min <= value && value <= max) {
1657                 *opt = value;
1658                 return OK;
1659         }
1661         config_msg = "Integer value out of bound";
1662         return ERR;
1665 static bool
1666 set_color(int *color, const char *name)
1668         if (map_enum(color, color_map, name))
1669                 return TRUE;
1670         if (!prefixcmp(name, "color"))
1671                 return parse_int(color, name + 5, 0, 255) == OK;
1672         return FALSE;
1675 /* Wants: object fgcolor bgcolor [attribute] */
1676 static int
1677 option_color_command(int argc, const char *argv[])
1679         struct line_info *info;
1681         if (argc < 3) {
1682                 config_msg = "Wrong number of arguments given to color command";
1683                 return ERR;
1684         }
1686         info = get_line_info(argv[0]);
1687         if (!info) {
1688                 static const struct enum_map obsolete[] = {
1689                         ENUM_MAP("main-delim",  LINE_DELIMITER),
1690                         ENUM_MAP("main-date",   LINE_DATE),
1691                         ENUM_MAP("main-author", LINE_AUTHOR),
1692                 };
1693                 int index;
1695                 if (!map_enum(&index, obsolete, argv[0])) {
1696                         config_msg = "Unknown color name";
1697                         return ERR;
1698                 }
1699                 info = &line_info[index];
1700         }
1702         if (!set_color(&info->fg, argv[1]) ||
1703             !set_color(&info->bg, argv[2])) {
1704                 config_msg = "Unknown color";
1705                 return ERR;
1706         }
1708         info->attr = 0;
1709         while (argc-- > 3) {
1710                 int attr;
1712                 if (!set_attribute(&attr, argv[argc])) {
1713                         config_msg = "Unknown attribute";
1714                         return ERR;
1715                 }
1716                 info->attr |= attr;
1717         }
1719         return OK;
1722 static int parse_bool(bool *opt, const char *arg)
1724         *opt = (!strcmp(arg, "1") || !strcmp(arg, "true") || !strcmp(arg, "yes"))
1725                 ? TRUE : FALSE;
1726         return OK;
1729 static int parse_enum_do(unsigned int *opt, const char *arg,
1730                          const struct enum_map *map, size_t map_size)
1732         bool is_true;
1734         assert(map_size > 1);
1736         if (map_enum_do(map, map_size, (int *) opt, arg))
1737                 return OK;
1739         if (parse_bool(&is_true, arg) != OK)
1740                 return ERR;
1742         *opt = is_true ? map[1].value : map[0].value;
1743         return OK;
1746 #define parse_enum(opt, arg, map) \
1747         parse_enum_do(opt, arg, map, ARRAY_SIZE(map))
1749 static int
1750 parse_string(char *opt, const char *arg, size_t optsize)
1752         int arglen = strlen(arg);
1754         switch (arg[0]) {
1755         case '\"':
1756         case '\'':
1757                 if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1758                         config_msg = "Unmatched quotation";
1759                         return ERR;
1760                 }
1761                 arg += 1; arglen -= 2;
1762         default:
1763                 string_ncopy_do(opt, optsize, arg, arglen);
1764                 return OK;
1765         }
1768 /* Wants: name = value */
1769 static int
1770 option_set_command(int argc, const char *argv[])
1772         if (argc != 3) {
1773                 config_msg = "Wrong number of arguments given to set command";
1774                 return ERR;
1775         }
1777         if (strcmp(argv[1], "=")) {
1778                 config_msg = "No value assigned";
1779                 return ERR;
1780         }
1782         if (!strcmp(argv[0], "show-author"))
1783                 return parse_enum(&opt_author, argv[2], author_map);
1785         if (!strcmp(argv[0], "show-date"))
1786                 return parse_enum(&opt_date, argv[2], date_map);
1788         if (!strcmp(argv[0], "show-rev-graph"))
1789                 return parse_bool(&opt_rev_graph, argv[2]);
1791         if (!strcmp(argv[0], "show-refs"))
1792                 return parse_bool(&opt_show_refs, argv[2]);
1794         if (!strcmp(argv[0], "show-line-numbers"))
1795                 return parse_bool(&opt_line_number, argv[2]);
1797         if (!strcmp(argv[0], "line-graphics"))
1798                 return parse_bool(&opt_line_graphics, argv[2]);
1800         if (!strcmp(argv[0], "line-number-interval"))
1801                 return parse_int(&opt_num_interval, argv[2], 1, 1024);
1803         if (!strcmp(argv[0], "author-width"))
1804                 return parse_int(&opt_author_cols, argv[2], 0, 1024);
1806         if (!strcmp(argv[0], "horizontal-scroll"))
1807                 return parse_step(&opt_hscroll, argv[2]);
1809         if (!strcmp(argv[0], "split-view-height"))
1810                 return parse_step(&opt_scale_split_view, argv[2]);
1812         if (!strcmp(argv[0], "tab-size"))
1813                 return parse_int(&opt_tab_size, argv[2], 1, 1024);
1815         if (!strcmp(argv[0], "commit-encoding"))
1816                 return parse_string(opt_encoding, argv[2], sizeof(opt_encoding));
1818         config_msg = "Unknown variable name";
1819         return ERR;
1822 /* Wants: mode request key */
1823 static int
1824 option_bind_command(int argc, const char *argv[])
1826         enum request request;
1827         int keymap = -1;
1828         int key;
1830         if (argc < 3) {
1831                 config_msg = "Wrong number of arguments given to bind command";
1832                 return ERR;
1833         }
1835         if (set_keymap(&keymap, argv[0]) == ERR) {
1836                 config_msg = "Unknown key map";
1837                 return ERR;
1838         }
1840         key = get_key_value(argv[1]);
1841         if (key == ERR) {
1842                 config_msg = "Unknown key";
1843                 return ERR;
1844         }
1846         request = get_request(argv[2]);
1847         if (request == REQ_NONE) {
1848                 static const struct enum_map obsolete[] = {
1849                         ENUM_MAP("cherry-pick",         REQ_NONE),
1850                         ENUM_MAP("screen-resize",       REQ_NONE),
1851                         ENUM_MAP("tree-parent",         REQ_PARENT),
1852                 };
1853                 int alias;
1855                 if (map_enum(&alias, obsolete, argv[2])) {
1856                         if (alias != REQ_NONE)
1857                                 add_keybinding(keymap, alias, key);
1858                         config_msg = "Obsolete request name";
1859                         return ERR;
1860                 }
1861         }
1862         if (request == REQ_NONE && *argv[2]++ == '!')
1863                 request = add_run_request(keymap, key, argc - 2, argv + 2);
1864         if (request == REQ_NONE) {
1865                 config_msg = "Unknown request name";
1866                 return ERR;
1867         }
1869         add_keybinding(keymap, request, key);
1871         return OK;
1874 static int
1875 set_option(const char *opt, char *value)
1877         const char *argv[SIZEOF_ARG];
1878         int argc = 0;
1880         if (!argv_from_string(argv, &argc, value)) {
1881                 config_msg = "Too many option arguments";
1882                 return ERR;
1883         }
1885         if (!strcmp(opt, "color"))
1886                 return option_color_command(argc, argv);
1888         if (!strcmp(opt, "set"))
1889                 return option_set_command(argc, argv);
1891         if (!strcmp(opt, "bind"))
1892                 return option_bind_command(argc, argv);
1894         config_msg = "Unknown option command";
1895         return ERR;
1898 static int
1899 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
1901         int status = OK;
1903         config_lineno++;
1904         config_msg = "Internal error";
1906         /* Check for comment markers, since read_properties() will
1907          * only ensure opt and value are split at first " \t". */
1908         optlen = strcspn(opt, "#");
1909         if (optlen == 0)
1910                 return OK;
1912         if (opt[optlen] != 0) {
1913                 config_msg = "No option value";
1914                 status = ERR;
1916         }  else {
1917                 /* Look for comment endings in the value. */
1918                 size_t len = strcspn(value, "#");
1920                 if (len < valuelen) {
1921                         valuelen = len;
1922                         value[valuelen] = 0;
1923                 }
1925                 status = set_option(opt, value);
1926         }
1928         if (status == ERR) {
1929                 warn("Error on line %d, near '%.*s': %s",
1930                      config_lineno, (int) optlen, opt, config_msg);
1931                 config_errors = TRUE;
1932         }
1934         /* Always keep going if errors are encountered. */
1935         return OK;
1938 static void
1939 load_option_file(const char *path)
1941         struct io io = {};
1943         /* It's OK that the file doesn't exist. */
1944         if (!io_open(&io, "%s", path))
1945                 return;
1947         config_lineno = 0;
1948         config_errors = FALSE;
1950         if (io_load(&io, " \t", read_option) == ERR ||
1951             config_errors == TRUE)
1952                 warn("Errors while loading %s.", path);
1955 static int
1956 load_options(void)
1958         const char *home = getenv("HOME");
1959         const char *tigrc_user = getenv("TIGRC_USER");
1960         const char *tigrc_system = getenv("TIGRC_SYSTEM");
1961         char buf[SIZEOF_STR];
1963         add_builtin_run_requests();
1965         if (!tigrc_system)
1966                 tigrc_system = SYSCONFDIR "/tigrc";
1967         load_option_file(tigrc_system);
1969         if (!tigrc_user) {
1970                 if (!home || !string_format(buf, "%s/.tigrc", home))
1971                         return ERR;
1972                 tigrc_user = buf;
1973         }
1974         load_option_file(tigrc_user);
1976         return OK;
1980 /*
1981  * The viewer
1982  */
1984 struct view;
1985 struct view_ops;
1987 /* The display array of active views and the index of the current view. */
1988 static struct view *display[2];
1989 static unsigned int current_view;
1991 #define foreach_displayed_view(view, i) \
1992         for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1994 #define displayed_views()       (display[1] != NULL ? 2 : 1)
1996 /* Current head and commit ID */
1997 static char ref_blob[SIZEOF_REF]        = "";
1998 static char ref_commit[SIZEOF_REF]      = "HEAD";
1999 static char ref_head[SIZEOF_REF]        = "HEAD";
2001 struct view {
2002         const char *name;       /* View name */
2003         const char *cmd_env;    /* Command line set via environment */
2004         const char *id;         /* Points to either of ref_{head,commit,blob} */
2006         struct view_ops *ops;   /* View operations */
2008         enum keymap keymap;     /* What keymap does this view have */
2009         bool git_dir;           /* Whether the view requires a git directory. */
2011         char ref[SIZEOF_REF];   /* Hovered commit reference */
2012         char vid[SIZEOF_REF];   /* View ID. Set to id member when updating. */
2014         int height, width;      /* The width and height of the main window */
2015         WINDOW *win;            /* The main window */
2016         WINDOW *title;          /* The title window living below the main window */
2018         /* Navigation */
2019         unsigned long offset;   /* Offset of the window top */
2020         unsigned long yoffset;  /* Offset from the window side. */
2021         unsigned long lineno;   /* Current line number */
2022         unsigned long p_offset; /* Previous offset of the window top */
2023         unsigned long p_yoffset;/* Previous offset from the window side */
2024         unsigned long p_lineno; /* Previous current line number */
2025         bool p_restore;         /* Should the previous position be restored. */
2027         /* Searching */
2028         char grep[SIZEOF_STR];  /* Search string */
2029         regex_t *regex;         /* Pre-compiled regexp */
2031         /* If non-NULL, points to the view that opened this view. If this view
2032          * is closed tig will switch back to the parent view. */
2033         struct view *parent;
2035         /* Buffering */
2036         size_t lines;           /* Total number of lines */
2037         struct line *line;      /* Line index */
2038         unsigned int digits;    /* Number of digits in the lines member. */
2040         /* Drawing */
2041         struct line *curline;   /* Line currently being drawn. */
2042         enum line_type curtype; /* Attribute currently used for drawing. */
2043         unsigned long col;      /* Column when drawing. */
2044         bool has_scrolled;      /* View was scrolled. */
2046         /* Loading */
2047         struct io io;
2048         struct io *pipe;
2049         time_t start_time;
2050         time_t update_secs;
2051 };
2053 struct view_ops {
2054         /* What type of content being displayed. Used in the title bar. */
2055         const char *type;
2056         /* Default command arguments. */
2057         const char **argv;
2058         /* Open and reads in all view content. */
2059         bool (*open)(struct view *view);
2060         /* Read one line; updates view->line. */
2061         bool (*read)(struct view *view, char *data);
2062         /* Draw one line; @lineno must be < view->height. */
2063         bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
2064         /* Depending on view handle a special requests. */
2065         enum request (*request)(struct view *view, enum request request, struct line *line);
2066         /* Search for regexp in a line. */
2067         bool (*grep)(struct view *view, struct line *line);
2068         /* Select line */
2069         void (*select)(struct view *view, struct line *line);
2070         /* Prepare view for loading */
2071         bool (*prepare)(struct view *view);
2072 };
2074 static struct view_ops blame_ops;
2075 static struct view_ops blob_ops;
2076 static struct view_ops diff_ops;
2077 static struct view_ops help_ops;
2078 static struct view_ops log_ops;
2079 static struct view_ops main_ops;
2080 static struct view_ops pager_ops;
2081 static struct view_ops stage_ops;
2082 static struct view_ops status_ops;
2083 static struct view_ops tree_ops;
2084 static struct view_ops branch_ops;
2086 #define VIEW_STR(name, env, ref, ops, map, git) \
2087         { name, #env, ref, ops, map, git }
2089 #define VIEW_(id, name, ops, git, ref) \
2090         VIEW_STR(name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
2093 static struct view views[] = {
2094         VIEW_(MAIN,   "main",   &main_ops,   TRUE,  ref_head),
2095         VIEW_(DIFF,   "diff",   &diff_ops,   TRUE,  ref_commit),
2096         VIEW_(LOG,    "log",    &log_ops,    TRUE,  ref_head),
2097         VIEW_(TREE,   "tree",   &tree_ops,   TRUE,  ref_commit),
2098         VIEW_(BLOB,   "blob",   &blob_ops,   TRUE,  ref_blob),
2099         VIEW_(BLAME,  "blame",  &blame_ops,  TRUE,  ref_commit),
2100         VIEW_(BRANCH, "branch", &branch_ops, TRUE,  ref_head),
2101         VIEW_(HELP,   "help",   &help_ops,   FALSE, ""),
2102         VIEW_(PAGER,  "pager",  &pager_ops,  FALSE, "stdin"),
2103         VIEW_(STATUS, "status", &status_ops, TRUE,  ""),
2104         VIEW_(STAGE,  "stage",  &stage_ops,  TRUE,  ""),
2105 };
2107 #define VIEW(req)       (&views[(req) - REQ_OFFSET - 1])
2108 #define VIEW_REQ(view)  ((view) - views + REQ_OFFSET + 1)
2110 #define foreach_view(view, i) \
2111         for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
2113 #define view_is_displayed(view) \
2114         (view == display[0] || view == display[1])
2117 static inline void
2118 set_view_attr(struct view *view, enum line_type type)
2120         if (!view->curline->selected && view->curtype != type) {
2121                 (void) wattrset(view->win, get_line_attr(type));
2122                 wchgat(view->win, -1, 0, type, NULL);
2123                 view->curtype = type;
2124         }
2127 static int
2128 draw_chars(struct view *view, enum line_type type, const char *string,
2129            int max_len, bool use_tilde)
2131         static char out_buffer[BUFSIZ * 2];
2132         int len = 0;
2133         int col = 0;
2134         int trimmed = FALSE;
2135         size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2137         if (max_len <= 0)
2138                 return 0;
2140         len = utf8_length(&string, skip, &col, max_len, &trimmed, use_tilde, opt_tab_size);
2142         set_view_attr(view, type);
2143         if (len > 0) {
2144                 if (opt_iconv_out != ICONV_NONE) {
2145                         ICONV_CONST char *inbuf = (ICONV_CONST char *) string;
2146                         size_t inlen = len + 1;
2148                         char *outbuf = out_buffer;
2149                         size_t outlen = sizeof(out_buffer);
2151                         size_t ret;
2153                         ret = iconv(opt_iconv_out, &inbuf, &inlen, &outbuf, &outlen);
2154                         if (ret != (size_t) -1) {
2155                                 string = out_buffer;
2156                                 len = sizeof(out_buffer) - outlen;
2157                         }
2158                 }
2160                 waddnstr(view->win, string, len);
2161         }
2162         if (trimmed && use_tilde) {
2163                 set_view_attr(view, LINE_DELIMITER);
2164                 waddch(view->win, '~');
2165                 col++;
2166         }
2168         return col;
2171 static int
2172 draw_space(struct view *view, enum line_type type, int max, int spaces)
2174         static char space[] = "                    ";
2175         int col = 0;
2177         spaces = MIN(max, spaces);
2179         while (spaces > 0) {
2180                 int len = MIN(spaces, sizeof(space) - 1);
2182                 col += draw_chars(view, type, space, len, FALSE);
2183                 spaces -= len;
2184         }
2186         return col;
2189 static bool
2190 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
2192         view->col += draw_chars(view, type, string, view->width + view->yoffset - view->col, trim);
2193         return view->width + view->yoffset <= view->col;
2196 static bool
2197 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
2199         size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2200         int max = view->width + view->yoffset - view->col;
2201         int i;
2203         if (max < size)
2204                 size = max;
2206         set_view_attr(view, type);
2207         /* Using waddch() instead of waddnstr() ensures that
2208          * they'll be rendered correctly for the cursor line. */
2209         for (i = skip; i < size; i++)
2210                 waddch(view->win, graphic[i]);
2212         view->col += size;
2213         if (size < max && skip <= size)
2214                 waddch(view->win, ' ');
2215         view->col++;
2217         return view->width + view->yoffset <= view->col;
2220 static bool
2221 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
2223         int max = MIN(view->width + view->yoffset - view->col, len);
2224         int col;
2226         if (text)
2227                 col = draw_chars(view, type, text, max - 1, trim);
2228         else
2229                 col = draw_space(view, type, max - 1, max - 1);
2231         view->col += col;
2232         view->col += draw_space(view, LINE_DEFAULT, max - col, max - col);
2233         return view->width + view->yoffset <= view->col;
2236 static bool
2237 draw_date(struct view *view, struct time *time)
2239         const char *date = time && time->sec ? mkdate(time) : "";
2240         int cols = opt_date == DATE_SHORT ? DATE_SHORT_COLS : DATE_COLS;
2242         return draw_field(view, LINE_DATE, date, cols, FALSE);
2245 static bool
2246 draw_author(struct view *view, const char *author)
2248         bool trim = opt_author_cols == 0 || opt_author_cols > 5;
2249         bool abbreviate = opt_author == AUTHOR_ABBREVIATED || !trim;
2251         if (abbreviate && author)
2252                 author = get_author_initials(author);
2254         return draw_field(view, LINE_AUTHOR, author, opt_author_cols, trim);
2257 static bool
2258 draw_mode(struct view *view, mode_t mode)
2260         const char *str;
2262         if (S_ISDIR(mode))
2263                 str = "drwxr-xr-x";
2264         else if (S_ISLNK(mode))
2265                 str = "lrwxrwxrwx";
2266         else if (S_ISGITLINK(mode))
2267                 str = "m---------";
2268         else if (S_ISREG(mode) && mode & S_IXUSR)
2269                 str = "-rwxr-xr-x";
2270         else if (S_ISREG(mode))
2271                 str = "-rw-r--r--";
2272         else
2273                 str = "----------";
2275         return draw_field(view, LINE_MODE, str, STRING_SIZE("-rw-r--r-- "), FALSE);
2278 static bool
2279 draw_lineno(struct view *view, unsigned int lineno)
2281         char number[10];
2282         int digits3 = view->digits < 3 ? 3 : view->digits;
2283         int max = MIN(view->width + view->yoffset - view->col, digits3);
2284         char *text = NULL;
2285         chtype separator = opt_line_graphics ? ACS_VLINE : '|';
2287         lineno += view->offset + 1;
2288         if (lineno == 1 || (lineno % opt_num_interval) == 0) {
2289                 static char fmt[] = "%1ld";
2291                 fmt[1] = '0' + (view->digits <= 9 ? digits3 : 1);
2292                 if (string_format(number, fmt, lineno))
2293                         text = number;
2294         }
2295         if (text)
2296                 view->col += draw_chars(view, LINE_LINE_NUMBER, text, max, TRUE);
2297         else
2298                 view->col += draw_space(view, LINE_LINE_NUMBER, max, digits3);
2299         return draw_graphic(view, LINE_DEFAULT, &separator, 1);
2302 static bool
2303 draw_view_line(struct view *view, unsigned int lineno)
2305         struct line *line;
2306         bool selected = (view->offset + lineno == view->lineno);
2308         assert(view_is_displayed(view));
2310         if (view->offset + lineno >= view->lines)
2311                 return FALSE;
2313         line = &view->line[view->offset + lineno];
2315         wmove(view->win, lineno, 0);
2316         if (line->cleareol)
2317                 wclrtoeol(view->win);
2318         view->col = 0;
2319         view->curline = line;
2320         view->curtype = LINE_NONE;
2321         line->selected = FALSE;
2322         line->dirty = line->cleareol = 0;
2324         if (selected) {
2325                 set_view_attr(view, LINE_CURSOR);
2326                 line->selected = TRUE;
2327                 view->ops->select(view, line);
2328         }
2330         return view->ops->draw(view, line, lineno);
2333 static void
2334 redraw_view_dirty(struct view *view)
2336         bool dirty = FALSE;
2337         int lineno;
2339         for (lineno = 0; lineno < view->height; lineno++) {
2340                 if (view->offset + lineno >= view->lines)
2341                         break;
2342                 if (!view->line[view->offset + lineno].dirty)
2343                         continue;
2344                 dirty = TRUE;
2345                 if (!draw_view_line(view, lineno))
2346                         break;
2347         }
2349         if (!dirty)
2350                 return;
2351         wnoutrefresh(view->win);
2354 static void
2355 redraw_view_from(struct view *view, int lineno)
2357         assert(0 <= lineno && lineno < view->height);
2359         for (; lineno < view->height; lineno++) {
2360                 if (!draw_view_line(view, lineno))
2361                         break;
2362         }
2364         wnoutrefresh(view->win);
2367 static void
2368 redraw_view(struct view *view)
2370         werase(view->win);
2371         redraw_view_from(view, 0);
2375 static void
2376 update_view_title(struct view *view)
2378         char buf[SIZEOF_STR];
2379         char state[SIZEOF_STR];
2380         size_t bufpos = 0, statelen = 0;
2382         assert(view_is_displayed(view));
2384         if (view != VIEW(REQ_VIEW_STATUS) && view->lines) {
2385                 unsigned int view_lines = view->offset + view->height;
2386                 unsigned int lines = view->lines
2387                                    ? MIN(view_lines, view->lines) * 100 / view->lines
2388                                    : 0;
2390                 string_format_from(state, &statelen, " - %s %d of %d (%d%%)",
2391                                    view->ops->type,
2392                                    view->lineno + 1,
2393                                    view->lines,
2394                                    lines);
2396         }
2398         if (view->pipe) {
2399                 time_t secs = time(NULL) - view->start_time;
2401                 /* Three git seconds are a long time ... */
2402                 if (secs > 2)
2403                         string_format_from(state, &statelen, " loading %lds", secs);
2404         }
2406         string_format_from(buf, &bufpos, "[%s]", view->name);
2407         if (*view->ref && bufpos < view->width) {
2408                 size_t refsize = strlen(view->ref);
2409                 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2411                 if (minsize < view->width)
2412                         refsize = view->width - minsize + 7;
2413                 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2414         }
2416         if (statelen && bufpos < view->width) {
2417                 string_format_from(buf, &bufpos, "%s", state);
2418         }
2420         if (view == display[current_view])
2421                 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2422         else
2423                 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2425         mvwaddnstr(view->title, 0, 0, buf, bufpos);
2426         wclrtoeol(view->title);
2427         wnoutrefresh(view->title);
2430 static int
2431 apply_step(double step, int value)
2433         if (step >= 1)
2434                 return (int) step;
2435         value *= step + 0.01;
2436         return value ? value : 1;
2439 static void
2440 resize_display(void)
2442         int offset, i;
2443         struct view *base = display[0];
2444         struct view *view = display[1] ? display[1] : display[0];
2446         /* Setup window dimensions */
2448         getmaxyx(stdscr, base->height, base->width);
2450         /* Make room for the status window. */
2451         base->height -= 1;
2453         if (view != base) {
2454                 /* Horizontal split. */
2455                 view->width   = base->width;
2456                 view->height  = apply_step(opt_scale_split_view, base->height);
2457                 view->height  = MAX(view->height, MIN_VIEW_HEIGHT);
2458                 view->height  = MIN(view->height, base->height - MIN_VIEW_HEIGHT);
2459                 base->height -= view->height;
2461                 /* Make room for the title bar. */
2462                 view->height -= 1;
2463         }
2465         /* Make room for the title bar. */
2466         base->height -= 1;
2468         offset = 0;
2470         foreach_displayed_view (view, i) {
2471                 if (!view->win) {
2472                         view->win = newwin(view->height, 0, offset, 0);
2473                         if (!view->win)
2474                                 die("Failed to create %s view", view->name);
2476                         scrollok(view->win, FALSE);
2478                         view->title = newwin(1, 0, offset + view->height, 0);
2479                         if (!view->title)
2480                                 die("Failed to create title window");
2482                 } else {
2483                         wresize(view->win, view->height, view->width);
2484                         mvwin(view->win,   offset, 0);
2485                         mvwin(view->title, offset + view->height, 0);
2486                 }
2488                 offset += view->height + 1;
2489         }
2492 static void
2493 redraw_display(bool clear)
2495         struct view *view;
2496         int i;
2498         foreach_displayed_view (view, i) {
2499                 if (clear)
2500                         wclear(view->win);
2501                 redraw_view(view);
2502                 update_view_title(view);
2503         }
2506 static void
2507 toggle_enum_option_do(unsigned int *opt, const char *help,
2508                       const struct enum_map *map, size_t size)
2510         *opt = (*opt + 1) % size;
2511         redraw_display(FALSE);
2512         report("Displaying %s %s", enum_name(map[*opt]), help);
2515 #define toggle_enum_option(opt, help, map) \
2516         toggle_enum_option_do(opt, help, map, ARRAY_SIZE(map))
2518 #define toggle_date() toggle_enum_option(&opt_date, "dates", date_map)
2519 #define toggle_author() toggle_enum_option(&opt_author, "author names", author_map)
2521 static void
2522 toggle_view_option(bool *option, const char *help)
2524         *option = !*option;
2525         redraw_display(FALSE);
2526         report("%sabling %s", *option ? "En" : "Dis", help);
2529 static void
2530 open_option_menu(void)
2532         const struct menu_item menu[] = {
2533                 { '.', "line numbers", &opt_line_number },
2534                 { 'D', "date display", &opt_date },
2535                 { 'A', "author display", &opt_author },
2536                 { 'g', "revision graph display", &opt_rev_graph },
2537                 { 'F', "reference display", &opt_show_refs },
2538                 { 0 }
2539         };
2540         int selected = 0;
2542         if (prompt_menu("Toggle option", menu, &selected)) {
2543                 if (menu[selected].data == &opt_date)
2544                         toggle_date();
2545                 else if (menu[selected].data == &opt_author)
2546                         toggle_author();
2547                 else
2548                         toggle_view_option(menu[selected].data, menu[selected].text);
2549         }
2552 static void
2553 maximize_view(struct view *view)
2555         memset(display, 0, sizeof(display));
2556         current_view = 0;
2557         display[current_view] = view;
2558         resize_display();
2559         redraw_display(FALSE);
2560         report("");
2564 /*
2565  * Navigation
2566  */
2568 static bool
2569 goto_view_line(struct view *view, unsigned long offset, unsigned long lineno)
2571         if (lineno >= view->lines)
2572                 lineno = view->lines > 0 ? view->lines - 1 : 0;
2574         if (offset > lineno || offset + view->height <= lineno) {
2575                 unsigned long half = view->height / 2;
2577                 if (lineno > half)
2578                         offset = lineno - half;
2579                 else
2580                         offset = 0;
2581         }
2583         if (offset != view->offset || lineno != view->lineno) {
2584                 view->offset = offset;
2585                 view->lineno = lineno;
2586                 return TRUE;
2587         }
2589         return FALSE;
2592 /* Scrolling backend */
2593 static void
2594 do_scroll_view(struct view *view, int lines)
2596         bool redraw_current_line = FALSE;
2598         /* The rendering expects the new offset. */
2599         view->offset += lines;
2601         assert(0 <= view->offset && view->offset < view->lines);
2602         assert(lines);
2604         /* Move current line into the view. */
2605         if (view->lineno < view->offset) {
2606                 view->lineno = view->offset;
2607                 redraw_current_line = TRUE;
2608         } else if (view->lineno >= view->offset + view->height) {
2609                 view->lineno = view->offset + view->height - 1;
2610                 redraw_current_line = TRUE;
2611         }
2613         assert(view->offset <= view->lineno && view->lineno < view->lines);
2615         /* Redraw the whole screen if scrolling is pointless. */
2616         if (view->height < ABS(lines)) {
2617                 redraw_view(view);
2619         } else {
2620                 int line = lines > 0 ? view->height - lines : 0;
2621                 int end = line + ABS(lines);
2623                 scrollok(view->win, TRUE);
2624                 wscrl(view->win, lines);
2625                 scrollok(view->win, FALSE);
2627                 while (line < end && draw_view_line(view, line))
2628                         line++;
2630                 if (redraw_current_line)
2631                         draw_view_line(view, view->lineno - view->offset);
2632                 wnoutrefresh(view->win);
2633         }
2635         view->has_scrolled = TRUE;
2636         report("");
2639 /* Scroll frontend */
2640 static void
2641 scroll_view(struct view *view, enum request request)
2643         int lines = 1;
2645         assert(view_is_displayed(view));
2647         switch (request) {
2648         case REQ_SCROLL_LEFT:
2649                 if (view->yoffset == 0) {
2650                         report("Cannot scroll beyond the first column");
2651                         return;
2652                 }
2653                 if (view->yoffset <= apply_step(opt_hscroll, view->width))
2654                         view->yoffset = 0;
2655                 else
2656                         view->yoffset -= apply_step(opt_hscroll, view->width);
2657                 redraw_view_from(view, 0);
2658                 report("");
2659                 return;
2660         case REQ_SCROLL_RIGHT:
2661                 view->yoffset += apply_step(opt_hscroll, view->width);
2662                 redraw_view(view);
2663                 report("");
2664                 return;
2665         case REQ_SCROLL_PAGE_DOWN:
2666                 lines = view->height;
2667         case REQ_SCROLL_LINE_DOWN:
2668                 if (view->offset + lines > view->lines)
2669                         lines = view->lines - view->offset;
2671                 if (lines == 0 || view->offset + view->height >= view->lines) {
2672                         report("Cannot scroll beyond the last line");
2673                         return;
2674                 }
2675                 break;
2677         case REQ_SCROLL_PAGE_UP:
2678                 lines = view->height;
2679         case REQ_SCROLL_LINE_UP:
2680                 if (lines > view->offset)
2681                         lines = view->offset;
2683                 if (lines == 0) {
2684                         report("Cannot scroll beyond the first line");
2685                         return;
2686                 }
2688                 lines = -lines;
2689                 break;
2691         default:
2692                 die("request %d not handled in switch", request);
2693         }
2695         do_scroll_view(view, lines);
2698 /* Cursor moving */
2699 static void
2700 move_view(struct view *view, enum request request)
2702         int scroll_steps = 0;
2703         int steps;
2705         switch (request) {
2706         case REQ_MOVE_FIRST_LINE:
2707                 steps = -view->lineno;
2708                 break;
2710         case REQ_MOVE_LAST_LINE:
2711                 steps = view->lines - view->lineno - 1;
2712                 break;
2714         case REQ_MOVE_PAGE_UP:
2715                 steps = view->height > view->lineno
2716                       ? -view->lineno : -view->height;
2717                 break;
2719         case REQ_MOVE_PAGE_DOWN:
2720                 steps = view->lineno + view->height >= view->lines
2721                       ? view->lines - view->lineno - 1 : view->height;
2722                 break;
2724         case REQ_MOVE_UP:
2725                 steps = -1;
2726                 break;
2728         case REQ_MOVE_DOWN:
2729                 steps = 1;
2730                 break;
2732         default:
2733                 die("request %d not handled in switch", request);
2734         }
2736         if (steps <= 0 && view->lineno == 0) {
2737                 report("Cannot move beyond the first line");
2738                 return;
2740         } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2741                 report("Cannot move beyond the last line");
2742                 return;
2743         }
2745         /* Move the current line */
2746         view->lineno += steps;
2747         assert(0 <= view->lineno && view->lineno < view->lines);
2749         /* Check whether the view needs to be scrolled */
2750         if (view->lineno < view->offset ||
2751             view->lineno >= view->offset + view->height) {
2752                 scroll_steps = steps;
2753                 if (steps < 0 && -steps > view->offset) {
2754                         scroll_steps = -view->offset;
2756                 } else if (steps > 0) {
2757                         if (view->lineno == view->lines - 1 &&
2758                             view->lines > view->height) {
2759                                 scroll_steps = view->lines - view->offset - 1;
2760                                 if (scroll_steps >= view->height)
2761                                         scroll_steps -= view->height - 1;
2762                         }
2763                 }
2764         }
2766         if (!view_is_displayed(view)) {
2767                 view->offset += scroll_steps;
2768                 assert(0 <= view->offset && view->offset < view->lines);
2769                 view->ops->select(view, &view->line[view->lineno]);
2770                 return;
2771         }
2773         /* Repaint the old "current" line if we be scrolling */
2774         if (ABS(steps) < view->height)
2775                 draw_view_line(view, view->lineno - steps - view->offset);
2777         if (scroll_steps) {
2778                 do_scroll_view(view, scroll_steps);
2779                 return;
2780         }
2782         /* Draw the current line */
2783         draw_view_line(view, view->lineno - view->offset);
2785         wnoutrefresh(view->win);
2786         report("");
2790 /*
2791  * Searching
2792  */
2794 static void search_view(struct view *view, enum request request);
2796 static bool
2797 grep_text(struct view *view, const char *text[])
2799         regmatch_t pmatch;
2800         size_t i;
2802         for (i = 0; text[i]; i++)
2803                 if (*text[i] &&
2804                     regexec(view->regex, text[i], 1, &pmatch, 0) != REG_NOMATCH)
2805                         return TRUE;
2806         return FALSE;
2809 static void
2810 select_view_line(struct view *view, unsigned long lineno)
2812         unsigned long old_lineno = view->lineno;
2813         unsigned long old_offset = view->offset;
2815         if (goto_view_line(view, view->offset, lineno)) {
2816                 if (view_is_displayed(view)) {
2817                         if (old_offset != view->offset) {
2818                                 redraw_view(view);
2819                         } else {
2820                                 draw_view_line(view, old_lineno - view->offset);
2821                                 draw_view_line(view, view->lineno - view->offset);
2822                                 wnoutrefresh(view->win);
2823                         }
2824                 } else {
2825                         view->ops->select(view, &view->line[view->lineno]);
2826                 }
2827         }
2830 static void
2831 find_next(struct view *view, enum request request)
2833         unsigned long lineno = view->lineno;
2834         int direction;
2836         if (!*view->grep) {
2837                 if (!*opt_search)
2838                         report("No previous search");
2839                 else
2840                         search_view(view, request);
2841                 return;
2842         }
2844         switch (request) {
2845         case REQ_SEARCH:
2846         case REQ_FIND_NEXT:
2847                 direction = 1;
2848                 break;
2850         case REQ_SEARCH_BACK:
2851         case REQ_FIND_PREV:
2852                 direction = -1;
2853                 break;
2855         default:
2856                 return;
2857         }
2859         if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
2860                 lineno += direction;
2862         /* Note, lineno is unsigned long so will wrap around in which case it
2863          * will become bigger than view->lines. */
2864         for (; lineno < view->lines; lineno += direction) {
2865                 if (view->ops->grep(view, &view->line[lineno])) {
2866                         select_view_line(view, lineno);
2867                         report("Line %ld matches '%s'", lineno + 1, view->grep);
2868                         return;
2869                 }
2870         }
2872         report("No match found for '%s'", view->grep);
2875 static void
2876 search_view(struct view *view, enum request request)
2878         int regex_err;
2880         if (view->regex) {
2881                 regfree(view->regex);
2882                 *view->grep = 0;
2883         } else {
2884                 view->regex = calloc(1, sizeof(*view->regex));
2885                 if (!view->regex)
2886                         return;
2887         }
2889         regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
2890         if (regex_err != 0) {
2891                 char buf[SIZEOF_STR] = "unknown error";
2893                 regerror(regex_err, view->regex, buf, sizeof(buf));
2894                 report("Search failed: %s", buf);
2895                 return;
2896         }
2898         string_copy(view->grep, opt_search);
2900         find_next(view, request);
2903 /*
2904  * Incremental updating
2905  */
2907 static void
2908 reset_view(struct view *view)
2910         int i;
2912         for (i = 0; i < view->lines; i++)
2913                 free(view->line[i].data);
2914         free(view->line);
2916         view->p_offset = view->offset;
2917         view->p_yoffset = view->yoffset;
2918         view->p_lineno = view->lineno;
2920         view->line = NULL;
2921         view->offset = 0;
2922         view->yoffset = 0;
2923         view->lines  = 0;
2924         view->lineno = 0;
2925         view->vid[0] = 0;
2926         view->update_secs = 0;
2929 static void
2930 free_argv(const char *argv[])
2932         int argc;
2934         for (argc = 0; argv[argc]; argc++)
2935                 free((void *) argv[argc]);
2938 static const char *
2939 format_arg(const char *name)
2941         static struct {
2942                 const char *name;
2943                 size_t namelen;
2944                 const char *value;
2945                 const char *value_if_empty;
2946         } vars[] = {
2947 #define FORMAT_VAR(name, value, value_if_empty) \
2948         { name, STRING_SIZE(name), value, value_if_empty }
2949                 FORMAT_VAR("%(directory)",      opt_path,       ""),
2950                 FORMAT_VAR("%(file)",           opt_file,       ""),
2951                 FORMAT_VAR("%(ref)",            opt_ref,        "HEAD"),
2952                 FORMAT_VAR("%(head)",           ref_head,       ""),
2953                 FORMAT_VAR("%(commit)",         ref_commit,     ""),
2954                 FORMAT_VAR("%(blob)",           ref_blob,       ""),
2955         };
2956         int i;
2958         for (i = 0; i < ARRAY_SIZE(vars); i++)
2959                 if (!strncmp(name, vars[i].name, vars[i].namelen))
2960                         return *vars[i].value ? vars[i].value : vars[i].value_if_empty;
2962         return NULL;
2964 static bool
2965 format_argv(const char *dst_argv[], const char *src_argv[], enum format_flags flags)
2967         char buf[SIZEOF_STR];
2968         int argc;
2969         bool noreplace = flags == FORMAT_NONE;
2971         free_argv(dst_argv);
2973         for (argc = 0; src_argv[argc]; argc++) {
2974                 const char *arg = src_argv[argc];
2975                 size_t bufpos = 0;
2977                 while (arg) {
2978                         char *next = strstr(arg, "%(");
2979                         int len = next - arg;
2980                         const char *value;
2982                         if (!next || noreplace) {
2983                                 if (flags == FORMAT_DASH && !strcmp(arg, "--"))
2984                                         noreplace = TRUE;
2985                                 len = strlen(arg);
2986                                 value = "";
2988                         } else {
2989                                 value = format_arg(next);
2991                                 if (!value) {
2992                                         report("Unknown replacement: `%s`", next);
2993                                         return FALSE;
2994                                 }
2995                         }
2997                         if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
2998                                 return FALSE;
3000                         arg = next && !noreplace ? strchr(next, ')') + 1 : NULL;
3001                 }
3003                 dst_argv[argc] = strdup(buf);
3004                 if (!dst_argv[argc])
3005                         break;
3006         }
3008         dst_argv[argc] = NULL;
3010         return src_argv[argc] == NULL;
3013 static bool
3014 restore_view_position(struct view *view)
3016         if (!view->p_restore || (view->pipe && view->lines <= view->p_lineno))
3017                 return FALSE;
3019         /* Changing the view position cancels the restoring. */
3020         /* FIXME: Changing back to the first line is not detected. */
3021         if (view->offset != 0 || view->lineno != 0) {
3022                 view->p_restore = FALSE;
3023                 return FALSE;
3024         }
3026         if (goto_view_line(view, view->p_offset, view->p_lineno) &&
3027             view_is_displayed(view))
3028                 werase(view->win);
3030         view->yoffset = view->p_yoffset;
3031         view->p_restore = FALSE;
3033         return TRUE;
3036 static void
3037 end_update(struct view *view, bool force)
3039         if (!view->pipe)
3040                 return;
3041         while (!view->ops->read(view, NULL))
3042                 if (!force)
3043                         return;
3044         set_nonblocking_input(FALSE);
3045         if (force)
3046                 kill_io(view->pipe);
3047         done_io(view->pipe);
3048         view->pipe = NULL;
3051 static void
3052 setup_update(struct view *view, const char *vid)
3054         set_nonblocking_input(TRUE);
3055         reset_view(view);
3056         string_copy_rev(view->vid, vid);
3057         view->pipe = &view->io;
3058         view->start_time = time(NULL);
3061 static bool
3062 prepare_update(struct view *view, const char *argv[], const char *dir,
3063                enum format_flags flags)
3065         if (view->pipe)
3066                 end_update(view, TRUE);
3067         return init_io_rd(&view->io, argv, dir, flags);
3070 static bool
3071 prepare_update_file(struct view *view, const char *name)
3073         if (view->pipe)
3074                 end_update(view, TRUE);
3075         return io_open(&view->io, "%s/%s", opt_cdup[0] ? opt_cdup : ".", name);
3078 static bool
3079 begin_update(struct view *view, bool refresh)
3081         if (view->pipe)
3082                 end_update(view, TRUE);
3084         if (!refresh) {
3085                 if (view->ops->prepare) {
3086                         if (!view->ops->prepare(view))
3087                                 return FALSE;
3088                 } else if (!init_io_rd(&view->io, view->ops->argv, NULL, FORMAT_ALL)) {
3089                         return FALSE;
3090                 }
3092                 /* Put the current ref_* value to the view title ref
3093                  * member. This is needed by the blob view. Most other
3094                  * views sets it automatically after loading because the
3095                  * first line is a commit line. */
3096                 string_copy_rev(view->ref, view->id);
3097         }
3099         if (!start_io(&view->io))
3100                 return FALSE;
3102         setup_update(view, view->id);
3104         return TRUE;
3107 static bool
3108 update_view(struct view *view)
3110         char out_buffer[BUFSIZ * 2];
3111         char *line;
3112         /* Clear the view and redraw everything since the tree sorting
3113          * might have rearranged things. */
3114         bool redraw = view->lines == 0;
3115         bool can_read = TRUE;
3117         if (!view->pipe)
3118                 return TRUE;
3120         if (!io_can_read(view->pipe)) {
3121                 if (view->lines == 0 && view_is_displayed(view)) {
3122                         time_t secs = time(NULL) - view->start_time;
3124                         if (secs > 1 && secs > view->update_secs) {
3125                                 if (view->update_secs == 0)
3126                                         redraw_view(view);
3127                                 update_view_title(view);
3128                                 view->update_secs = secs;
3129                         }
3130                 }
3131                 return TRUE;
3132         }
3134         for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
3135                 if (opt_iconv_in != ICONV_NONE) {
3136                         ICONV_CONST char *inbuf = line;
3137                         size_t inlen = strlen(line) + 1;
3139                         char *outbuf = out_buffer;
3140                         size_t outlen = sizeof(out_buffer);
3142                         size_t ret;
3144                         ret = iconv(opt_iconv_in, &inbuf, &inlen, &outbuf, &outlen);
3145                         if (ret != (size_t) -1)
3146                                 line = out_buffer;
3147                 }
3149                 if (!view->ops->read(view, line)) {
3150                         report("Allocation failure");
3151                         end_update(view, TRUE);
3152                         return FALSE;
3153                 }
3154         }
3156         {
3157                 unsigned long lines = view->lines;
3158                 int digits;
3160                 for (digits = 0; lines; digits++)
3161                         lines /= 10;
3163                 /* Keep the displayed view in sync with line number scaling. */
3164                 if (digits != view->digits) {
3165                         view->digits = digits;
3166                         if (opt_line_number || view == VIEW(REQ_VIEW_BLAME))
3167                                 redraw = TRUE;
3168                 }
3169         }
3171         if (io_error(view->pipe)) {
3172                 report("Failed to read: %s", io_strerror(view->pipe));
3173                 end_update(view, TRUE);
3175         } else if (io_eof(view->pipe)) {
3176                 report("");
3177                 end_update(view, FALSE);
3178         }
3180         if (restore_view_position(view))
3181                 redraw = TRUE;
3183         if (!view_is_displayed(view))
3184                 return TRUE;
3186         if (redraw)
3187                 redraw_view_from(view, 0);
3188         else
3189                 redraw_view_dirty(view);
3191         /* Update the title _after_ the redraw so that if the redraw picks up a
3192          * commit reference in view->ref it'll be available here. */
3193         update_view_title(view);
3194         return TRUE;
3197 DEFINE_ALLOCATOR(realloc_lines, struct line, 256)
3199 static struct line *
3200 add_line_data(struct view *view, void *data, enum line_type type)
3202         struct line *line;
3204         if (!realloc_lines(&view->line, view->lines, 1))
3205                 return NULL;
3207         line = &view->line[view->lines++];
3208         memset(line, 0, sizeof(*line));
3209         line->type = type;
3210         line->data = data;
3211         line->dirty = 1;
3213         return line;
3216 static struct line *
3217 add_line_text(struct view *view, const char *text, enum line_type type)
3219         char *data = text ? strdup(text) : NULL;
3221         return data ? add_line_data(view, data, type) : NULL;
3224 static struct line *
3225 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
3227         char buf[SIZEOF_STR];
3228         va_list args;
3230         va_start(args, fmt);
3231         if (vsnprintf(buf, sizeof(buf), fmt, args) >= sizeof(buf))
3232                 buf[0] = 0;
3233         va_end(args);
3235         return buf[0] ? add_line_text(view, buf, type) : NULL;
3238 /*
3239  * View opening
3240  */
3242 enum open_flags {
3243         OPEN_DEFAULT = 0,       /* Use default view switching. */
3244         OPEN_SPLIT = 1,         /* Split current view. */
3245         OPEN_RELOAD = 4,        /* Reload view even if it is the current. */
3246         OPEN_REFRESH = 16,      /* Refresh view using previous command. */
3247         OPEN_PREPARED = 32,     /* Open already prepared command. */
3248 };
3250 static void
3251 open_view(struct view *prev, enum request request, enum open_flags flags)
3253         bool split = !!(flags & OPEN_SPLIT);
3254         bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
3255         bool nomaximize = !!(flags & OPEN_REFRESH);
3256         struct view *view = VIEW(request);
3257         int nviews = displayed_views();
3258         struct view *base_view = display[0];
3260         if (view == prev && nviews == 1 && !reload) {
3261                 report("Already in %s view", view->name);
3262                 return;
3263         }
3265         if (view->git_dir && !opt_git_dir[0]) {
3266                 report("The %s view is disabled in pager view", view->name);
3267                 return;
3268         }
3270         if (split) {
3271                 display[1] = view;
3272                 current_view = 1;
3273         } else if (!nomaximize) {
3274                 /* Maximize the current view. */
3275                 memset(display, 0, sizeof(display));
3276                 current_view = 0;
3277                 display[current_view] = view;
3278         }
3280         /* No parent signals that this is the first loaded view. */
3281         if (prev && view != prev) {
3282                 view->parent = prev;
3283         }
3285         /* Resize the view when switching between split- and full-screen,
3286          * or when switching between two different full-screen views. */
3287         if (nviews != displayed_views() ||
3288             (nviews == 1 && base_view != display[0]))
3289                 resize_display();
3291         if (view->ops->open) {
3292                 if (view->pipe)
3293                         end_update(view, TRUE);
3294                 if (!view->ops->open(view)) {
3295                         report("Failed to load %s view", view->name);
3296                         return;
3297                 }
3298                 restore_view_position(view);
3300         } else if ((reload || strcmp(view->vid, view->id)) &&
3301                    !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
3302                 report("Failed to load %s view", view->name);
3303                 return;
3304         }
3306         if (split && prev->lineno - prev->offset >= prev->height) {
3307                 /* Take the title line into account. */
3308                 int lines = prev->lineno - prev->offset - prev->height + 1;
3310                 /* Scroll the view that was split if the current line is
3311                  * outside the new limited view. */
3312                 do_scroll_view(prev, lines);
3313         }
3315         if (prev && view != prev && split && view_is_displayed(prev)) {
3316                 /* "Blur" the previous view. */
3317                 update_view_title(prev);
3318         }
3320         if (view->pipe && view->lines == 0) {
3321                 /* Clear the old view and let the incremental updating refill
3322                  * the screen. */
3323                 werase(view->win);
3324                 view->p_restore = flags & (OPEN_RELOAD | OPEN_REFRESH);
3325                 report("");
3326         } else if (view_is_displayed(view)) {
3327                 redraw_view(view);
3328                 report("");
3329         }
3332 static void
3333 open_external_viewer(const char *argv[], const char *dir)
3335         def_prog_mode();           /* save current tty modes */
3336         endwin();                  /* restore original tty modes */
3337         run_io_fg(argv, dir);
3338         fprintf(stderr, "Press Enter to continue");
3339         getc(opt_tty);
3340         reset_prog_mode();
3341         redraw_display(TRUE);
3344 static void
3345 open_mergetool(const char *file)
3347         const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
3349         open_external_viewer(mergetool_argv, opt_cdup);
3352 static void
3353 open_editor(const char *file)
3355         const char *editor_argv[] = { "vi", file, NULL };
3356         const char *editor;
3358         editor = getenv("GIT_EDITOR");
3359         if (!editor && *opt_editor)
3360                 editor = opt_editor;
3361         if (!editor)
3362                 editor = getenv("VISUAL");
3363         if (!editor)
3364                 editor = getenv("EDITOR");
3365         if (!editor)
3366                 editor = "vi";
3368         editor_argv[0] = editor;
3369         open_external_viewer(editor_argv, opt_cdup);
3372 static void
3373 open_run_request(enum request request)
3375         struct run_request *req = get_run_request(request);
3376         const char *argv[ARRAY_SIZE(req->argv)] = { NULL };
3378         if (!req) {
3379                 report("Unknown run request");
3380                 return;
3381         }
3383         if (format_argv(argv, req->argv, FORMAT_ALL))
3384                 open_external_viewer(argv, NULL);
3385         free_argv(argv);
3388 /*
3389  * User request switch noodle
3390  */
3392 static int
3393 view_driver(struct view *view, enum request request)
3395         int i;
3397         if (request == REQ_NONE)
3398                 return TRUE;
3400         if (request > REQ_NONE) {
3401                 open_run_request(request);
3402                 /* FIXME: When all views can refresh always do this. */
3403                 if (view == VIEW(REQ_VIEW_STATUS) ||
3404                     view == VIEW(REQ_VIEW_MAIN) ||
3405                     view == VIEW(REQ_VIEW_LOG) ||
3406                     view == VIEW(REQ_VIEW_BRANCH) ||
3407                     view == VIEW(REQ_VIEW_STAGE))
3408                         request = REQ_REFRESH;
3409                 else
3410                         return TRUE;
3411         }
3413         if (view && view->lines) {
3414                 request = view->ops->request(view, request, &view->line[view->lineno]);
3415                 if (request == REQ_NONE)
3416                         return TRUE;
3417         }
3419         switch (request) {
3420         case REQ_MOVE_UP:
3421         case REQ_MOVE_DOWN:
3422         case REQ_MOVE_PAGE_UP:
3423         case REQ_MOVE_PAGE_DOWN:
3424         case REQ_MOVE_FIRST_LINE:
3425         case REQ_MOVE_LAST_LINE:
3426                 move_view(view, request);
3427                 break;
3429         case REQ_SCROLL_LEFT:
3430         case REQ_SCROLL_RIGHT:
3431         case REQ_SCROLL_LINE_DOWN:
3432         case REQ_SCROLL_LINE_UP:
3433         case REQ_SCROLL_PAGE_DOWN:
3434         case REQ_SCROLL_PAGE_UP:
3435                 scroll_view(view, request);
3436                 break;
3438         case REQ_VIEW_BLAME:
3439                 if (!opt_file[0]) {
3440                         report("No file chosen, press %s to open tree view",
3441                                get_key(view->keymap, REQ_VIEW_TREE));
3442                         break;
3443                 }
3444                 open_view(view, request, OPEN_DEFAULT);
3445                 break;
3447         case REQ_VIEW_BLOB:
3448                 if (!ref_blob[0]) {
3449                         report("No file chosen, press %s to open tree view",
3450                                get_key(view->keymap, REQ_VIEW_TREE));
3451                         break;
3452                 }
3453                 open_view(view, request, OPEN_DEFAULT);
3454                 break;
3456         case REQ_VIEW_PAGER:
3457                 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3458                         report("No pager content, press %s to run command from prompt",
3459                                get_key(view->keymap, REQ_PROMPT));
3460                         break;
3461                 }
3462                 open_view(view, request, OPEN_DEFAULT);
3463                 break;
3465         case REQ_VIEW_STAGE:
3466                 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3467                         report("No stage content, press %s to open the status view and choose file",
3468                                get_key(view->keymap, REQ_VIEW_STATUS));
3469                         break;
3470                 }
3471                 open_view(view, request, OPEN_DEFAULT);
3472                 break;
3474         case REQ_VIEW_STATUS:
3475                 if (opt_is_inside_work_tree == FALSE) {
3476                         report("The status view requires a working tree");
3477                         break;
3478                 }
3479                 open_view(view, request, OPEN_DEFAULT);
3480                 break;
3482         case REQ_VIEW_MAIN:
3483         case REQ_VIEW_DIFF:
3484         case REQ_VIEW_LOG:
3485         case REQ_VIEW_TREE:
3486         case REQ_VIEW_HELP:
3487         case REQ_VIEW_BRANCH:
3488                 open_view(view, request, OPEN_DEFAULT);
3489                 break;
3491         case REQ_NEXT:
3492         case REQ_PREVIOUS:
3493                 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3495                 if ((view == VIEW(REQ_VIEW_DIFF) &&
3496                      view->parent == VIEW(REQ_VIEW_MAIN)) ||
3497                    (view == VIEW(REQ_VIEW_DIFF) &&
3498                      view->parent == VIEW(REQ_VIEW_BLAME)) ||
3499                    (view == VIEW(REQ_VIEW_STAGE) &&
3500                      view->parent == VIEW(REQ_VIEW_STATUS)) ||
3501                    (view == VIEW(REQ_VIEW_BLOB) &&
3502                      view->parent == VIEW(REQ_VIEW_TREE)) ||
3503                    (view == VIEW(REQ_VIEW_MAIN) &&
3504                      view->parent == VIEW(REQ_VIEW_BRANCH))) {
3505                         int line;
3507                         view = view->parent;
3508                         line = view->lineno;
3509                         move_view(view, request);
3510                         if (view_is_displayed(view))
3511                                 update_view_title(view);
3512                         if (line != view->lineno)
3513                                 view->ops->request(view, REQ_ENTER,
3514                                                    &view->line[view->lineno]);
3516                 } else {
3517                         move_view(view, request);
3518                 }
3519                 break;
3521         case REQ_VIEW_NEXT:
3522         {
3523                 int nviews = displayed_views();
3524                 int next_view = (current_view + 1) % nviews;
3526                 if (next_view == current_view) {
3527                         report("Only one view is displayed");
3528                         break;
3529                 }
3531                 current_view = next_view;
3532                 /* Blur out the title of the previous view. */
3533                 update_view_title(view);
3534                 report("");
3535                 break;
3536         }
3537         case REQ_REFRESH:
3538                 report("Refreshing is not yet supported for the %s view", view->name);
3539                 break;
3541         case REQ_MAXIMIZE:
3542                 if (displayed_views() == 2)
3543                         maximize_view(view);
3544                 break;
3546         case REQ_OPTIONS:
3547                 open_option_menu();
3548                 break;
3550         case REQ_TOGGLE_LINENO:
3551                 toggle_view_option(&opt_line_number, "line numbers");
3552                 break;
3554         case REQ_TOGGLE_DATE:
3555                 toggle_date();
3556                 break;
3558         case REQ_TOGGLE_AUTHOR:
3559                 toggle_author();
3560                 break;
3562         case REQ_TOGGLE_REV_GRAPH:
3563                 toggle_view_option(&opt_rev_graph, "revision graph display");
3564                 break;
3566         case REQ_TOGGLE_REFS:
3567                 toggle_view_option(&opt_show_refs, "reference display");
3568                 break;
3570         case REQ_TOGGLE_SORT_FIELD:
3571         case REQ_TOGGLE_SORT_ORDER:
3572                 report("Sorting is not yet supported for the %s view", view->name);
3573                 break;
3575         case REQ_SEARCH:
3576         case REQ_SEARCH_BACK:
3577                 search_view(view, request);
3578                 break;
3580         case REQ_FIND_NEXT:
3581         case REQ_FIND_PREV:
3582                 find_next(view, request);
3583                 break;
3585         case REQ_STOP_LOADING:
3586                 for (i = 0; i < ARRAY_SIZE(views); i++) {
3587                         view = &views[i];
3588                         if (view->pipe)
3589                                 report("Stopped loading the %s view", view->name),
3590                         end_update(view, TRUE);
3591                 }
3592                 break;
3594         case REQ_SHOW_VERSION:
3595                 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3596                 return TRUE;
3598         case REQ_SCREEN_REDRAW:
3599                 redraw_display(TRUE);
3600                 break;
3602         case REQ_EDIT:
3603                 report("Nothing to edit");
3604                 break;
3606         case REQ_ENTER:
3607                 report("Nothing to enter");
3608                 break;
3610         case REQ_VIEW_CLOSE:
3611                 /* XXX: Mark closed views by letting view->parent point to the
3612                  * view itself. Parents to closed view should never be
3613                  * followed. */
3614                 if (view->parent &&
3615                     view->parent->parent != view->parent) {
3616                         maximize_view(view->parent);
3617                         view->parent = view;
3618                         break;
3619                 }
3620                 /* Fall-through */
3621         case REQ_QUIT:
3622                 return FALSE;
3624         default:
3625                 report("Unknown key, press %s for help",
3626                        get_key(view->keymap, REQ_VIEW_HELP));
3627                 return TRUE;
3628         }
3630         return TRUE;
3634 /*
3635  * View backend utilities
3636  */
3638 enum sort_field {
3639         ORDERBY_NAME,
3640         ORDERBY_DATE,
3641         ORDERBY_AUTHOR,
3642 };
3644 struct sort_state {
3645         const enum sort_field *fields;
3646         size_t size, current;
3647         bool reverse;
3648 };
3650 #define SORT_STATE(fields) { fields, ARRAY_SIZE(fields), 0 }
3651 #define get_sort_field(state) ((state).fields[(state).current])
3652 #define sort_order(state, result) ((state).reverse ? -(result) : (result))
3654 static void
3655 sort_view(struct view *view, enum request request, struct sort_state *state,
3656           int (*compare)(const void *, const void *))
3658         switch (request) {
3659         case REQ_TOGGLE_SORT_FIELD:
3660                 state->current = (state->current + 1) % state->size;
3661                 break;
3663         case REQ_TOGGLE_SORT_ORDER:
3664                 state->reverse = !state->reverse;
3665                 break;
3666         default:
3667                 die("Not a sort request");
3668         }
3670         qsort(view->line, view->lines, sizeof(*view->line), compare);
3671         redraw_view(view);
3674 DEFINE_ALLOCATOR(realloc_authors, const char *, 256)
3676 /* Small author cache to reduce memory consumption. It uses binary
3677  * search to lookup or find place to position new entries. No entries
3678  * are ever freed. */
3679 static const char *
3680 get_author(const char *name)
3682         static const char **authors;
3683         static size_t authors_size;
3684         int from = 0, to = authors_size - 1;
3686         while (from <= to) {
3687                 size_t pos = (to + from) / 2;
3688                 int cmp = strcmp(name, authors[pos]);
3690                 if (!cmp)
3691                         return authors[pos];
3693                 if (cmp < 0)
3694                         to = pos - 1;
3695                 else
3696                         from = pos + 1;
3697         }
3699         if (!realloc_authors(&authors, authors_size, 1))
3700                 return NULL;
3701         name = strdup(name);
3702         if (!name)
3703                 return NULL;
3705         memmove(authors + from + 1, authors + from, (authors_size - from) * sizeof(*authors));
3706         authors[from] = name;
3707         authors_size++;
3709         return name;
3712 static void
3713 parse_timesec(struct time *time, const char *sec)
3715         time->sec = (time_t) atol(sec);
3718 static void
3719 parse_timezone(struct time *time, const char *zone)
3721         long tz;
3723         tz  = ('0' - zone[1]) * 60 * 60 * 10;
3724         tz += ('0' - zone[2]) * 60 * 60;
3725         tz += ('0' - zone[3]) * 60;
3726         tz += ('0' - zone[4]);
3728         if (zone[0] == '-')
3729                 tz = -tz;
3731         time->tz = tz;
3732         time->sec -= tz;
3735 /* Parse author lines where the name may be empty:
3736  *      author  <email@address.tld> 1138474660 +0100
3737  */
3738 static void
3739 parse_author_line(char *ident, const char **author, struct time *time)
3741         char *nameend = strchr(ident, '<');
3742         char *emailend = strchr(ident, '>');
3744         if (nameend && emailend)
3745                 *nameend = *emailend = 0;
3746         ident = chomp_string(ident);
3747         if (!*ident) {
3748                 if (nameend)
3749                         ident = chomp_string(nameend + 1);
3750                 if (!*ident)
3751                         ident = "Unknown";
3752         }
3754         *author = get_author(ident);
3756         /* Parse epoch and timezone */
3757         if (emailend && emailend[1] == ' ') {
3758                 char *secs = emailend + 2;
3759                 char *zone = strchr(secs, ' ');
3761                 parse_timesec(time, secs);
3763                 if (zone && strlen(zone) == STRING_SIZE(" +0700"))
3764                         parse_timezone(time, zone + 1);
3765         }
3768 static bool
3769 open_commit_parent_menu(char buf[SIZEOF_STR], int *parents)
3771         char rev[SIZEOF_REV];
3772         const char *revlist_argv[] = {
3773                 "git", "log", "--no-color", "-1", "--pretty=format:%s", rev, NULL
3774         };
3775         struct menu_item *items;
3776         char text[SIZEOF_STR];
3777         bool ok = TRUE;
3778         int i;
3780         items = calloc(*parents + 1, sizeof(*items));
3781         if (!items)
3782                 return FALSE;
3784         for (i = 0; i < *parents; i++) {
3785                 string_copy_rev(rev, &buf[SIZEOF_REV * i]);
3786                 if (!run_io_buf(revlist_argv, text, sizeof(text)) ||
3787                     !(items[i].text = strdup(text))) {
3788                         ok = FALSE;
3789                         break;
3790                 }
3791         }
3793         if (ok) {
3794                 *parents = 0;
3795                 ok = prompt_menu("Select parent", items, parents);
3796         }
3797         for (i = 0; items[i].text; i++)
3798                 free((char *) items[i].text);
3799         free(items);
3800         return ok;
3803 static bool
3804 select_commit_parent(const char *id, char rev[SIZEOF_REV], const char *path)
3806         char buf[SIZEOF_STR * 4];
3807         const char *revlist_argv[] = {
3808                 "git", "log", "--no-color", "-1",
3809                         "--pretty=format:%P", id, "--", path, NULL
3810         };
3811         int parents;
3813         if (!run_io_buf(revlist_argv, buf, sizeof(buf)) ||
3814             (parents = strlen(buf) / 40) < 0) {
3815                 report("Failed to get parent information");
3816                 return FALSE;
3818         } else if (parents == 0) {
3819                 if (path)
3820                         report("Path '%s' does not exist in the parent", path);
3821                 else
3822                         report("The selected commit has no parents");
3823                 return FALSE;
3824         }
3826         if (parents > 1 && !open_commit_parent_menu(buf, &parents))
3827                 return FALSE;
3829         string_copy_rev(rev, &buf[41 * parents]);
3830         return TRUE;
3833 /*
3834  * Pager backend
3835  */
3837 static bool
3838 pager_draw(struct view *view, struct line *line, unsigned int lineno)
3840         char text[SIZEOF_STR];
3842         if (opt_line_number && draw_lineno(view, lineno))
3843                 return TRUE;
3845         string_expand(text, sizeof(text), line->data, opt_tab_size);
3846         draw_text(view, line->type, text, TRUE);
3847         return TRUE;
3850 static bool
3851 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
3853         const char *describe_argv[] = { "git", "describe", commit_id, NULL };
3854         char ref[SIZEOF_STR];
3856         if (!run_io_buf(describe_argv, ref, sizeof(ref)) || !*ref)
3857                 return TRUE;
3859         /* This is the only fatal call, since it can "corrupt" the buffer. */
3860         if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
3861                 return FALSE;
3863         return TRUE;
3866 static void
3867 add_pager_refs(struct view *view, struct line *line)
3869         char buf[SIZEOF_STR];
3870         char *commit_id = (char *)line->data + STRING_SIZE("commit ");
3871         struct ref_list *list;
3872         size_t bufpos = 0, i;
3873         const char *sep = "Refs: ";
3874         bool is_tag = FALSE;
3876         assert(line->type == LINE_COMMIT);
3878         list = get_ref_list(commit_id);
3879         if (!list) {
3880                 if (view == VIEW(REQ_VIEW_DIFF))
3881                         goto try_add_describe_ref;
3882                 return;
3883         }
3885         for (i = 0; i < list->size; i++) {
3886                 struct ref *ref = list->refs[i];
3887                 const char *fmt = ref->tag    ? "%s[%s]" :
3888                                   ref->remote ? "%s<%s>" : "%s%s";
3890                 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
3891                         return;
3892                 sep = ", ";
3893                 if (ref->tag)
3894                         is_tag = TRUE;
3895         }
3897         if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
3898 try_add_describe_ref:
3899                 /* Add <tag>-g<commit_id> "fake" reference. */
3900                 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
3901                         return;
3902         }
3904         if (bufpos == 0)
3905                 return;
3907         add_line_text(view, buf, LINE_PP_REFS);
3910 static bool
3911 pager_read(struct view *view, char *data)
3913         struct line *line;
3915         if (!data)
3916                 return TRUE;
3918         line = add_line_text(view, data, get_line_type(data));
3919         if (!line)
3920                 return FALSE;
3922         if (line->type == LINE_COMMIT &&
3923             (view == VIEW(REQ_VIEW_DIFF) ||
3924              view == VIEW(REQ_VIEW_LOG)))
3925                 add_pager_refs(view, line);
3927         return TRUE;
3930 static enum request
3931 pager_request(struct view *view, enum request request, struct line *line)
3933         int split = 0;
3935         if (request != REQ_ENTER)
3936                 return request;
3938         if (line->type == LINE_COMMIT &&
3939            (view == VIEW(REQ_VIEW_LOG) ||
3940             view == VIEW(REQ_VIEW_PAGER))) {
3941                 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
3942                 split = 1;
3943         }
3945         /* Always scroll the view even if it was split. That way
3946          * you can use Enter to scroll through the log view and
3947          * split open each commit diff. */
3948         scroll_view(view, REQ_SCROLL_LINE_DOWN);
3950         /* FIXME: A minor workaround. Scrolling the view will call report("")
3951          * but if we are scrolling a non-current view this won't properly
3952          * update the view title. */
3953         if (split)
3954                 update_view_title(view);
3956         return REQ_NONE;
3959 static bool
3960 pager_grep(struct view *view, struct line *line)
3962         const char *text[] = { line->data, NULL };
3964         return grep_text(view, text);
3967 static void
3968 pager_select(struct view *view, struct line *line)
3970         if (line->type == LINE_COMMIT) {
3971                 char *text = (char *)line->data + STRING_SIZE("commit ");
3973                 if (view != VIEW(REQ_VIEW_PAGER))
3974                         string_copy_rev(view->ref, text);
3975                 string_copy_rev(ref_commit, text);
3976         }
3979 static struct view_ops pager_ops = {
3980         "line",
3981         NULL,
3982         NULL,
3983         pager_read,
3984         pager_draw,
3985         pager_request,
3986         pager_grep,
3987         pager_select,
3988 };
3990 static const char *log_argv[SIZEOF_ARG] = {
3991         "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
3992 };
3994 static enum request
3995 log_request(struct view *view, enum request request, struct line *line)
3997         switch (request) {
3998         case REQ_REFRESH:
3999                 load_refs();
4000                 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
4001                 return REQ_NONE;
4002         default:
4003                 return pager_request(view, request, line);
4004         }
4007 static struct view_ops log_ops = {
4008         "line",
4009         log_argv,
4010         NULL,
4011         pager_read,
4012         pager_draw,
4013         log_request,
4014         pager_grep,
4015         pager_select,
4016 };
4018 static const char *diff_argv[SIZEOF_ARG] = {
4019         "git", "show", "--pretty=fuller", "--no-color", "--root",
4020                 "--patch-with-stat", "--find-copies-harder", "-C", "%(commit)", NULL
4021 };
4023 static struct view_ops diff_ops = {
4024         "line",
4025         diff_argv,
4026         NULL,
4027         pager_read,
4028         pager_draw,
4029         pager_request,
4030         pager_grep,
4031         pager_select,
4032 };
4034 /*
4035  * Help backend
4036  */
4038 static bool help_keymap_hidden[ARRAY_SIZE(keymap_table)];
4040 static bool
4041 help_open_keymap_title(struct view *view, enum keymap keymap)
4043         struct line *line;
4045         line = add_line_format(view, LINE_HELP_KEYMAP, "[%c] %s bindings",
4046                                help_keymap_hidden[keymap] ? '+' : '-',
4047                                enum_name(keymap_table[keymap]));
4048         if (line)
4049                 line->other = keymap;
4051         return help_keymap_hidden[keymap];
4054 static void
4055 help_open_keymap(struct view *view, enum keymap keymap)
4057         const char *group = NULL;
4058         char buf[SIZEOF_STR];
4059         size_t bufpos;
4060         bool add_title = TRUE;
4061         int i;
4063         for (i = 0; i < ARRAY_SIZE(req_info); i++) {
4064                 const char *key = NULL;
4066                 if (req_info[i].request == REQ_NONE)
4067                         continue;
4069                 if (!req_info[i].request) {
4070                         group = req_info[i].help;
4071                         continue;
4072                 }
4074                 key = get_keys(keymap, req_info[i].request, TRUE);
4075                 if (!key || !*key)
4076                         continue;
4078                 if (add_title && help_open_keymap_title(view, keymap))
4079                         return;
4080                 add_title = FALSE;
4082                 if (group) {
4083                         add_line_text(view, group, LINE_HELP_GROUP);
4084                         group = NULL;
4085                 }
4087                 add_line_format(view, LINE_DEFAULT, "    %-25s %-20s %s", key,
4088                                 enum_name(req_info[i]), req_info[i].help);
4089         }
4091         group = "External commands:";
4093         for (i = 0; i < run_requests; i++) {
4094                 struct run_request *req = get_run_request(REQ_NONE + i + 1);
4095                 const char *key;
4096                 int argc;
4098                 if (!req || req->keymap != keymap)
4099                         continue;
4101                 key = get_key_name(req->key);
4102                 if (!*key)
4103                         key = "(no key defined)";
4105                 if (add_title && help_open_keymap_title(view, keymap))
4106                         return;
4107                 if (group) {
4108                         add_line_text(view, group, LINE_HELP_GROUP);
4109                         group = NULL;
4110                 }
4112                 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
4113                         if (!string_format_from(buf, &bufpos, "%s%s",
4114                                                 argc ? " " : "", req->argv[argc]))
4115                                 return;
4117                 add_line_format(view, LINE_DEFAULT, "    %-25s `%s`", key, buf);
4118         }
4121 static bool
4122 help_open(struct view *view)
4124         enum keymap keymap;
4126         reset_view(view);
4127         add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
4128         add_line_text(view, "", LINE_DEFAULT);
4130         for (keymap = 0; keymap < ARRAY_SIZE(keymap_table); keymap++)
4131                 help_open_keymap(view, keymap);
4133         return TRUE;
4136 static enum request
4137 help_request(struct view *view, enum request request, struct line *line)
4139         switch (request) {
4140         case REQ_ENTER:
4141                 if (line->type == LINE_HELP_KEYMAP) {
4142                         help_keymap_hidden[line->other] =
4143                                 !help_keymap_hidden[line->other];
4144                         view->p_restore = TRUE;
4145                         open_view(view, REQ_VIEW_HELP, OPEN_REFRESH);
4146                 }
4148                 return REQ_NONE;
4149         default:
4150                 return pager_request(view, request, line);
4151         }
4154 static struct view_ops help_ops = {
4155         "line",
4156         NULL,
4157         help_open,
4158         NULL,
4159         pager_draw,
4160         help_request,
4161         pager_grep,
4162         pager_select,
4163 };
4166 /*
4167  * Tree backend
4168  */
4170 struct tree_stack_entry {
4171         struct tree_stack_entry *prev;  /* Entry below this in the stack */
4172         unsigned long lineno;           /* Line number to restore */
4173         char *name;                     /* Position of name in opt_path */
4174 };
4176 /* The top of the path stack. */
4177 static struct tree_stack_entry *tree_stack = NULL;
4178 unsigned long tree_lineno = 0;
4180 static void
4181 pop_tree_stack_entry(void)
4183         struct tree_stack_entry *entry = tree_stack;
4185         tree_lineno = entry->lineno;
4186         entry->name[0] = 0;
4187         tree_stack = entry->prev;
4188         free(entry);
4191 static void
4192 push_tree_stack_entry(const char *name, unsigned long lineno)
4194         struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
4195         size_t pathlen = strlen(opt_path);
4197         if (!entry)
4198                 return;
4200         entry->prev = tree_stack;
4201         entry->name = opt_path + pathlen;
4202         tree_stack = entry;
4204         if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
4205                 pop_tree_stack_entry();
4206                 return;
4207         }
4209         /* Move the current line to the first tree entry. */
4210         tree_lineno = 1;
4211         entry->lineno = lineno;
4214 /* Parse output from git-ls-tree(1):
4215  *
4216  * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
4217  */
4219 #define SIZEOF_TREE_ATTR \
4220         STRING_SIZE("100644 blob f931e1d229c3e185caad4449bf5b66ed72462657\t")
4222 #define SIZEOF_TREE_MODE \
4223         STRING_SIZE("100644 ")
4225 #define TREE_ID_OFFSET \
4226         STRING_SIZE("100644 blob ")
4228 struct tree_entry {
4229         char id[SIZEOF_REV];
4230         mode_t mode;
4231         struct time time;               /* Date from the author ident. */
4232         const char *author;             /* Author of the commit. */
4233         char name[1];
4234 };
4236 static const char *
4237 tree_path(const struct line *line)
4239         return ((struct tree_entry *) line->data)->name;
4242 static int
4243 tree_compare_entry(const struct line *line1, const struct line *line2)
4245         if (line1->type != line2->type)
4246                 return line1->type == LINE_TREE_DIR ? -1 : 1;
4247         return strcmp(tree_path(line1), tree_path(line2));
4250 static const enum sort_field tree_sort_fields[] = {
4251         ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
4252 };
4253 static struct sort_state tree_sort_state = SORT_STATE(tree_sort_fields);
4255 static int
4256 tree_compare(const void *l1, const void *l2)
4258         const struct line *line1 = (const struct line *) l1;
4259         const struct line *line2 = (const struct line *) l2;
4260         const struct tree_entry *entry1 = ((const struct line *) l1)->data;
4261         const struct tree_entry *entry2 = ((const struct line *) l2)->data;
4263         if (line1->type == LINE_TREE_HEAD)
4264                 return -1;
4265         if (line2->type == LINE_TREE_HEAD)
4266                 return 1;
4268         switch (get_sort_field(tree_sort_state)) {
4269         case ORDERBY_DATE:
4270                 return sort_order(tree_sort_state, timecmp(&entry1->time, &entry2->time));
4272         case ORDERBY_AUTHOR:
4273                 return sort_order(tree_sort_state, strcmp(entry1->author, entry2->author));
4275         case ORDERBY_NAME:
4276         default:
4277                 return sort_order(tree_sort_state, tree_compare_entry(line1, line2));
4278         }
4282 static struct line *
4283 tree_entry(struct view *view, enum line_type type, const char *path,
4284            const char *mode, const char *id)
4286         struct tree_entry *entry = calloc(1, sizeof(*entry) + strlen(path));
4287         struct line *line = entry ? add_line_data(view, entry, type) : NULL;
4289         if (!entry || !line) {
4290                 free(entry);
4291                 return NULL;
4292         }
4294         strncpy(entry->name, path, strlen(path));
4295         if (mode)
4296                 entry->mode = strtoul(mode, NULL, 8);
4297         if (id)
4298                 string_copy_rev(entry->id, id);
4300         return line;
4303 static bool
4304 tree_read_date(struct view *view, char *text, bool *read_date)
4306         static const char *author_name;
4307         static struct time author_time;
4309         if (!text && *read_date) {
4310                 *read_date = FALSE;
4311                 return TRUE;
4313         } else if (!text) {
4314                 char *path = *opt_path ? opt_path : ".";
4315                 /* Find next entry to process */
4316                 const char *log_file[] = {
4317                         "git", "log", "--no-color", "--pretty=raw",
4318                                 "--cc", "--raw", view->id, "--", path, NULL
4319                 };
4320                 struct io io = {};
4322                 if (!view->lines) {
4323                         tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL);
4324                         report("Tree is empty");
4325                         return TRUE;
4326                 }
4328                 if (!run_io_rd(&io, log_file, opt_cdup, FORMAT_NONE)) {
4329                         report("Failed to load tree data");
4330                         return TRUE;
4331                 }
4333                 done_io(view->pipe);
4334                 view->io = io;
4335                 *read_date = TRUE;
4336                 return FALSE;
4338         } else if (*text == 'a' && get_line_type(text) == LINE_AUTHOR) {
4339                 parse_author_line(text + STRING_SIZE("author "),
4340                                   &author_name, &author_time);
4342         } else if (*text == ':') {
4343                 char *pos;
4344                 size_t annotated = 1;
4345                 size_t i;
4347                 pos = strchr(text, '\t');
4348                 if (!pos)
4349                         return TRUE;
4350                 text = pos + 1;
4351                 if (*opt_path && !strncmp(text, opt_path, strlen(opt_path)))
4352                         text += strlen(opt_path);
4353                 pos = strchr(text, '/');
4354                 if (pos)
4355                         *pos = 0;
4357                 for (i = 1; i < view->lines; i++) {
4358                         struct line *line = &view->line[i];
4359                         struct tree_entry *entry = line->data;
4361                         annotated += !!entry->author;
4362                         if (entry->author || strcmp(entry->name, text))
4363                                 continue;
4365                         entry->author = author_name;
4366                         entry->time = author_time;
4367                         line->dirty = 1;
4368                         break;
4369                 }
4371                 if (annotated == view->lines)
4372                         kill_io(view->pipe);
4373         }
4374         return TRUE;
4377 static bool
4378 tree_read(struct view *view, char *text)
4380         static bool read_date = FALSE;
4381         struct tree_entry *data;
4382         struct line *entry, *line;
4383         enum line_type type;
4384         size_t textlen = text ? strlen(text) : 0;
4385         char *path = text + SIZEOF_TREE_ATTR;
4387         if (read_date || !text)
4388                 return tree_read_date(view, text, &read_date);
4390         if (textlen <= SIZEOF_TREE_ATTR)
4391                 return FALSE;
4392         if (view->lines == 0 &&
4393             !tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL))
4394                 return FALSE;
4396         /* Strip the path part ... */
4397         if (*opt_path) {
4398                 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
4399                 size_t striplen = strlen(opt_path);
4401                 if (pathlen > striplen)
4402                         memmove(path, path + striplen,
4403                                 pathlen - striplen + 1);
4405                 /* Insert "link" to parent directory. */
4406                 if (view->lines == 1 &&
4407                     !tree_entry(view, LINE_TREE_DIR, "..", "040000", view->ref))
4408                         return FALSE;
4409         }
4411         type = text[SIZEOF_TREE_MODE] == 't' ? LINE_TREE_DIR : LINE_TREE_FILE;
4412         entry = tree_entry(view, type, path, text, text + TREE_ID_OFFSET);
4413         if (!entry)
4414                 return FALSE;
4415         data = entry->data;
4417         /* Skip "Directory ..." and ".." line. */
4418         for (line = &view->line[1 + !!*opt_path]; line < entry; line++) {
4419                 if (tree_compare_entry(line, entry) <= 0)
4420                         continue;
4422                 memmove(line + 1, line, (entry - line) * sizeof(*entry));
4424                 line->data = data;
4425                 line->type = type;
4426                 for (; line <= entry; line++)
4427                         line->dirty = line->cleareol = 1;
4428                 return TRUE;
4429         }
4431         if (tree_lineno > view->lineno) {
4432                 view->lineno = tree_lineno;
4433                 tree_lineno = 0;
4434         }
4436         return TRUE;
4439 static bool
4440 tree_draw(struct view *view, struct line *line, unsigned int lineno)
4442         struct tree_entry *entry = line->data;
4444         if (line->type == LINE_TREE_HEAD) {
4445                 if (draw_text(view, line->type, "Directory path /", TRUE))
4446                         return TRUE;
4447         } else {
4448                 if (draw_mode(view, entry->mode))
4449                         return TRUE;
4451                 if (opt_author && draw_author(view, entry->author))
4452                         return TRUE;
4454                 if (opt_date && draw_date(view, &entry->time))
4455                         return TRUE;
4456         }
4457         if (draw_text(view, line->type, entry->name, TRUE))
4458                 return TRUE;
4459         return TRUE;
4462 static void
4463 open_blob_editor()
4465         char file[SIZEOF_STR] = "/tmp/tigblob.XXXXXX";
4466         int fd = mkstemp(file);
4468         if (fd == -1)
4469                 report("Failed to create temporary file");
4470         else if (!run_io_append(blob_ops.argv, FORMAT_ALL, fd))
4471                 report("Failed to save blob data to file");
4472         else
4473                 open_editor(file);
4474         if (fd != -1)
4475                 unlink(file);
4478 static enum request
4479 tree_request(struct view *view, enum request request, struct line *line)
4481         enum open_flags flags;
4483         switch (request) {
4484         case REQ_VIEW_BLAME:
4485                 if (line->type != LINE_TREE_FILE) {
4486                         report("Blame only supported for files");
4487                         return REQ_NONE;
4488                 }
4490                 string_copy(opt_ref, view->vid);
4491                 return request;
4493         case REQ_EDIT:
4494                 if (line->type != LINE_TREE_FILE) {
4495                         report("Edit only supported for files");
4496                 } else if (!is_head_commit(view->vid)) {
4497                         open_blob_editor();
4498                 } else {
4499                         open_editor(opt_file);
4500                 }
4501                 return REQ_NONE;
4503         case REQ_TOGGLE_SORT_FIELD:
4504         case REQ_TOGGLE_SORT_ORDER:
4505                 sort_view(view, request, &tree_sort_state, tree_compare);
4506                 return REQ_NONE;
4508         case REQ_PARENT:
4509                 if (!*opt_path) {
4510                         /* quit view if at top of tree */
4511                         return REQ_VIEW_CLOSE;
4512                 }
4513                 /* fake 'cd  ..' */
4514                 line = &view->line[1];
4515                 break;
4517         case REQ_ENTER:
4518                 break;
4520         default:
4521                 return request;
4522         }
4524         /* Cleanup the stack if the tree view is at a different tree. */
4525         while (!*opt_path && tree_stack)
4526                 pop_tree_stack_entry();
4528         switch (line->type) {
4529         case LINE_TREE_DIR:
4530                 /* Depending on whether it is a subdirectory or parent link
4531                  * mangle the path buffer. */
4532                 if (line == &view->line[1] && *opt_path) {
4533                         pop_tree_stack_entry();
4535                 } else {
4536                         const char *basename = tree_path(line);
4538                         push_tree_stack_entry(basename, view->lineno);
4539                 }
4541                 /* Trees and subtrees share the same ID, so they are not not
4542                  * unique like blobs. */
4543                 flags = OPEN_RELOAD;
4544                 request = REQ_VIEW_TREE;
4545                 break;
4547         case LINE_TREE_FILE:
4548                 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4549                 request = REQ_VIEW_BLOB;
4550                 break;
4552         default:
4553                 return REQ_NONE;
4554         }
4556         open_view(view, request, flags);
4557         if (request == REQ_VIEW_TREE)
4558                 view->lineno = tree_lineno;
4560         return REQ_NONE;
4563 static bool
4564 tree_grep(struct view *view, struct line *line)
4566         struct tree_entry *entry = line->data;
4567         const char *text[] = {
4568                 entry->name,
4569                 opt_author ? entry->author : "",
4570                 opt_date ? mkdate(&entry->time) : "",
4571                 NULL
4572         };
4574         return grep_text(view, text);
4577 static void
4578 tree_select(struct view *view, struct line *line)
4580         struct tree_entry *entry = line->data;
4582         if (line->type == LINE_TREE_FILE) {
4583                 string_copy_rev(ref_blob, entry->id);
4584                 string_format(opt_file, "%s%s", opt_path, tree_path(line));
4586         } else if (line->type != LINE_TREE_DIR) {
4587                 return;
4588         }
4590         string_copy_rev(view->ref, entry->id);
4593 static bool
4594 tree_prepare(struct view *view)
4596         if (view->lines == 0 && opt_prefix[0]) {
4597                 char *pos = opt_prefix;
4599                 while (pos && *pos) {
4600                         char *end = strchr(pos, '/');
4602                         if (end)
4603                                 *end = 0;
4604                         push_tree_stack_entry(pos, 0);
4605                         pos = end;
4606                         if (end) {
4607                                 *end = '/';
4608                                 pos++;
4609                         }
4610                 }
4612         } else if (strcmp(view->vid, view->id)) {
4613                 opt_path[0] = 0;
4614         }
4616         return init_io_rd(&view->io, view->ops->argv, opt_cdup, FORMAT_ALL);
4619 static const char *tree_argv[SIZEOF_ARG] = {
4620         "git", "ls-tree", "%(commit)", "%(directory)", NULL
4621 };
4623 static struct view_ops tree_ops = {
4624         "file",
4625         tree_argv,
4626         NULL,
4627         tree_read,
4628         tree_draw,
4629         tree_request,
4630         tree_grep,
4631         tree_select,
4632         tree_prepare,
4633 };
4635 static bool
4636 blob_read(struct view *view, char *line)
4638         if (!line)
4639                 return TRUE;
4640         return add_line_text(view, line, LINE_DEFAULT) != NULL;
4643 static enum request
4644 blob_request(struct view *view, enum request request, struct line *line)
4646         switch (request) {
4647         case REQ_EDIT:
4648                 open_blob_editor();
4649                 return REQ_NONE;
4650         default:
4651                 return pager_request(view, request, line);
4652         }
4655 static const char *blob_argv[SIZEOF_ARG] = {
4656         "git", "cat-file", "blob", "%(blob)", NULL
4657 };
4659 static struct view_ops blob_ops = {
4660         "line",
4661         blob_argv,
4662         NULL,
4663         blob_read,
4664         pager_draw,
4665         blob_request,
4666         pager_grep,
4667         pager_select,
4668 };
4670 /*
4671  * Blame backend
4672  *
4673  * Loading the blame view is a two phase job:
4674  *
4675  *  1. File content is read either using opt_file from the
4676  *     filesystem or using git-cat-file.
4677  *  2. Then blame information is incrementally added by
4678  *     reading output from git-blame.
4679  */
4681 static const char *blame_head_argv[] = {
4682         "git", "blame", "--incremental", "--", "%(file)", NULL
4683 };
4685 static const char *blame_ref_argv[] = {
4686         "git", "blame", "--incremental", "%(ref)", "--", "%(file)", NULL
4687 };
4689 static const char *blame_cat_file_argv[] = {
4690         "git", "cat-file", "blob", "%(ref):%(file)", NULL
4691 };
4693 struct blame_commit {
4694         char id[SIZEOF_REV];            /* SHA1 ID. */
4695         char title[128];                /* First line of the commit message. */
4696         const char *author;             /* Author of the commit. */
4697         struct time time;               /* Date from the author ident. */
4698         char filename[128];             /* Name of file. */
4699         bool has_previous;              /* Was a "previous" line detected. */
4700 };
4702 struct blame {
4703         struct blame_commit *commit;
4704         unsigned long lineno;
4705         char text[1];
4706 };
4708 static bool
4709 blame_open(struct view *view)
4711         char path[SIZEOF_STR];
4713         if (!view->parent && *opt_prefix) {
4714                 string_copy(path, opt_file);
4715                 if (!string_format(opt_file, "%s%s", opt_prefix, path))
4716                         return FALSE;
4717         }
4719         if (*opt_ref || !io_open(&view->io, "%s%s", opt_cdup, opt_file)) {
4720                 if (!run_io_rd(&view->io, blame_cat_file_argv, opt_cdup, FORMAT_ALL))
4721                         return FALSE;
4722         }
4724         setup_update(view, opt_file);
4725         string_format(view->ref, "%s ...", opt_file);
4727         return TRUE;
4730 static struct blame_commit *
4731 get_blame_commit(struct view *view, const char *id)
4733         size_t i;
4735         for (i = 0; i < view->lines; i++) {
4736                 struct blame *blame = view->line[i].data;
4738                 if (!blame->commit)
4739                         continue;
4741                 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
4742                         return blame->commit;
4743         }
4745         {
4746                 struct blame_commit *commit = calloc(1, sizeof(*commit));
4748                 if (commit)
4749                         string_ncopy(commit->id, id, SIZEOF_REV);
4750                 return commit;
4751         }
4754 static bool
4755 parse_number(const char **posref, size_t *number, size_t min, size_t max)
4757         const char *pos = *posref;
4759         *posref = NULL;
4760         pos = strchr(pos + 1, ' ');
4761         if (!pos || !isdigit(pos[1]))
4762                 return FALSE;
4763         *number = atoi(pos + 1);
4764         if (*number < min || *number > max)
4765                 return FALSE;
4767         *posref = pos;
4768         return TRUE;
4771 static struct blame_commit *
4772 parse_blame_commit(struct view *view, const char *text, int *blamed)
4774         struct blame_commit *commit;
4775         struct blame *blame;
4776         const char *pos = text + SIZEOF_REV - 2;
4777         size_t orig_lineno = 0;
4778         size_t lineno;
4779         size_t group;
4781         if (strlen(text) <= SIZEOF_REV || pos[1] != ' ')
4782                 return NULL;
4784         if (!parse_number(&pos, &orig_lineno, 1, 9999999) ||
4785             !parse_number(&pos, &lineno, 1, view->lines) ||
4786             !parse_number(&pos, &group, 1, view->lines - lineno + 1))
4787                 return NULL;
4789         commit = get_blame_commit(view, text);
4790         if (!commit)
4791                 return NULL;
4793         *blamed += group;
4794         while (group--) {
4795                 struct line *line = &view->line[lineno + group - 1];
4797                 blame = line->data;
4798                 blame->commit = commit;
4799                 blame->lineno = orig_lineno + group - 1;
4800                 line->dirty = 1;
4801         }
4803         return commit;
4806 static bool
4807 blame_read_file(struct view *view, const char *line, bool *read_file)
4809         if (!line) {
4810                 const char **argv = *opt_ref ? blame_ref_argv : blame_head_argv;
4811                 struct io io = {};
4813                 if (view->lines == 0 && !view->parent)
4814                         die("No blame exist for %s", view->vid);
4816                 if (view->lines == 0 || !run_io_rd(&io, argv, opt_cdup, FORMAT_ALL)) {
4817                         report("Failed to load blame data");
4818                         return TRUE;
4819                 }
4821                 done_io(view->pipe);
4822                 view->io = io;
4823                 *read_file = FALSE;
4824                 return FALSE;
4826         } else {
4827                 size_t linelen = strlen(line);
4828                 struct blame *blame = malloc(sizeof(*blame) + linelen);
4830                 if (!blame)
4831                         return FALSE;
4833                 blame->commit = NULL;
4834                 strncpy(blame->text, line, linelen);
4835                 blame->text[linelen] = 0;
4836                 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
4837         }
4840 static bool
4841 match_blame_header(const char *name, char **line)
4843         size_t namelen = strlen(name);
4844         bool matched = !strncmp(name, *line, namelen);
4846         if (matched)
4847                 *line += namelen;
4849         return matched;
4852 static bool
4853 blame_read(struct view *view, char *line)
4855         static struct blame_commit *commit = NULL;
4856         static int blamed = 0;
4857         static bool read_file = TRUE;
4859         if (read_file)
4860                 return blame_read_file(view, line, &read_file);
4862         if (!line) {
4863                 /* Reset all! */
4864                 commit = NULL;
4865                 blamed = 0;
4866                 read_file = TRUE;
4867                 string_format(view->ref, "%s", view->vid);
4868                 if (view_is_displayed(view)) {
4869                         update_view_title(view);
4870                         redraw_view_from(view, 0);
4871                 }
4872                 return TRUE;
4873         }
4875         if (!commit) {
4876                 commit = parse_blame_commit(view, line, &blamed);
4877                 string_format(view->ref, "%s %2d%%", view->vid,
4878                               view->lines ? blamed * 100 / view->lines : 0);
4880         } else if (match_blame_header("author ", &line)) {
4881                 commit->author = get_author(line);
4883         } else if (match_blame_header("author-time ", &line)) {
4884                 parse_timesec(&commit->time, line);
4886         } else if (match_blame_header("author-tz ", &line)) {
4887                 parse_timezone(&commit->time, line);
4889         } else if (match_blame_header("summary ", &line)) {
4890                 string_ncopy(commit->title, line, strlen(line));
4892         } else if (match_blame_header("previous ", &line)) {
4893                 commit->has_previous = TRUE;
4895         } else if (match_blame_header("filename ", &line)) {
4896                 string_ncopy(commit->filename, line, strlen(line));
4897                 commit = NULL;
4898         }
4900         return TRUE;
4903 static bool
4904 blame_draw(struct view *view, struct line *line, unsigned int lineno)
4906         struct blame *blame = line->data;
4907         struct time *time = NULL;
4908         const char *id = NULL, *author = NULL;
4909         char text[SIZEOF_STR];
4911         if (blame->commit && *blame->commit->filename) {
4912                 id = blame->commit->id;
4913                 author = blame->commit->author;
4914                 time = &blame->commit->time;
4915         }
4917         if (opt_date && draw_date(view, time))
4918                 return TRUE;
4920         if (opt_author && draw_author(view, author))
4921                 return TRUE;
4923         if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
4924                 return TRUE;
4926         if (draw_lineno(view, lineno))
4927                 return TRUE;
4929         string_expand(text, sizeof(text), blame->text, opt_tab_size);
4930         draw_text(view, LINE_DEFAULT, text, TRUE);
4931         return TRUE;
4934 static bool
4935 check_blame_commit(struct blame *blame, bool check_null_id)
4937         if (!blame->commit)
4938                 report("Commit data not loaded yet");
4939         else if (check_null_id && !strcmp(blame->commit->id, NULL_ID))
4940                 report("No commit exist for the selected line");
4941         else
4942                 return TRUE;
4943         return FALSE;
4946 static void
4947 setup_blame_parent_line(struct view *view, struct blame *blame)
4949         const char *diff_tree_argv[] = {
4950                 "git", "diff-tree", "-U0", blame->commit->id,
4951                         "--", blame->commit->filename, NULL
4952         };
4953         struct io io = {};
4954         int parent_lineno = -1;
4955         int blamed_lineno = -1;
4956         char *line;
4958         if (!run_io(&io, diff_tree_argv, NULL, IO_RD))
4959                 return;
4961         while ((line = io_get(&io, '\n', TRUE))) {
4962                 if (*line == '@') {
4963                         char *pos = strchr(line, '+');
4965                         parent_lineno = atoi(line + 4);
4966                         if (pos)
4967                                 blamed_lineno = atoi(pos + 1);
4969                 } else if (*line == '+' && parent_lineno != -1) {
4970                         if (blame->lineno == blamed_lineno - 1 &&
4971                             !strcmp(blame->text, line + 1)) {
4972                                 view->lineno = parent_lineno ? parent_lineno - 1 : 0;
4973                                 break;
4974                         }
4975                         blamed_lineno++;
4976                 }
4977         }
4979         done_io(&io);
4982 static enum request
4983 blame_request(struct view *view, enum request request, struct line *line)
4985         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4986         struct blame *blame = line->data;
4988         switch (request) {
4989         case REQ_VIEW_BLAME:
4990                 if (check_blame_commit(blame, TRUE)) {
4991                         string_copy(opt_ref, blame->commit->id);
4992                         string_copy(opt_file, blame->commit->filename);
4993                         if (blame->lineno)
4994                                 view->lineno = blame->lineno;
4995                         open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4996                 }
4997                 break;
4999         case REQ_PARENT:
5000                 if (check_blame_commit(blame, TRUE) &&
5001                     select_commit_parent(blame->commit->id, opt_ref,
5002                                          blame->commit->filename)) {
5003                         string_copy(opt_file, blame->commit->filename);
5004                         setup_blame_parent_line(view, blame);
5005                         open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
5006                 }
5007                 break;
5009         case REQ_ENTER:
5010                 if (!check_blame_commit(blame, FALSE))
5011                         break;
5013                 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
5014                     !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
5015                         break;
5017                 if (!strcmp(blame->commit->id, NULL_ID)) {
5018                         struct view *diff = VIEW(REQ_VIEW_DIFF);
5019                         const char *diff_index_argv[] = {
5020                                 "git", "diff-index", "--root", "--patch-with-stat",
5021                                         "-C", "-M", "HEAD", "--", view->vid, NULL
5022                         };
5024                         if (!blame->commit->has_previous) {
5025                                 diff_index_argv[1] = "diff";
5026                                 diff_index_argv[2] = "--no-color";
5027                                 diff_index_argv[6] = "--";
5028                                 diff_index_argv[7] = "/dev/null";
5029                         }
5031                         if (!prepare_update(diff, diff_index_argv, NULL, FORMAT_DASH)) {
5032                                 report("Failed to allocate diff command");
5033                                 break;
5034                         }
5035                         flags |= OPEN_PREPARED;
5036                 }
5038                 open_view(view, REQ_VIEW_DIFF, flags);
5039                 if (VIEW(REQ_VIEW_DIFF)->pipe && !strcmp(blame->commit->id, NULL_ID))
5040                         string_copy_rev(VIEW(REQ_VIEW_DIFF)->ref, NULL_ID);
5041                 break;
5043         default:
5044                 return request;
5045         }
5047         return REQ_NONE;
5050 static bool
5051 blame_grep(struct view *view, struct line *line)
5053         struct blame *blame = line->data;
5054         struct blame_commit *commit = blame->commit;
5055         const char *text[] = {
5056                 blame->text,
5057                 commit ? commit->title : "",
5058                 commit ? commit->id : "",
5059                 commit && opt_author ? commit->author : "",
5060                 commit && opt_date ? mkdate(&commit->time) : "",
5061                 NULL
5062         };
5064         return grep_text(view, text);
5067 static void
5068 blame_select(struct view *view, struct line *line)
5070         struct blame *blame = line->data;
5071         struct blame_commit *commit = blame->commit;
5073         if (!commit)
5074                 return;
5076         if (!strcmp(commit->id, NULL_ID))
5077                 string_ncopy(ref_commit, "HEAD", 4);
5078         else
5079                 string_copy_rev(ref_commit, commit->id);
5082 static struct view_ops blame_ops = {
5083         "line",
5084         NULL,
5085         blame_open,
5086         blame_read,
5087         blame_draw,
5088         blame_request,
5089         blame_grep,
5090         blame_select,
5091 };
5093 /*
5094  * Branch backend
5095  */
5097 struct branch {
5098         const char *author;             /* Author of the last commit. */
5099         struct time time;               /* Date of the last activity. */
5100         const struct ref *ref;          /* Name and commit ID information. */
5101 };
5103 static const struct ref branch_all;
5105 static const enum sort_field branch_sort_fields[] = {
5106         ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
5107 };
5108 static struct sort_state branch_sort_state = SORT_STATE(branch_sort_fields);
5110 static int
5111 branch_compare(const void *l1, const void *l2)
5113         const struct branch *branch1 = ((const struct line *) l1)->data;
5114         const struct branch *branch2 = ((const struct line *) l2)->data;
5116         switch (get_sort_field(branch_sort_state)) {
5117         case ORDERBY_DATE:
5118                 return sort_order(branch_sort_state, timecmp(&branch1->time, &branch2->time));
5120         case ORDERBY_AUTHOR:
5121                 return sort_order(branch_sort_state, strcmp(branch1->author, branch2->author));
5123         case ORDERBY_NAME:
5124         default:
5125                 return sort_order(branch_sort_state, strcmp(branch1->ref->name, branch2->ref->name));
5126         }
5129 static bool
5130 branch_draw(struct view *view, struct line *line, unsigned int lineno)
5132         struct branch *branch = line->data;
5133         enum line_type type = branch->ref->head ? LINE_MAIN_HEAD : LINE_DEFAULT;
5135         if (opt_date && draw_date(view, &branch->time))
5136                 return TRUE;
5138         if (opt_author && draw_author(view, branch->author))
5139                 return TRUE;
5141         draw_text(view, type, branch->ref == &branch_all ? "All branches" : branch->ref->name, TRUE);
5142         return TRUE;
5145 static enum request
5146 branch_request(struct view *view, enum request request, struct line *line)
5148         struct branch *branch = line->data;
5150         switch (request) {
5151         case REQ_REFRESH:
5152                 load_refs();
5153                 open_view(view, REQ_VIEW_BRANCH, OPEN_REFRESH);
5154                 return REQ_NONE;
5156         case REQ_TOGGLE_SORT_FIELD:
5157         case REQ_TOGGLE_SORT_ORDER:
5158                 sort_view(view, request, &branch_sort_state, branch_compare);
5159                 return REQ_NONE;
5161         case REQ_ENTER:
5162                 if (branch->ref == &branch_all) {
5163                         const char *all_branches_argv[] = {
5164                                 "git", "log", "--no-color", "--pretty=raw", "--parents",
5165                                       "--topo-order", "--all", NULL
5166                         };
5167                         struct view *main_view = VIEW(REQ_VIEW_MAIN);
5169                         if (!prepare_update(main_view, all_branches_argv, NULL, FORMAT_NONE)) {
5170                                 report("Failed to load view of all branches");
5171                                 return REQ_NONE;
5172                         }
5173                         open_view(view, REQ_VIEW_MAIN, OPEN_PREPARED | OPEN_SPLIT);
5174                 } else {
5175                         open_view(view, REQ_VIEW_MAIN, OPEN_SPLIT);
5176                 }
5177                 return REQ_NONE;
5179         default:
5180                 return request;
5181         }
5184 static bool
5185 branch_read(struct view *view, char *line)
5187         static char id[SIZEOF_REV];
5188         struct branch *reference;
5189         size_t i;
5191         if (!line)
5192                 return TRUE;
5194         switch (get_line_type(line)) {
5195         case LINE_COMMIT:
5196                 string_copy_rev(id, line + STRING_SIZE("commit "));
5197                 return TRUE;
5199         case LINE_AUTHOR:
5200                 for (i = 0, reference = NULL; i < view->lines; i++) {
5201                         struct branch *branch = view->line[i].data;
5203                         if (strcmp(branch->ref->id, id))
5204                                 continue;
5206                         view->line[i].dirty = TRUE;
5207                         if (reference) {
5208                                 branch->author = reference->author;
5209                                 branch->time = reference->time;
5210                                 continue;
5211                         }
5213                         parse_author_line(line + STRING_SIZE("author "),
5214                                           &branch->author, &branch->time);
5215                         reference = branch;
5216                 }
5217                 return TRUE;
5219         default:
5220                 return TRUE;
5221         }
5225 static bool
5226 branch_open_visitor(void *data, const struct ref *ref)
5228         struct view *view = data;
5229         struct branch *branch;
5231         if (ref->tag || ref->ltag || ref->remote)
5232                 return TRUE;
5234         branch = calloc(1, sizeof(*branch));
5235         if (!branch)
5236                 return FALSE;
5238         branch->ref = ref;
5239         return !!add_line_data(view, branch, LINE_DEFAULT);
5242 static bool
5243 branch_open(struct view *view)
5245         const char *branch_log[] = {
5246                 "git", "log", "--no-color", "--pretty=raw",
5247                         "--simplify-by-decoration", "--all", NULL
5248         };
5250         if (!run_io_rd(&view->io, branch_log, NULL, FORMAT_NONE)) {
5251                 report("Failed to load branch data");
5252                 return TRUE;
5253         }
5255         setup_update(view, view->id);
5256         branch_open_visitor(view, &branch_all);
5257         foreach_ref(branch_open_visitor, view);
5258         view->p_restore = TRUE;
5260         return TRUE;
5263 static bool
5264 branch_grep(struct view *view, struct line *line)
5266         struct branch *branch = line->data;
5267         const char *text[] = {
5268                 branch->ref->name,
5269                 branch->author,
5270                 NULL
5271         };
5273         return grep_text(view, text);
5276 static void
5277 branch_select(struct view *view, struct line *line)
5279         struct branch *branch = line->data;
5281         string_copy_rev(view->ref, branch->ref->id);
5282         string_copy_rev(ref_commit, branch->ref->id);
5283         string_copy_rev(ref_head, branch->ref->id);
5286 static struct view_ops branch_ops = {
5287         "branch",
5288         NULL,
5289         branch_open,
5290         branch_read,
5291         branch_draw,
5292         branch_request,
5293         branch_grep,
5294         branch_select,
5295 };
5297 /*
5298  * Status backend
5299  */
5301 struct status {
5302         char status;
5303         struct {
5304                 mode_t mode;
5305                 char rev[SIZEOF_REV];
5306                 char name[SIZEOF_STR];
5307         } old;
5308         struct {
5309                 mode_t mode;
5310                 char rev[SIZEOF_REV];
5311                 char name[SIZEOF_STR];
5312         } new;
5313 };
5315 static char status_onbranch[SIZEOF_STR];
5316 static struct status stage_status;
5317 static enum line_type stage_line_type;
5318 static size_t stage_chunks;
5319 static int *stage_chunk;
5321 DEFINE_ALLOCATOR(realloc_ints, int, 32)
5323 /* This should work even for the "On branch" line. */
5324 static inline bool
5325 status_has_none(struct view *view, struct line *line)
5327         return line < view->line + view->lines && !line[1].data;
5330 /* Get fields from the diff line:
5331  * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
5332  */
5333 static inline bool
5334 status_get_diff(struct status *file, const char *buf, size_t bufsize)
5336         const char *old_mode = buf +  1;
5337         const char *new_mode = buf +  8;
5338         const char *old_rev  = buf + 15;
5339         const char *new_rev  = buf + 56;
5340         const char *status   = buf + 97;
5342         if (bufsize < 98 ||
5343             old_mode[-1] != ':' ||
5344             new_mode[-1] != ' ' ||
5345             old_rev[-1]  != ' ' ||
5346             new_rev[-1]  != ' ' ||
5347             status[-1]   != ' ')
5348                 return FALSE;
5350         file->status = *status;
5352         string_copy_rev(file->old.rev, old_rev);
5353         string_copy_rev(file->new.rev, new_rev);
5355         file->old.mode = strtoul(old_mode, NULL, 8);
5356         file->new.mode = strtoul(new_mode, NULL, 8);
5358         file->old.name[0] = file->new.name[0] = 0;
5360         return TRUE;
5363 static bool
5364 status_run(struct view *view, const char *argv[], char status, enum line_type type)
5366         struct status *unmerged = NULL;
5367         char *buf;
5368         struct io io = {};
5370         if (!run_io(&io, argv, opt_cdup, IO_RD))
5371                 return FALSE;
5373         add_line_data(view, NULL, type);
5375         while ((buf = io_get(&io, 0, TRUE))) {
5376                 struct status *file = unmerged;
5378                 if (!file) {
5379                         file = calloc(1, sizeof(*file));
5380                         if (!file || !add_line_data(view, file, type))
5381                                 goto error_out;
5382                 }
5384                 /* Parse diff info part. */
5385                 if (status) {
5386                         file->status = status;
5387                         if (status == 'A')
5388                                 string_copy(file->old.rev, NULL_ID);
5390                 } else if (!file->status || file == unmerged) {
5391                         if (!status_get_diff(file, buf, strlen(buf)))
5392                                 goto error_out;
5394                         buf = io_get(&io, 0, TRUE);
5395                         if (!buf)
5396                                 break;
5398                         /* Collapse all modified entries that follow an
5399                          * associated unmerged entry. */
5400                         if (unmerged == file) {
5401                                 unmerged->status = 'U';
5402                                 unmerged = NULL;
5403                         } else if (file->status == 'U') {
5404                                 unmerged = file;
5405                         }
5406                 }
5408                 /* Grab the old name for rename/copy. */
5409                 if (!*file->old.name &&
5410                     (file->status == 'R' || file->status == 'C')) {
5411                         string_ncopy(file->old.name, buf, strlen(buf));
5413                         buf = io_get(&io, 0, TRUE);
5414                         if (!buf)
5415                                 break;
5416                 }
5418                 /* git-ls-files just delivers a NUL separated list of
5419                  * file names similar to the second half of the
5420                  * git-diff-* output. */
5421                 string_ncopy(file->new.name, buf, strlen(buf));
5422                 if (!*file->old.name)
5423                         string_copy(file->old.name, file->new.name);
5424                 file = NULL;
5425         }
5427         if (io_error(&io)) {
5428 error_out:
5429                 done_io(&io);
5430                 return FALSE;
5431         }
5433         if (!view->line[view->lines - 1].data)
5434                 add_line_data(view, NULL, LINE_STAT_NONE);
5436         done_io(&io);
5437         return TRUE;
5440 /* Don't show unmerged entries in the staged section. */
5441 static const char *status_diff_index_argv[] = {
5442         "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
5443                              "--cached", "-M", "HEAD", NULL
5444 };
5446 static const char *status_diff_files_argv[] = {
5447         "git", "diff-files", "-z", NULL
5448 };
5450 static const char *status_list_other_argv[] = {
5451         "git", "ls-files", "-z", "--others", "--exclude-standard", opt_prefix, NULL
5452 };
5454 static const char *status_list_no_head_argv[] = {
5455         "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
5456 };
5458 static const char *update_index_argv[] = {
5459         "git", "update-index", "-q", "--unmerged", "--refresh", NULL
5460 };
5462 /* Restore the previous line number to stay in the context or select a
5463  * line with something that can be updated. */
5464 static void
5465 status_restore(struct view *view)
5467         if (view->p_lineno >= view->lines)
5468                 view->p_lineno = view->lines - 1;
5469         while (view->p_lineno < view->lines && !view->line[view->p_lineno].data)
5470                 view->p_lineno++;
5471         while (view->p_lineno > 0 && !view->line[view->p_lineno].data)
5472                 view->p_lineno--;
5474         /* If the above fails, always skip the "On branch" line. */
5475         if (view->p_lineno < view->lines)
5476                 view->lineno = view->p_lineno;
5477         else
5478                 view->lineno = 1;
5480         if (view->lineno < view->offset)
5481                 view->offset = view->lineno;
5482         else if (view->offset + view->height <= view->lineno)
5483                 view->offset = view->lineno - view->height + 1;
5485         view->p_restore = FALSE;
5488 static void
5489 status_update_onbranch(void)
5491         static const char *paths[][2] = {
5492                 { "rebase-apply/rebasing",      "Rebasing" },
5493                 { "rebase-apply/applying",      "Applying mailbox" },
5494                 { "rebase-apply/",              "Rebasing mailbox" },
5495                 { "rebase-merge/interactive",   "Interactive rebase" },
5496                 { "rebase-merge/",              "Rebase merge" },
5497                 { "MERGE_HEAD",                 "Merging" },
5498                 { "BISECT_LOG",                 "Bisecting" },
5499                 { "HEAD",                       "On branch" },
5500         };
5501         char buf[SIZEOF_STR];
5502         struct stat stat;
5503         int i;
5505         if (is_initial_commit()) {
5506                 string_copy(status_onbranch, "Initial commit");
5507                 return;
5508         }
5510         for (i = 0; i < ARRAY_SIZE(paths); i++) {
5511                 char *head = opt_head;
5513                 if (!string_format(buf, "%s/%s", opt_git_dir, paths[i][0]) ||
5514                     lstat(buf, &stat) < 0)
5515                         continue;
5517                 if (!*opt_head) {
5518                         struct io io = {};
5520                         if (io_open(&io, "%s/rebase-merge/head-name", opt_git_dir) &&
5521                             io_read_buf(&io, buf, sizeof(buf))) {
5522                                 head = buf;
5523                                 if (!prefixcmp(head, "refs/heads/"))
5524                                         head += STRING_SIZE("refs/heads/");
5525                         }
5526                 }
5528                 if (!string_format(status_onbranch, "%s %s", paths[i][1], head))
5529                         string_copy(status_onbranch, opt_head);
5530                 return;
5531         }
5533         string_copy(status_onbranch, "Not currently on any branch");
5536 /* First parse staged info using git-diff-index(1), then parse unstaged
5537  * info using git-diff-files(1), and finally untracked files using
5538  * git-ls-files(1). */
5539 static bool
5540 status_open(struct view *view)
5542         reset_view(view);
5544         add_line_data(view, NULL, LINE_STAT_HEAD);
5545         status_update_onbranch();
5547         run_io_bg(update_index_argv);
5549         if (is_initial_commit()) {
5550                 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
5551                         return FALSE;
5552         } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
5553                 return FALSE;
5554         }
5556         if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
5557             !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
5558                 return FALSE;
5560         /* Restore the exact position or use the specialized restore
5561          * mode? */
5562         if (!view->p_restore)
5563                 status_restore(view);
5564         return TRUE;
5567 static bool
5568 status_draw(struct view *view, struct line *line, unsigned int lineno)
5570         struct status *status = line->data;
5571         enum line_type type;
5572         const char *text;
5574         if (!status) {
5575                 switch (line->type) {
5576                 case LINE_STAT_STAGED:
5577                         type = LINE_STAT_SECTION;
5578                         text = "Changes to be committed:";
5579                         break;
5581                 case LINE_STAT_UNSTAGED:
5582                         type = LINE_STAT_SECTION;
5583                         text = "Changed but not updated:";
5584                         break;
5586                 case LINE_STAT_UNTRACKED:
5587                         type = LINE_STAT_SECTION;
5588                         text = "Untracked files:";
5589                         break;
5591                 case LINE_STAT_NONE:
5592                         type = LINE_DEFAULT;
5593                         text = "  (no files)";
5594                         break;
5596                 case LINE_STAT_HEAD:
5597                         type = LINE_STAT_HEAD;
5598                         text = status_onbranch;
5599                         break;
5601                 default:
5602                         return FALSE;
5603                 }
5604         } else {
5605                 static char buf[] = { '?', ' ', ' ', ' ', 0 };
5607                 buf[0] = status->status;
5608                 if (draw_text(view, line->type, buf, TRUE))
5609                         return TRUE;
5610                 type = LINE_DEFAULT;
5611                 text = status->new.name;
5612         }
5614         draw_text(view, type, text, TRUE);
5615         return TRUE;
5618 static enum request
5619 status_load_error(struct view *view, struct view *stage, const char *path)
5621         if (displayed_views() == 2 || display[current_view] != view)
5622                 maximize_view(view);
5623         report("Failed to load '%s': %s", path, io_strerror(&stage->io));
5624         return REQ_NONE;
5627 static enum request
5628 status_enter(struct view *view, struct line *line)
5630         struct status *status = line->data;
5631         const char *oldpath = status ? status->old.name : NULL;
5632         /* Diffs for unmerged entries are empty when passing the new
5633          * path, so leave it empty. */
5634         const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
5635         const char *info;
5636         enum open_flags split;
5637         struct view *stage = VIEW(REQ_VIEW_STAGE);
5639         if (line->type == LINE_STAT_NONE ||
5640             (!status && line[1].type == LINE_STAT_NONE)) {
5641                 report("No file to diff");
5642                 return REQ_NONE;
5643         }
5645         switch (line->type) {
5646         case LINE_STAT_STAGED:
5647                 if (is_initial_commit()) {
5648                         const char *no_head_diff_argv[] = {
5649                                 "git", "diff", "--no-color", "--patch-with-stat",
5650                                         "--", "/dev/null", newpath, NULL
5651                         };
5653                         if (!prepare_update(stage, no_head_diff_argv, opt_cdup, FORMAT_DASH))
5654                                 return status_load_error(view, stage, newpath);
5655                 } else {
5656                         const char *index_show_argv[] = {
5657                                 "git", "diff-index", "--root", "--patch-with-stat",
5658                                         "-C", "-M", "--cached", "HEAD", "--",
5659                                         oldpath, newpath, NULL
5660                         };
5662                         if (!prepare_update(stage, index_show_argv, opt_cdup, FORMAT_DASH))
5663                                 return status_load_error(view, stage, newpath);
5664                 }
5666                 if (status)
5667                         info = "Staged changes to %s";
5668                 else
5669                         info = "Staged changes";
5670                 break;
5672         case LINE_STAT_UNSTAGED:
5673         {
5674                 const char *files_show_argv[] = {
5675                         "git", "diff-files", "--root", "--patch-with-stat",
5676                                 "-C", "-M", "--", oldpath, newpath, NULL
5677                 };
5679                 if (!prepare_update(stage, files_show_argv, opt_cdup, FORMAT_DASH))
5680                         return status_load_error(view, stage, newpath);
5681                 if (status)
5682                         info = "Unstaged changes to %s";
5683                 else
5684                         info = "Unstaged changes";
5685                 break;
5686         }
5687         case LINE_STAT_UNTRACKED:
5688                 if (!newpath) {
5689                         report("No file to show");
5690                         return REQ_NONE;
5691                 }
5693                 if (!suffixcmp(status->new.name, -1, "/")) {
5694                         report("Cannot display a directory");
5695                         return REQ_NONE;
5696                 }
5698                 if (!prepare_update_file(stage, newpath))
5699                         return status_load_error(view, stage, newpath);
5700                 info = "Untracked file %s";
5701                 break;
5703         case LINE_STAT_HEAD:
5704                 return REQ_NONE;
5706         default:
5707                 die("line type %d not handled in switch", line->type);
5708         }
5710         split = view_is_displayed(view) ? OPEN_SPLIT : 0;
5711         open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split);
5712         if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
5713                 if (status) {
5714                         stage_status = *status;
5715                 } else {
5716                         memset(&stage_status, 0, sizeof(stage_status));
5717                 }
5719                 stage_line_type = line->type;
5720                 stage_chunks = 0;
5721                 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
5722         }
5724         return REQ_NONE;
5727 static bool
5728 status_exists(struct status *status, enum line_type type)
5730         struct view *view = VIEW(REQ_VIEW_STATUS);
5731         unsigned long lineno;
5733         for (lineno = 0; lineno < view->lines; lineno++) {
5734                 struct line *line = &view->line[lineno];
5735                 struct status *pos = line->data;
5737                 if (line->type != type)
5738                         continue;
5739                 if (!pos && (!status || !status->status) && line[1].data) {
5740                         select_view_line(view, lineno);
5741                         return TRUE;
5742                 }
5743                 if (pos && !strcmp(status->new.name, pos->new.name)) {
5744                         select_view_line(view, lineno);
5745                         return TRUE;
5746                 }
5747         }
5749         return FALSE;
5753 static bool
5754 status_update_prepare(struct io *io, enum line_type type)
5756         const char *staged_argv[] = {
5757                 "git", "update-index", "-z", "--index-info", NULL
5758         };
5759         const char *others_argv[] = {
5760                 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
5761         };
5763         switch (type) {
5764         case LINE_STAT_STAGED:
5765                 return run_io(io, staged_argv, opt_cdup, IO_WR);
5767         case LINE_STAT_UNSTAGED:
5768         case LINE_STAT_UNTRACKED:
5769                 return run_io(io, others_argv, opt_cdup, IO_WR);
5771         default:
5772                 die("line type %d not handled in switch", type);
5773                 return FALSE;
5774         }
5777 static bool
5778 status_update_write(struct io *io, struct status *status, enum line_type type)
5780         char buf[SIZEOF_STR];
5781         size_t bufsize = 0;
5783         switch (type) {
5784         case LINE_STAT_STAGED:
5785                 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
5786                                         status->old.mode,
5787                                         status->old.rev,
5788                                         status->old.name, 0))
5789                         return FALSE;
5790                 break;
5792         case LINE_STAT_UNSTAGED:
5793         case LINE_STAT_UNTRACKED:
5794                 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
5795                         return FALSE;
5796                 break;
5798         default:
5799                 die("line type %d not handled in switch", type);
5800         }
5802         return io_write(io, buf, bufsize);
5805 static bool
5806 status_update_file(struct status *status, enum line_type type)
5808         struct io io = {};
5809         bool result;
5811         if (!status_update_prepare(&io, type))
5812                 return FALSE;
5814         result = status_update_write(&io, status, type);
5815         return done_io(&io) && result;
5818 static bool
5819 status_update_files(struct view *view, struct line *line)
5821         char buf[sizeof(view->ref)];
5822         struct io io = {};
5823         bool result = TRUE;
5824         struct line *pos = view->line + view->lines;
5825         int files = 0;
5826         int file, done;
5827         int cursor_y = -1, cursor_x = -1;
5829         if (!status_update_prepare(&io, line->type))
5830                 return FALSE;
5832         for (pos = line; pos < view->line + view->lines && pos->data; pos++)
5833                 files++;
5835         string_copy(buf, view->ref);
5836         getsyx(cursor_y, cursor_x);
5837         for (file = 0, done = 5; result && file < files; line++, file++) {
5838                 int almost_done = file * 100 / files;
5840                 if (almost_done > done) {
5841                         done = almost_done;
5842                         string_format(view->ref, "updating file %u of %u (%d%% done)",
5843                                       file, files, done);
5844                         update_view_title(view);
5845                         setsyx(cursor_y, cursor_x);
5846                         doupdate();
5847                 }
5848                 result = status_update_write(&io, line->data, line->type);
5849         }
5850         string_copy(view->ref, buf);
5852         return done_io(&io) && result;
5855 static bool
5856 status_update(struct view *view)
5858         struct line *line = &view->line[view->lineno];
5860         assert(view->lines);
5862         if (!line->data) {
5863                 /* This should work even for the "On branch" line. */
5864                 if (line < view->line + view->lines && !line[1].data) {
5865                         report("Nothing to update");
5866                         return FALSE;
5867                 }
5869                 if (!status_update_files(view, line + 1)) {
5870                         report("Failed to update file status");
5871                         return FALSE;
5872                 }
5874         } else if (!status_update_file(line->data, line->type)) {
5875                 report("Failed to update file status");
5876                 return FALSE;
5877         }
5879         return TRUE;
5882 static bool
5883 status_revert(struct status *status, enum line_type type, bool has_none)
5885         if (!status || type != LINE_STAT_UNSTAGED) {
5886                 if (type == LINE_STAT_STAGED) {
5887                         report("Cannot revert changes to staged files");
5888                 } else if (type == LINE_STAT_UNTRACKED) {
5889                         report("Cannot revert changes to untracked files");
5890                 } else if (has_none) {
5891                         report("Nothing to revert");
5892                 } else {
5893                         report("Cannot revert changes to multiple files");
5894                 }
5896         } else if (prompt_yesno("Are you sure you want to revert changes?")) {
5897                 char mode[10] = "100644";
5898                 const char *reset_argv[] = {
5899                         "git", "update-index", "--cacheinfo", mode,
5900                                 status->old.rev, status->old.name, NULL
5901                 };
5902                 const char *checkout_argv[] = {
5903                         "git", "checkout", "--", status->old.name, NULL
5904                 };
5906                 if (status->status == 'U') {
5907                         string_format(mode, "%5o", status->old.mode);
5909                         if (status->old.mode == 0 && status->new.mode == 0) {
5910                                 reset_argv[2] = "--force-remove";
5911                                 reset_argv[3] = status->old.name;
5912                                 reset_argv[4] = NULL;
5913                         }
5915                         if (!run_io_fg(reset_argv, opt_cdup))
5916                                 return FALSE;
5917                         if (status->old.mode == 0 && status->new.mode == 0)
5918                                 return TRUE;
5919                 }
5921                 return run_io_fg(checkout_argv, opt_cdup);
5922         }
5924         return FALSE;
5927 static enum request
5928 status_request(struct view *view, enum request request, struct line *line)
5930         struct status *status = line->data;
5932         switch (request) {
5933         case REQ_STATUS_UPDATE:
5934                 if (!status_update(view))
5935                         return REQ_NONE;
5936                 break;
5938         case REQ_STATUS_REVERT:
5939                 if (!status_revert(status, line->type, status_has_none(view, line)))
5940                         return REQ_NONE;
5941                 break;
5943         case REQ_STATUS_MERGE:
5944                 if (!status || status->status != 'U') {
5945                         report("Merging only possible for files with unmerged status ('U').");
5946                         return REQ_NONE;
5947                 }
5948                 open_mergetool(status->new.name);
5949                 break;
5951         case REQ_EDIT:
5952                 if (!status)
5953                         return request;
5954                 if (status->status == 'D') {
5955                         report("File has been deleted.");
5956                         return REQ_NONE;
5957                 }
5959                 open_editor(status->new.name);
5960                 break;
5962         case REQ_VIEW_BLAME:
5963                 if (status)
5964                         opt_ref[0] = 0;
5965                 return request;
5967         case REQ_ENTER:
5968                 /* After returning the status view has been split to
5969                  * show the stage view. No further reloading is
5970                  * necessary. */
5971                 return status_enter(view, line);
5973         case REQ_REFRESH:
5974                 /* Simply reload the view. */
5975                 break;
5977         default:
5978                 return request;
5979         }
5981         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
5983         return REQ_NONE;
5986 static void
5987 status_select(struct view *view, struct line *line)
5989         struct status *status = line->data;
5990         char file[SIZEOF_STR] = "all files";
5991         const char *text;
5992         const char *key;
5994         if (status && !string_format(file, "'%s'", status->new.name))
5995                 return;
5997         if (!status && line[1].type == LINE_STAT_NONE)
5998                 line++;
6000         switch (line->type) {
6001         case LINE_STAT_STAGED:
6002                 text = "Press %s to unstage %s for commit";
6003                 break;
6005         case LINE_STAT_UNSTAGED:
6006                 text = "Press %s to stage %s for commit";
6007                 break;
6009         case LINE_STAT_UNTRACKED:
6010                 text = "Press %s to stage %s for addition";
6011                 break;
6013         case LINE_STAT_HEAD:
6014         case LINE_STAT_NONE:
6015                 text = "Nothing to update";
6016                 break;
6018         default:
6019                 die("line type %d not handled in switch", line->type);
6020         }
6022         if (status && status->status == 'U') {
6023                 text = "Press %s to resolve conflict in %s";
6024                 key = get_key(KEYMAP_STATUS, REQ_STATUS_MERGE);
6026         } else {
6027                 key = get_key(KEYMAP_STATUS, REQ_STATUS_UPDATE);
6028         }
6030         string_format(view->ref, text, key, file);
6031         if (status)
6032                 string_copy(opt_file, status->new.name);
6035 static bool
6036 status_grep(struct view *view, struct line *line)
6038         struct status *status = line->data;
6040         if (status) {
6041                 const char buf[2] = { status->status, 0 };
6042                 const char *text[] = { status->new.name, buf, NULL };
6044                 return grep_text(view, text);
6045         }
6047         return FALSE;
6050 static struct view_ops status_ops = {
6051         "file",
6052         NULL,
6053         status_open,
6054         NULL,
6055         status_draw,
6056         status_request,
6057         status_grep,
6058         status_select,
6059 };
6062 static bool
6063 stage_diff_write(struct io *io, struct line *line, struct line *end)
6065         while (line < end) {
6066                 if (!io_write(io, line->data, strlen(line->data)) ||
6067                     !io_write(io, "\n", 1))
6068                         return FALSE;
6069                 line++;
6070                 if (line->type == LINE_DIFF_CHUNK ||
6071                     line->type == LINE_DIFF_HEADER)
6072                         break;
6073         }
6075         return TRUE;
6078 static struct line *
6079 stage_diff_find(struct view *view, struct line *line, enum line_type type)
6081         for (; view->line < line; line--)
6082                 if (line->type == type)
6083                         return line;
6085         return NULL;
6088 static bool
6089 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
6091         const char *apply_argv[SIZEOF_ARG] = {
6092                 "git", "apply", "--whitespace=nowarn", NULL
6093         };
6094         struct line *diff_hdr;
6095         struct io io = {};
6096         int argc = 3;
6098         diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
6099         if (!diff_hdr)
6100                 return FALSE;
6102         if (!revert)
6103                 apply_argv[argc++] = "--cached";
6104         if (revert || stage_line_type == LINE_STAT_STAGED)
6105                 apply_argv[argc++] = "-R";
6106         apply_argv[argc++] = "-";
6107         apply_argv[argc++] = NULL;
6108         if (!run_io(&io, apply_argv, opt_cdup, IO_WR))
6109                 return FALSE;
6111         if (!stage_diff_write(&io, diff_hdr, chunk) ||
6112             !stage_diff_write(&io, chunk, view->line + view->lines))
6113                 chunk = NULL;
6115         done_io(&io);
6116         run_io_bg(update_index_argv);
6118         return chunk ? TRUE : FALSE;
6121 static bool
6122 stage_update(struct view *view, struct line *line)
6124         struct line *chunk = NULL;
6126         if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
6127                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6129         if (chunk) {
6130                 if (!stage_apply_chunk(view, chunk, FALSE)) {
6131                         report("Failed to apply chunk");
6132                         return FALSE;
6133                 }
6135         } else if (!stage_status.status) {
6136                 view = VIEW(REQ_VIEW_STATUS);
6138                 for (line = view->line; line < view->line + view->lines; line++)
6139                         if (line->type == stage_line_type)
6140                                 break;
6142                 if (!status_update_files(view, line + 1)) {
6143                         report("Failed to update files");
6144                         return FALSE;
6145                 }
6147         } else if (!status_update_file(&stage_status, stage_line_type)) {
6148                 report("Failed to update file");
6149                 return FALSE;
6150         }
6152         return TRUE;
6155 static bool
6156 stage_revert(struct view *view, struct line *line)
6158         struct line *chunk = NULL;
6160         if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
6161                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6163         if (chunk) {
6164                 if (!prompt_yesno("Are you sure you want to revert changes?"))
6165                         return FALSE;
6167                 if (!stage_apply_chunk(view, chunk, TRUE)) {
6168                         report("Failed to revert chunk");
6169                         return FALSE;
6170                 }
6171                 return TRUE;
6173         } else {
6174                 return status_revert(stage_status.status ? &stage_status : NULL,
6175                                      stage_line_type, FALSE);
6176         }
6180 static void
6181 stage_next(struct view *view, struct line *line)
6183         int i;
6185         if (!stage_chunks) {
6186                 for (line = view->line; line < view->line + view->lines; line++) {
6187                         if (line->type != LINE_DIFF_CHUNK)
6188                                 continue;
6190                         if (!realloc_ints(&stage_chunk, stage_chunks, 1)) {
6191                                 report("Allocation failure");
6192                                 return;
6193                         }
6195                         stage_chunk[stage_chunks++] = line - view->line;
6196                 }
6197         }
6199         for (i = 0; i < stage_chunks; i++) {
6200                 if (stage_chunk[i] > view->lineno) {
6201                         do_scroll_view(view, stage_chunk[i] - view->lineno);
6202                         report("Chunk %d of %d", i + 1, stage_chunks);
6203                         return;
6204                 }
6205         }
6207         report("No next chunk found");
6210 static enum request
6211 stage_request(struct view *view, enum request request, struct line *line)
6213         switch (request) {
6214         case REQ_STATUS_UPDATE:
6215                 if (!stage_update(view, line))
6216                         return REQ_NONE;
6217                 break;
6219         case REQ_STATUS_REVERT:
6220                 if (!stage_revert(view, line))
6221                         return REQ_NONE;
6222                 break;
6224         case REQ_STAGE_NEXT:
6225                 if (stage_line_type == LINE_STAT_UNTRACKED) {
6226                         report("File is untracked; press %s to add",
6227                                get_key(KEYMAP_STAGE, REQ_STATUS_UPDATE));
6228                         return REQ_NONE;
6229                 }
6230                 stage_next(view, line);
6231                 return REQ_NONE;
6233         case REQ_EDIT:
6234                 if (!stage_status.new.name[0])
6235                         return request;
6236                 if (stage_status.status == 'D') {
6237                         report("File has been deleted.");
6238                         return REQ_NONE;
6239                 }
6241                 open_editor(stage_status.new.name);
6242                 break;
6244         case REQ_REFRESH:
6245                 /* Reload everything ... */
6246                 break;
6248         case REQ_VIEW_BLAME:
6249                 if (stage_status.new.name[0]) {
6250                         string_copy(opt_file, stage_status.new.name);
6251                         opt_ref[0] = 0;
6252                 }
6253                 return request;
6255         case REQ_ENTER:
6256                 return pager_request(view, request, line);
6258         default:
6259                 return request;
6260         }
6262         VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
6263         open_view(view, REQ_VIEW_STATUS, OPEN_REFRESH);
6265         /* Check whether the staged entry still exists, and close the
6266          * stage view if it doesn't. */
6267         if (!status_exists(&stage_status, stage_line_type)) {
6268                 status_restore(VIEW(REQ_VIEW_STATUS));
6269                 return REQ_VIEW_CLOSE;
6270         }
6272         if (stage_line_type == LINE_STAT_UNTRACKED) {
6273                 if (!suffixcmp(stage_status.new.name, -1, "/")) {
6274                         report("Cannot display a directory");
6275                         return REQ_NONE;
6276                 }
6278                 if (!prepare_update_file(view, stage_status.new.name)) {
6279                         report("Failed to open file: %s", strerror(errno));
6280                         return REQ_NONE;
6281                 }
6282         }
6283         open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
6285         return REQ_NONE;
6288 static struct view_ops stage_ops = {
6289         "line",
6290         NULL,
6291         NULL,
6292         pager_read,
6293         pager_draw,
6294         stage_request,
6295         pager_grep,
6296         pager_select,
6297 };
6300 /*
6301  * Revision graph
6302  */
6304 struct commit {
6305         char id[SIZEOF_REV];            /* SHA1 ID. */
6306         char title[128];                /* First line of the commit message. */
6307         const char *author;             /* Author of the commit. */
6308         struct time time;               /* Date from the author ident. */
6309         struct ref_list *refs;          /* Repository references. */
6310         chtype graph[SIZEOF_REVGRAPH];  /* Ancestry chain graphics. */
6311         size_t graph_size;              /* The width of the graph array. */
6312         bool has_parents;               /* Rewritten --parents seen. */
6313 };
6315 /* Size of rev graph with no  "padding" columns */
6316 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
6318 struct rev_graph {
6319         struct rev_graph *prev, *next, *parents;
6320         char rev[SIZEOF_REVITEMS][SIZEOF_REV];
6321         size_t size;
6322         struct commit *commit;
6323         size_t pos;
6324         unsigned int boundary:1;
6325 };
6327 /* Parents of the commit being visualized. */
6328 static struct rev_graph graph_parents[4];
6330 /* The current stack of revisions on the graph. */
6331 static struct rev_graph graph_stacks[4] = {
6332         { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
6333         { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
6334         { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
6335         { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
6336 };
6338 static inline bool
6339 graph_parent_is_merge(struct rev_graph *graph)
6341         return graph->parents->size > 1;
6344 static inline void
6345 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
6347         struct commit *commit = graph->commit;
6349         if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
6350                 commit->graph[commit->graph_size++] = symbol;
6353 static void
6354 clear_rev_graph(struct rev_graph *graph)
6356         graph->boundary = 0;
6357         graph->size = graph->pos = 0;
6358         graph->commit = NULL;
6359         memset(graph->parents, 0, sizeof(*graph->parents));
6362 static void
6363 done_rev_graph(struct rev_graph *graph)
6365         if (graph_parent_is_merge(graph) &&
6366             graph->pos < graph->size - 1 &&
6367             graph->next->size == graph->size + graph->parents->size - 1) {
6368                 size_t i = graph->pos + graph->parents->size - 1;
6370                 graph->commit->graph_size = i * 2;
6371                 while (i < graph->next->size - 1) {
6372                         append_to_rev_graph(graph, ' ');
6373                         append_to_rev_graph(graph, '\\');
6374                         i++;
6375                 }
6376         }
6378         clear_rev_graph(graph);
6381 static void
6382 push_rev_graph(struct rev_graph *graph, const char *parent)
6384         int i;
6386         /* "Collapse" duplicate parents lines.
6387          *
6388          * FIXME: This needs to also update update the drawn graph but
6389          * for now it just serves as a method for pruning graph lines. */
6390         for (i = 0; i < graph->size; i++)
6391                 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
6392                         return;
6394         if (graph->size < SIZEOF_REVITEMS) {
6395                 string_copy_rev(graph->rev[graph->size++], parent);
6396         }
6399 static chtype
6400 get_rev_graph_symbol(struct rev_graph *graph)
6402         chtype symbol;
6404         if (graph->boundary)
6405                 symbol = REVGRAPH_BOUND;
6406         else if (graph->parents->size == 0)
6407                 symbol = REVGRAPH_INIT;
6408         else if (graph_parent_is_merge(graph))
6409                 symbol = REVGRAPH_MERGE;
6410         else if (graph->pos >= graph->size)
6411                 symbol = REVGRAPH_BRANCH;
6412         else
6413                 symbol = REVGRAPH_COMMIT;
6415         return symbol;
6418 static void
6419 draw_rev_graph(struct rev_graph *graph)
6421         struct rev_filler {
6422                 chtype separator, line;
6423         };
6424         enum { DEFAULT, RSHARP, RDIAG, LDIAG };
6425         static struct rev_filler fillers[] = {
6426                 { ' ',  '|' },
6427                 { '`',  '.' },
6428                 { '\'', ' ' },
6429                 { '/',  ' ' },
6430         };
6431         chtype symbol = get_rev_graph_symbol(graph);
6432         struct rev_filler *filler;
6433         size_t i;
6435         fillers[DEFAULT].line = opt_line_graphics ? ACS_VLINE : '|';
6436         filler = &fillers[DEFAULT];
6438         for (i = 0; i < graph->pos; i++) {
6439                 append_to_rev_graph(graph, filler->line);
6440                 if (graph_parent_is_merge(graph->prev) &&
6441                     graph->prev->pos == i)
6442                         filler = &fillers[RSHARP];
6444                 append_to_rev_graph(graph, filler->separator);
6445         }
6447         /* Place the symbol for this revision. */
6448         append_to_rev_graph(graph, symbol);
6450         if (graph->prev->size > graph->size)
6451                 filler = &fillers[RDIAG];
6452         else
6453                 filler = &fillers[DEFAULT];
6455         i++;
6457         for (; i < graph->size; i++) {
6458                 append_to_rev_graph(graph, filler->separator);
6459                 append_to_rev_graph(graph, filler->line);
6460                 if (graph_parent_is_merge(graph->prev) &&
6461                     i < graph->prev->pos + graph->parents->size)
6462                         filler = &fillers[RSHARP];
6463                 if (graph->prev->size > graph->size)
6464                         filler = &fillers[LDIAG];
6465         }
6467         if (graph->prev->size > graph->size) {
6468                 append_to_rev_graph(graph, filler->separator);
6469                 if (filler->line != ' ')
6470                         append_to_rev_graph(graph, filler->line);
6471         }
6474 /* Prepare the next rev graph */
6475 static void
6476 prepare_rev_graph(struct rev_graph *graph)
6478         size_t i;
6480         /* First, traverse all lines of revisions up to the active one. */
6481         for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
6482                 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
6483                         break;
6485                 push_rev_graph(graph->next, graph->rev[graph->pos]);
6486         }
6488         /* Interleave the new revision parent(s). */
6489         for (i = 0; !graph->boundary && i < graph->parents->size; i++)
6490                 push_rev_graph(graph->next, graph->parents->rev[i]);
6492         /* Lastly, put any remaining revisions. */
6493         for (i = graph->pos + 1; i < graph->size; i++)
6494                 push_rev_graph(graph->next, graph->rev[i]);
6497 static void
6498 update_rev_graph(struct view *view, struct rev_graph *graph)
6500         /* If this is the finalizing update ... */
6501         if (graph->commit)
6502                 prepare_rev_graph(graph);
6504         /* Graph visualization needs a one rev look-ahead,
6505          * so the first update doesn't visualize anything. */
6506         if (!graph->prev->commit)
6507                 return;
6509         if (view->lines > 2)
6510                 view->line[view->lines - 3].dirty = 1;
6511         if (view->lines > 1)
6512                 view->line[view->lines - 2].dirty = 1;
6513         draw_rev_graph(graph->prev);
6514         done_rev_graph(graph->prev->prev);
6518 /*
6519  * Main view backend
6520  */
6522 static const char *main_argv[SIZEOF_ARG] = {
6523         "git", "log", "--no-color", "--pretty=raw", "--parents",
6524                       "--topo-order", "%(head)", NULL
6525 };
6527 static bool
6528 main_draw(struct view *view, struct line *line, unsigned int lineno)
6530         struct commit *commit = line->data;
6532         if (!commit->author)
6533                 return FALSE;
6535         if (opt_date && draw_date(view, &commit->time))
6536                 return TRUE;
6538         if (opt_author && draw_author(view, commit->author))
6539                 return TRUE;
6541         if (opt_rev_graph && commit->graph_size &&
6542             draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
6543                 return TRUE;
6545         if (opt_show_refs && commit->refs) {
6546                 size_t i;
6548                 for (i = 0; i < commit->refs->size; i++) {
6549                         struct ref *ref = commit->refs->refs[i];
6550                         enum line_type type;
6552                         if (ref->head)
6553                                 type = LINE_MAIN_HEAD;
6554                         else if (ref->ltag)
6555                                 type = LINE_MAIN_LOCAL_TAG;
6556                         else if (ref->tag)
6557                                 type = LINE_MAIN_TAG;
6558                         else if (ref->tracked)
6559                                 type = LINE_MAIN_TRACKED;
6560                         else if (ref->remote)
6561                                 type = LINE_MAIN_REMOTE;
6562                         else
6563                                 type = LINE_MAIN_REF;
6565                         if (draw_text(view, type, "[", TRUE) ||
6566                             draw_text(view, type, ref->name, TRUE) ||
6567                             draw_text(view, type, "]", TRUE))
6568                                 return TRUE;
6570                         if (draw_text(view, LINE_DEFAULT, " ", TRUE))
6571                                 return TRUE;
6572                 }
6573         }
6575         draw_text(view, LINE_DEFAULT, commit->title, TRUE);
6576         return TRUE;
6579 /* Reads git log --pretty=raw output and parses it into the commit struct. */
6580 static bool
6581 main_read(struct view *view, char *line)
6583         static struct rev_graph *graph = graph_stacks;
6584         enum line_type type;
6585         struct commit *commit;
6587         if (!line) {
6588                 int i;
6590                 if (!view->lines && !view->parent)
6591                         die("No revisions match the given arguments.");
6592                 if (view->lines > 0) {
6593                         commit = view->line[view->lines - 1].data;
6594                         view->line[view->lines - 1].dirty = 1;
6595                         if (!commit->author) {
6596                                 view->lines--;
6597                                 free(commit);
6598                                 graph->commit = NULL;
6599                         }
6600                 }
6601                 update_rev_graph(view, graph);
6603                 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
6604                         clear_rev_graph(&graph_stacks[i]);
6605                 return TRUE;
6606         }
6608         type = get_line_type(line);
6609         if (type == LINE_COMMIT) {
6610                 commit = calloc(1, sizeof(struct commit));
6611                 if (!commit)
6612                         return FALSE;
6614                 line += STRING_SIZE("commit ");
6615                 if (*line == '-') {
6616                         graph->boundary = 1;
6617                         line++;
6618                 }
6620                 string_copy_rev(commit->id, line);
6621                 commit->refs = get_ref_list(commit->id);
6622                 graph->commit = commit;
6623                 add_line_data(view, commit, LINE_MAIN_COMMIT);
6625                 while ((line = strchr(line, ' '))) {
6626                         line++;
6627                         push_rev_graph(graph->parents, line);
6628                         commit->has_parents = TRUE;
6629                 }
6630                 return TRUE;
6631         }
6633         if (!view->lines)
6634                 return TRUE;
6635         commit = view->line[view->lines - 1].data;
6637         switch (type) {
6638         case LINE_PARENT:
6639                 if (commit->has_parents)
6640                         break;
6641                 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
6642                 break;
6644         case LINE_AUTHOR:
6645                 parse_author_line(line + STRING_SIZE("author "),
6646                                   &commit->author, &commit->time);
6647                 update_rev_graph(view, graph);
6648                 graph = graph->next;
6649                 break;
6651         default:
6652                 /* Fill in the commit title if it has not already been set. */
6653                 if (commit->title[0])
6654                         break;
6656                 /* Require titles to start with a non-space character at the
6657                  * offset used by git log. */
6658                 if (strncmp(line, "    ", 4))
6659                         break;
6660                 line += 4;
6661                 /* Well, if the title starts with a whitespace character,
6662                  * try to be forgiving.  Otherwise we end up with no title. */
6663                 while (isspace(*line))
6664                         line++;
6665                 if (*line == '\0')
6666                         break;
6667                 /* FIXME: More graceful handling of titles; append "..." to
6668                  * shortened titles, etc. */
6670                 string_expand(commit->title, sizeof(commit->title), line, 1);
6671                 view->line[view->lines - 1].dirty = 1;
6672         }
6674         return TRUE;
6677 static enum request
6678 main_request(struct view *view, enum request request, struct line *line)
6680         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
6682         switch (request) {
6683         case REQ_ENTER:
6684                 open_view(view, REQ_VIEW_DIFF, flags);
6685                 break;
6686         case REQ_REFRESH:
6687                 load_refs();
6688                 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
6689                 break;
6690         default:
6691                 return request;
6692         }
6694         return REQ_NONE;
6697 static bool
6698 grep_refs(struct ref_list *list, regex_t *regex)
6700         regmatch_t pmatch;
6701         size_t i;
6703         if (!opt_show_refs || !list)
6704                 return FALSE;
6706         for (i = 0; i < list->size; i++) {
6707                 if (regexec(regex, list->refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
6708                         return TRUE;
6709         }
6711         return FALSE;
6714 static bool
6715 main_grep(struct view *view, struct line *line)
6717         struct commit *commit = line->data;
6718         const char *text[] = {
6719                 commit->title,
6720                 opt_author ? commit->author : "",
6721                 opt_date ? mkdate(&commit->time) : "",
6722                 NULL
6723         };
6725         return grep_text(view, text) || grep_refs(commit->refs, view->regex);
6728 static void
6729 main_select(struct view *view, struct line *line)
6731         struct commit *commit = line->data;
6733         string_copy_rev(view->ref, commit->id);
6734         string_copy_rev(ref_commit, view->ref);
6737 static struct view_ops main_ops = {
6738         "commit",
6739         main_argv,
6740         NULL,
6741         main_read,
6742         main_draw,
6743         main_request,
6744         main_grep,
6745         main_select,
6746 };
6749 /*
6750  * Unicode / UTF-8 handling
6751  *
6752  * NOTE: Much of the following code for dealing with Unicode is derived from
6753  * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
6754  * src/intl/charset.c from the UTF-8 branch commit elinks-0.11.0-g31f2c28.
6755  */
6757 static inline int
6758 unicode_width(unsigned long c, int tab_size)
6760         if (c >= 0x1100 &&
6761            (c <= 0x115f                         /* Hangul Jamo */
6762             || c == 0x2329
6763             || c == 0x232a
6764             || (c >= 0x2e80  && c <= 0xa4cf && c != 0x303f)
6765                                                 /* CJK ... Yi */
6766             || (c >= 0xac00  && c <= 0xd7a3)    /* Hangul Syllables */
6767             || (c >= 0xf900  && c <= 0xfaff)    /* CJK Compatibility Ideographs */
6768             || (c >= 0xfe30  && c <= 0xfe6f)    /* CJK Compatibility Forms */
6769             || (c >= 0xff00  && c <= 0xff60)    /* Fullwidth Forms */
6770             || (c >= 0xffe0  && c <= 0xffe6)
6771             || (c >= 0x20000 && c <= 0x2fffd)
6772             || (c >= 0x30000 && c <= 0x3fffd)))
6773                 return 2;
6775         if (c == '\t')
6776                 return tab_size;
6778         return 1;
6781 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
6782  * Illegal bytes are set one. */
6783 static const unsigned char utf8_bytes[256] = {
6784         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,
6785         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,
6786         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,
6787         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,
6788         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,
6789         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,
6790         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,
6791         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,
6792 };
6794 static inline unsigned char
6795 utf8_char_length(const char *string, const char *end)
6797         int c = *(unsigned char *) string;
6799         return utf8_bytes[c];
6802 /* Decode UTF-8 multi-byte representation into a Unicode character. */
6803 static inline unsigned long
6804 utf8_to_unicode(const char *string, size_t length)
6806         unsigned long unicode;
6808         switch (length) {
6809         case 1:
6810                 unicode  =   string[0];
6811                 break;
6812         case 2:
6813                 unicode  =  (string[0] & 0x1f) << 6;
6814                 unicode +=  (string[1] & 0x3f);
6815                 break;
6816         case 3:
6817                 unicode  =  (string[0] & 0x0f) << 12;
6818                 unicode += ((string[1] & 0x3f) << 6);
6819                 unicode +=  (string[2] & 0x3f);
6820                 break;
6821         case 4:
6822                 unicode  =  (string[0] & 0x0f) << 18;
6823                 unicode += ((string[1] & 0x3f) << 12);
6824                 unicode += ((string[2] & 0x3f) << 6);
6825                 unicode +=  (string[3] & 0x3f);
6826                 break;
6827         case 5:
6828                 unicode  =  (string[0] & 0x0f) << 24;
6829                 unicode += ((string[1] & 0x3f) << 18);
6830                 unicode += ((string[2] & 0x3f) << 12);
6831                 unicode += ((string[3] & 0x3f) << 6);
6832                 unicode +=  (string[4] & 0x3f);
6833                 break;
6834         case 6:
6835                 unicode  =  (string[0] & 0x01) << 30;
6836                 unicode += ((string[1] & 0x3f) << 24);
6837                 unicode += ((string[2] & 0x3f) << 18);
6838                 unicode += ((string[3] & 0x3f) << 12);
6839                 unicode += ((string[4] & 0x3f) << 6);
6840                 unicode +=  (string[5] & 0x3f);
6841                 break;
6842         default:
6843                 die("Invalid Unicode length");
6844         }
6846         /* Invalid characters could return the special 0xfffd value but NUL
6847          * should be just as good. */
6848         return unicode > 0xffff ? 0 : unicode;
6851 /* Calculates how much of string can be shown within the given maximum width
6852  * and sets trimmed parameter to non-zero value if all of string could not be
6853  * shown. If the reserve flag is TRUE, it will reserve at least one
6854  * trailing character, which can be useful when drawing a delimiter.
6855  *
6856  * Returns the number of bytes to output from string to satisfy max_width. */
6857 static size_t
6858 utf8_length(const char **start, size_t skip, int *width, size_t max_width, int *trimmed, bool reserve, int tab_size)
6860         const char *string = *start;
6861         const char *end = strchr(string, '\0');
6862         unsigned char last_bytes = 0;
6863         size_t last_ucwidth = 0;
6865         *width = 0;
6866         *trimmed = 0;
6868         while (string < end) {
6869                 unsigned char bytes = utf8_char_length(string, end);
6870                 size_t ucwidth;
6871                 unsigned long unicode;
6873                 if (string + bytes > end)
6874                         break;
6876                 /* Change representation to figure out whether
6877                  * it is a single- or double-width character. */
6879                 unicode = utf8_to_unicode(string, bytes);
6880                 /* FIXME: Graceful handling of invalid Unicode character. */
6881                 if (!unicode)
6882                         break;
6884                 ucwidth = unicode_width(unicode, tab_size);
6885                 if (skip > 0) {
6886                         skip -= ucwidth <= skip ? ucwidth : skip;
6887                         *start += bytes;
6888                 }
6889                 *width  += ucwidth;
6890                 if (*width > max_width) {
6891                         *trimmed = 1;
6892                         *width -= ucwidth;
6893                         if (reserve && *width == max_width) {
6894                                 string -= last_bytes;
6895                                 *width -= last_ucwidth;
6896                         }
6897                         break;
6898                 }
6900                 string  += bytes;
6901                 last_bytes = ucwidth ? bytes : 0;
6902                 last_ucwidth = ucwidth;
6903         }
6905         return string - *start;
6909 /*
6910  * Status management
6911  */
6913 /* Whether or not the curses interface has been initialized. */
6914 static bool cursed = FALSE;
6916 /* Terminal hacks and workarounds. */
6917 static bool use_scroll_redrawwin;
6918 static bool use_scroll_status_wclear;
6920 /* The status window is used for polling keystrokes. */
6921 static WINDOW *status_win;
6923 /* Reading from the prompt? */
6924 static bool input_mode = FALSE;
6926 static bool status_empty = FALSE;
6928 /* Update status and title window. */
6929 static void
6930 report(const char *msg, ...)
6932         struct view *view = display[current_view];
6934         if (input_mode)
6935                 return;
6937         if (!view) {
6938                 char buf[SIZEOF_STR];
6939                 va_list args;
6941                 va_start(args, msg);
6942                 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
6943                         buf[sizeof(buf) - 1] = 0;
6944                         buf[sizeof(buf) - 2] = '.';
6945                         buf[sizeof(buf) - 3] = '.';
6946                         buf[sizeof(buf) - 4] = '.';
6947                 }
6948                 va_end(args);
6949                 die("%s", buf);
6950         }
6952         if (!status_empty || *msg) {
6953                 va_list args;
6955                 va_start(args, msg);
6957                 wmove(status_win, 0, 0);
6958                 if (view->has_scrolled && use_scroll_status_wclear)
6959                         wclear(status_win);
6960                 if (*msg) {
6961                         vwprintw(status_win, msg, args);
6962                         status_empty = FALSE;
6963                 } else {
6964                         status_empty = TRUE;
6965                 }
6966                 wclrtoeol(status_win);
6967                 wnoutrefresh(status_win);
6969                 va_end(args);
6970         }
6972         update_view_title(view);
6975 /* Controls when nodelay should be in effect when polling user input. */
6976 static void
6977 set_nonblocking_input(bool loading)
6979         static unsigned int loading_views;
6981         if ((loading == FALSE && loading_views-- == 1) ||
6982             (loading == TRUE  && loading_views++ == 0))
6983                 nodelay(status_win, loading);
6986 static void
6987 init_display(void)
6989         const char *term;
6990         int x, y;
6992         /* Initialize the curses library */
6993         if (isatty(STDIN_FILENO)) {
6994                 cursed = !!initscr();
6995                 opt_tty = stdin;
6996         } else {
6997                 /* Leave stdin and stdout alone when acting as a pager. */
6998                 opt_tty = fopen("/dev/tty", "r+");
6999                 if (!opt_tty)
7000                         die("Failed to open /dev/tty");
7001                 cursed = !!newterm(NULL, opt_tty, opt_tty);
7002         }
7004         if (!cursed)
7005                 die("Failed to initialize curses");
7007         nonl();         /* Disable conversion and detect newlines from input. */
7008         cbreak();       /* Take input chars one at a time, no wait for \n */
7009         noecho();       /* Don't echo input */
7010         leaveok(stdscr, FALSE);
7012         if (has_colors())
7013                 init_colors();
7015         getmaxyx(stdscr, y, x);
7016         status_win = newwin(1, 0, y - 1, 0);
7017         if (!status_win)
7018                 die("Failed to create status window");
7020         /* Enable keyboard mapping */
7021         keypad(status_win, TRUE);
7022         wbkgdset(status_win, get_line_attr(LINE_STATUS));
7024         TABSIZE = opt_tab_size;
7026         term = getenv("XTERM_VERSION") ? NULL : getenv("COLORTERM");
7027         if (term && !strcmp(term, "gnome-terminal")) {
7028                 /* In the gnome-terminal-emulator, the message from
7029                  * scrolling up one line when impossible followed by
7030                  * scrolling down one line causes corruption of the
7031                  * status line. This is fixed by calling wclear. */
7032                 use_scroll_status_wclear = TRUE;
7033                 use_scroll_redrawwin = FALSE;
7035         } else if (term && !strcmp(term, "xrvt-xpm")) {
7036                 /* No problems with full optimizations in xrvt-(unicode)
7037                  * and aterm. */
7038                 use_scroll_status_wclear = use_scroll_redrawwin = FALSE;
7040         } else {
7041                 /* When scrolling in (u)xterm the last line in the
7042                  * scrolling direction will update slowly. */
7043                 use_scroll_redrawwin = TRUE;
7044                 use_scroll_status_wclear = FALSE;
7045         }
7048 static int
7049 get_input(int prompt_position)
7051         struct view *view;
7052         int i, key, cursor_y, cursor_x;
7054         if (prompt_position)
7055                 input_mode = TRUE;
7057         while (TRUE) {
7058                 foreach_view (view, i) {
7059                         update_view(view);
7060                         if (view_is_displayed(view) && view->has_scrolled &&
7061                             use_scroll_redrawwin)
7062                                 redrawwin(view->win);
7063                         view->has_scrolled = FALSE;
7064                 }
7066                 /* Update the cursor position. */
7067                 if (prompt_position) {
7068                         getbegyx(status_win, cursor_y, cursor_x);
7069                         cursor_x = prompt_position;
7070                 } else {
7071                         view = display[current_view];
7072                         getbegyx(view->win, cursor_y, cursor_x);
7073                         cursor_x = view->width - 1;
7074                         cursor_y += view->lineno - view->offset;
7075                 }
7076                 setsyx(cursor_y, cursor_x);
7078                 /* Refresh, accept single keystroke of input */
7079                 doupdate();
7080                 key = wgetch(status_win);
7082                 /* wgetch() with nodelay() enabled returns ERR when
7083                  * there's no input. */
7084                 if (key == ERR) {
7086                 } else if (key == KEY_RESIZE) {
7087                         int height, width;
7089                         getmaxyx(stdscr, height, width);
7091                         wresize(status_win, 1, width);
7092                         mvwin(status_win, height - 1, 0);
7093                         wnoutrefresh(status_win);
7094                         resize_display();
7095                         redraw_display(TRUE);
7097                 } else {
7098                         input_mode = FALSE;
7099                         return key;
7100                 }
7101         }
7104 static char *
7105 prompt_input(const char *prompt, input_handler handler, void *data)
7107         enum input_status status = INPUT_OK;
7108         static char buf[SIZEOF_STR];
7109         size_t pos = 0;
7111         buf[pos] = 0;
7113         while (status == INPUT_OK || status == INPUT_SKIP) {
7114                 int key;
7116                 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
7117                 wclrtoeol(status_win);
7119                 key = get_input(pos + 1);
7120                 switch (key) {
7121                 case KEY_RETURN:
7122                 case KEY_ENTER:
7123                 case '\n':
7124                         status = pos ? INPUT_STOP : INPUT_CANCEL;
7125                         break;
7127                 case KEY_BACKSPACE:
7128                         if (pos > 0)
7129                                 buf[--pos] = 0;
7130                         else
7131                                 status = INPUT_CANCEL;
7132                         break;
7134                 case KEY_ESC:
7135                         status = INPUT_CANCEL;
7136                         break;
7138                 default:
7139                         if (pos >= sizeof(buf)) {
7140                                 report("Input string too long");
7141                                 return NULL;
7142                         }
7144                         status = handler(data, buf, key);
7145                         if (status == INPUT_OK)
7146                                 buf[pos++] = (char) key;
7147                 }
7148         }
7150         /* Clear the status window */
7151         status_empty = FALSE;
7152         report("");
7154         if (status == INPUT_CANCEL)
7155                 return NULL;
7157         buf[pos++] = 0;
7159         return buf;
7162 static enum input_status
7163 prompt_yesno_handler(void *data, char *buf, int c)
7165         if (c == 'y' || c == 'Y')
7166                 return INPUT_STOP;
7167         if (c == 'n' || c == 'N')
7168                 return INPUT_CANCEL;
7169         return INPUT_SKIP;
7172 static bool
7173 prompt_yesno(const char *prompt)
7175         char prompt2[SIZEOF_STR];
7177         if (!string_format(prompt2, "%s [Yy/Nn]", prompt))
7178                 return FALSE;
7180         return !!prompt_input(prompt2, prompt_yesno_handler, NULL);
7183 static enum input_status
7184 read_prompt_handler(void *data, char *buf, int c)
7186         return isprint(c) ? INPUT_OK : INPUT_SKIP;
7189 static char *
7190 read_prompt(const char *prompt)
7192         return prompt_input(prompt, read_prompt_handler, NULL);
7195 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected)
7197         enum input_status status = INPUT_OK;
7198         int size = 0;
7200         while (items[size].text)
7201                 size++;
7203         while (status == INPUT_OK) {
7204                 const struct menu_item *item = &items[*selected];
7205                 int key;
7206                 int i;
7208                 mvwprintw(status_win, 0, 0, "%s (%d of %d) ",
7209                           prompt, *selected + 1, size);
7210                 if (item->hotkey)
7211                         wprintw(status_win, "[%c] ", (char) item->hotkey);
7212                 wprintw(status_win, "%s", item->text);
7213                 wclrtoeol(status_win);
7215                 key = get_input(COLS - 1);
7216                 switch (key) {
7217                 case KEY_RETURN:
7218                 case KEY_ENTER:
7219                 case '\n':
7220                         status = INPUT_STOP;
7221                         break;
7223                 case KEY_LEFT:
7224                 case KEY_UP:
7225                         *selected = *selected - 1;
7226                         if (*selected < 0)
7227                                 *selected = size - 1;
7228                         break;
7230                 case KEY_RIGHT:
7231                 case KEY_DOWN:
7232                         *selected = (*selected + 1) % size;
7233                         break;
7235                 case KEY_ESC:
7236                         status = INPUT_CANCEL;
7237                         break;
7239                 default:
7240                         for (i = 0; items[i].text; i++)
7241                                 if (items[i].hotkey == key) {
7242                                         *selected = i;
7243                                         status = INPUT_STOP;
7244                                         break;
7245                                 }
7246                 }
7247         }
7249         /* Clear the status window */
7250         status_empty = FALSE;
7251         report("");
7253         return status != INPUT_CANCEL;
7256 /*
7257  * Repository properties
7258  */
7260 static struct ref **refs = NULL;
7261 static size_t refs_size = 0;
7262 static struct ref *refs_head = NULL;
7264 static struct ref_list **ref_lists = NULL;
7265 static size_t ref_lists_size = 0;
7267 DEFINE_ALLOCATOR(realloc_refs, struct ref *, 256)
7268 DEFINE_ALLOCATOR(realloc_refs_list, struct ref *, 8)
7269 DEFINE_ALLOCATOR(realloc_ref_lists, struct ref_list *, 8)
7271 static int
7272 compare_refs(const void *ref1_, const void *ref2_)
7274         const struct ref *ref1 = *(const struct ref **)ref1_;
7275         const struct ref *ref2 = *(const struct ref **)ref2_;
7277         if (ref1->tag != ref2->tag)
7278                 return ref2->tag - ref1->tag;
7279         if (ref1->ltag != ref2->ltag)
7280                 return ref2->ltag - ref2->ltag;
7281         if (ref1->head != ref2->head)
7282                 return ref2->head - ref1->head;
7283         if (ref1->tracked != ref2->tracked)
7284                 return ref2->tracked - ref1->tracked;
7285         if (ref1->remote != ref2->remote)
7286                 return ref2->remote - ref1->remote;
7287         return strcmp(ref1->name, ref2->name);
7290 static void
7291 foreach_ref(bool (*visitor)(void *data, const struct ref *ref), void *data)
7293         size_t i;
7295         for (i = 0; i < refs_size; i++)
7296                 if (!visitor(data, refs[i]))
7297                         break;
7300 static struct ref *
7301 get_ref_head()
7303         return refs_head;
7306 static struct ref_list *
7307 get_ref_list(const char *id)
7309         struct ref_list *list;
7310         size_t i;
7312         for (i = 0; i < ref_lists_size; i++)
7313                 if (!strcmp(id, ref_lists[i]->id))
7314                         return ref_lists[i];
7316         if (!realloc_ref_lists(&ref_lists, ref_lists_size, 1))
7317                 return NULL;
7318         list = calloc(1, sizeof(*list));
7319         if (!list)
7320                 return NULL;
7322         for (i = 0; i < refs_size; i++) {
7323                 if (!strcmp(id, refs[i]->id) &&
7324                     realloc_refs_list(&list->refs, list->size, 1))
7325                         list->refs[list->size++] = refs[i];
7326         }
7328         if (!list->refs) {
7329                 free(list);
7330                 return NULL;
7331         }
7333         qsort(list->refs, list->size, sizeof(*list->refs), compare_refs);
7334         ref_lists[ref_lists_size++] = list;
7335         return list;
7338 static int
7339 read_ref(char *id, size_t idlen, char *name, size_t namelen)
7341         struct ref *ref = NULL;
7342         bool tag = FALSE;
7343         bool ltag = FALSE;
7344         bool remote = FALSE;
7345         bool tracked = FALSE;
7346         bool head = FALSE;
7347         int from = 0, to = refs_size - 1;
7349         if (!prefixcmp(name, "refs/tags/")) {
7350                 if (!suffixcmp(name, namelen, "^{}")) {
7351                         namelen -= 3;
7352                         name[namelen] = 0;
7353                 } else {
7354                         ltag = TRUE;
7355                 }
7357                 tag = TRUE;
7358                 namelen -= STRING_SIZE("refs/tags/");
7359                 name    += STRING_SIZE("refs/tags/");
7361         } else if (!prefixcmp(name, "refs/remotes/")) {
7362                 remote = TRUE;
7363                 namelen -= STRING_SIZE("refs/remotes/");
7364                 name    += STRING_SIZE("refs/remotes/");
7365                 tracked  = !strcmp(opt_remote, name);
7367         } else if (!prefixcmp(name, "refs/heads/")) {
7368                 namelen -= STRING_SIZE("refs/heads/");
7369                 name    += STRING_SIZE("refs/heads/");
7370                 if (!strncmp(opt_head, name, namelen))
7371                         return OK;
7373         } else if (!strcmp(name, "HEAD")) {
7374                 head     = TRUE;
7375                 if (*opt_head) {
7376                         namelen  = strlen(opt_head);
7377                         name     = opt_head;
7378                 }
7379         }
7381         /* If we are reloading or it's an annotated tag, replace the
7382          * previous SHA1 with the resolved commit id; relies on the fact
7383          * git-ls-remote lists the commit id of an annotated tag right
7384          * before the commit id it points to. */
7385         while (from <= to) {
7386                 size_t pos = (to + from) / 2;
7387                 int cmp = strcmp(name, refs[pos]->name);
7389                 if (!cmp) {
7390                         ref = refs[pos];
7391                         break;
7392                 }
7394                 if (cmp < 0)
7395                         to = pos - 1;
7396                 else
7397                         from = pos + 1;
7398         }
7400         if (!ref) {
7401                 if (!realloc_refs(&refs, refs_size, 1))
7402                         return ERR;
7403                 ref = calloc(1, sizeof(*ref) + namelen);
7404                 if (!ref)
7405                         return ERR;
7406                 memmove(refs + from + 1, refs + from,
7407                         (refs_size - from) * sizeof(*refs));
7408                 refs[from] = ref;
7409                 strncpy(ref->name, name, namelen);
7410                 refs_size++;
7411         }
7413         ref->head = head;
7414         ref->tag = tag;
7415         ref->ltag = ltag;
7416         ref->remote = remote;
7417         ref->tracked = tracked;
7418         string_copy_rev(ref->id, id);
7420         if (head)
7421                 refs_head = ref;
7422         return OK;
7425 static int
7426 load_refs(void)
7428         const char *head_argv[] = {
7429                 "git", "symbolic-ref", "HEAD", NULL
7430         };
7431         static const char *ls_remote_argv[SIZEOF_ARG] = {
7432                 "git", "ls-remote", opt_git_dir, NULL
7433         };
7434         static bool init = FALSE;
7435         size_t i;
7437         if (!init) {
7438                 argv_from_env(ls_remote_argv, "TIG_LS_REMOTE");
7439                 init = TRUE;
7440         }
7442         if (!*opt_git_dir)
7443                 return OK;
7445         if (run_io_buf(head_argv, opt_head, sizeof(opt_head)) &&
7446             !prefixcmp(opt_head, "refs/heads/")) {
7447                 char *offset = opt_head + STRING_SIZE("refs/heads/");
7449                 memmove(opt_head, offset, strlen(offset) + 1);
7450         }
7452         refs_head = NULL;
7453         for (i = 0; i < refs_size; i++)
7454                 refs[i]->id[0] = 0;
7456         if (run_io_load(ls_remote_argv, "\t", read_ref) == ERR)
7457                 return ERR;
7459         /* Update the ref lists to reflect changes. */
7460         for (i = 0; i < ref_lists_size; i++) {
7461                 struct ref_list *list = ref_lists[i];
7462                 size_t old, new;
7464                 for (old = new = 0; old < list->size; old++)
7465                         if (!strcmp(list->id, list->refs[old]->id))
7466                                 list->refs[new++] = list->refs[old];
7467                 list->size = new;
7468         }
7470         return OK;
7473 static void
7474 set_remote_branch(const char *name, const char *value, size_t valuelen)
7476         if (!strcmp(name, ".remote")) {
7477                 string_ncopy(opt_remote, value, valuelen);
7479         } else if (*opt_remote && !strcmp(name, ".merge")) {
7480                 size_t from = strlen(opt_remote);
7482                 if (!prefixcmp(value, "refs/heads/"))
7483                         value += STRING_SIZE("refs/heads/");
7485                 if (!string_format_from(opt_remote, &from, "/%s", value))
7486                         opt_remote[0] = 0;
7487         }
7490 static void
7491 set_repo_config_option(char *name, char *value, int (*cmd)(int, const char **))
7493         const char *argv[SIZEOF_ARG] = { name, "=" };
7494         int argc = 1 + (cmd == option_set_command);
7495         int error = ERR;
7497         if (!argv_from_string(argv, &argc, value))
7498                 config_msg = "Too many option arguments";
7499         else
7500                 error = cmd(argc, argv);
7502         if (error == ERR)
7503                 warn("Option 'tig.%s': %s", name, config_msg);
7506 static bool
7507 set_environment_variable(const char *name, const char *value)
7509         size_t len = strlen(name) + 1 + strlen(value) + 1;
7510         char *env = malloc(len);
7512         if (env &&
7513             string_nformat(env, len, NULL, "%s=%s", name, value) &&
7514             putenv(env) == 0)
7515                 return TRUE;
7516         free(env);
7517         return FALSE;
7520 static void
7521 set_work_tree(const char *value)
7523         char cwd[SIZEOF_STR];
7525         if (!getcwd(cwd, sizeof(cwd)))
7526                 die("Failed to get cwd path: %s", strerror(errno));
7527         if (chdir(opt_git_dir) < 0)
7528                 die("Failed to chdir(%s): %s", strerror(errno));
7529         if (!getcwd(opt_git_dir, sizeof(opt_git_dir)))
7530                 die("Failed to get git path: %s", strerror(errno));
7531         if (chdir(cwd) < 0)
7532                 die("Failed to chdir(%s): %s", cwd, strerror(errno));
7533         if (chdir(value) < 0)
7534                 die("Failed to chdir(%s): %s", value, strerror(errno));
7535         if (!getcwd(cwd, sizeof(cwd)))
7536                 die("Failed to get cwd path: %s", strerror(errno));
7537         if (!set_environment_variable("GIT_WORK_TREE", cwd))
7538                 die("Failed to set GIT_WORK_TREE to '%s'", cwd);
7539         if (!set_environment_variable("GIT_DIR", opt_git_dir))
7540                 die("Failed to set GIT_DIR to '%s'", opt_git_dir);
7541         opt_is_inside_work_tree = TRUE;
7544 static int
7545 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
7547         if (!strcmp(name, "i18n.commitencoding"))
7548                 string_ncopy(opt_encoding, value, valuelen);
7550         else if (!strcmp(name, "core.editor"))
7551                 string_ncopy(opt_editor, value, valuelen);
7553         else if (!strcmp(name, "core.worktree"))
7554                 set_work_tree(value);
7556         else if (!prefixcmp(name, "tig.color."))
7557                 set_repo_config_option(name + 10, value, option_color_command);
7559         else if (!prefixcmp(name, "tig.bind."))
7560                 set_repo_config_option(name + 9, value, option_bind_command);
7562         else if (!prefixcmp(name, "tig."))
7563                 set_repo_config_option(name + 4, value, option_set_command);
7565         else if (*opt_head && !prefixcmp(name, "branch.") &&
7566                  !strncmp(name + 7, opt_head, strlen(opt_head)))
7567                 set_remote_branch(name + 7 + strlen(opt_head), value, valuelen);
7569         return OK;
7572 static int
7573 load_git_config(void)
7575         const char *config_list_argv[] = { "git", "config", "--list", NULL };
7577         return run_io_load(config_list_argv, "=", read_repo_config_option);
7580 static int
7581 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
7583         if (!opt_git_dir[0]) {
7584                 string_ncopy(opt_git_dir, name, namelen);
7586         } else if (opt_is_inside_work_tree == -1) {
7587                 /* This can be 3 different values depending on the
7588                  * version of git being used. If git-rev-parse does not
7589                  * understand --is-inside-work-tree it will simply echo
7590                  * the option else either "true" or "false" is printed.
7591                  * Default to true for the unknown case. */
7592                 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
7594         } else if (*name == '.') {
7595                 string_ncopy(opt_cdup, name, namelen);
7597         } else {
7598                 string_ncopy(opt_prefix, name, namelen);
7599         }
7601         return OK;
7604 static int
7605 load_repo_info(void)
7607         const char *rev_parse_argv[] = {
7608                 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
7609                         "--show-cdup", "--show-prefix", NULL
7610         };
7612         return run_io_load(rev_parse_argv, "=", read_repo_info);
7616 /*
7617  * Main
7618  */
7620 static const char usage[] =
7621 "tig " TIG_VERSION " (" __DATE__ ")\n"
7622 "\n"
7623 "Usage: tig        [options] [revs] [--] [paths]\n"
7624 "   or: tig show   [options] [revs] [--] [paths]\n"
7625 "   or: tig blame  [rev] path\n"
7626 "   or: tig status\n"
7627 "   or: tig <      [git command output]\n"
7628 "\n"
7629 "Options:\n"
7630 "  -v, --version   Show version and exit\n"
7631 "  -h, --help      Show help message and exit";
7633 static void __NORETURN
7634 quit(int sig)
7636         /* XXX: Restore tty modes and let the OS cleanup the rest! */
7637         if (cursed)
7638                 endwin();
7639         exit(0);
7642 static void __NORETURN
7643 die(const char *err, ...)
7645         va_list args;
7647         endwin();
7649         va_start(args, err);
7650         fputs("tig: ", stderr);
7651         vfprintf(stderr, err, args);
7652         fputs("\n", stderr);
7653         va_end(args);
7655         exit(1);
7658 static void
7659 warn(const char *msg, ...)
7661         va_list args;
7663         va_start(args, msg);
7664         fputs("tig warning: ", stderr);
7665         vfprintf(stderr, msg, args);
7666         fputs("\n", stderr);
7667         va_end(args);
7670 static enum request
7671 parse_options(int argc, const char *argv[])
7673         enum request request = REQ_VIEW_MAIN;
7674         const char *subcommand;
7675         bool seen_dashdash = FALSE;
7676         /* XXX: This is vulnerable to the user overriding options
7677          * required for the main view parser. */
7678         const char *custom_argv[SIZEOF_ARG] = {
7679                 "git", "log", "--no-color", "--pretty=raw", "--parents",
7680                         "--topo-order", NULL
7681         };
7682         int i, j = 6;
7684         if (!isatty(STDIN_FILENO)) {
7685                 io_open(&VIEW(REQ_VIEW_PAGER)->io, "");
7686                 return REQ_VIEW_PAGER;
7687         }
7689         if (argc <= 1)
7690                 return REQ_NONE;
7692         subcommand = argv[1];
7693         if (!strcmp(subcommand, "status")) {
7694                 if (argc > 2)
7695                         warn("ignoring arguments after `%s'", subcommand);
7696                 return REQ_VIEW_STATUS;
7698         } else if (!strcmp(subcommand, "blame")) {
7699                 if (argc <= 2 || argc > 4)
7700                         die("invalid number of options to blame\n\n%s", usage);
7702                 i = 2;
7703                 if (argc == 4) {
7704                         string_ncopy(opt_ref, argv[i], strlen(argv[i]));
7705                         i++;
7706                 }
7708                 string_ncopy(opt_file, argv[i], strlen(argv[i]));
7709                 return REQ_VIEW_BLAME;
7711         } else if (!strcmp(subcommand, "show")) {
7712                 request = REQ_VIEW_DIFF;
7714         } else {
7715                 subcommand = NULL;
7716         }
7718         if (subcommand) {
7719                 custom_argv[1] = subcommand;
7720                 j = 2;
7721         }
7723         for (i = 1 + !!subcommand; i < argc; i++) {
7724                 const char *opt = argv[i];
7726                 if (seen_dashdash || !strcmp(opt, "--")) {
7727                         seen_dashdash = TRUE;
7729                 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
7730                         printf("tig version %s\n", TIG_VERSION);
7731                         quit(0);
7733                 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
7734                         printf("%s\n", usage);
7735                         quit(0);
7736                 }
7738                 custom_argv[j++] = opt;
7739                 if (j >= ARRAY_SIZE(custom_argv))
7740                         die("command too long");
7741         }
7743         if (!prepare_update(VIEW(request), custom_argv, NULL, FORMAT_NONE))
7744                 die("Failed to format arguments");
7746         return request;
7749 int
7750 main(int argc, const char *argv[])
7752         const char *codeset = "UTF-8";
7753         enum request request = parse_options(argc, argv);
7754         struct view *view;
7755         size_t i;
7757         signal(SIGINT, quit);
7758         signal(SIGPIPE, SIG_IGN);
7760         if (setlocale(LC_ALL, "")) {
7761                 codeset = nl_langinfo(CODESET);
7762         }
7764         if (load_repo_info() == ERR)
7765                 die("Failed to load repo info.");
7767         if (load_options() == ERR)
7768                 die("Failed to load user config.");
7770         if (load_git_config() == ERR)
7771                 die("Failed to load repo config.");
7773         /* Require a git repository unless when running in pager mode. */
7774         if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
7775                 die("Not a git repository");
7777         if (*opt_encoding && strcmp(codeset, "UTF-8")) {
7778                 opt_iconv_in = iconv_open("UTF-8", opt_encoding);
7779                 if (opt_iconv_in == ICONV_NONE)
7780                         die("Failed to initialize character set conversion");
7781         }
7783         if (codeset && strcmp(codeset, "UTF-8")) {
7784                 opt_iconv_out = iconv_open(codeset, "UTF-8");
7785                 if (opt_iconv_out == ICONV_NONE)
7786                         die("Failed to initialize character set conversion");
7787         }
7789         if (load_refs() == ERR)
7790                 die("Failed to load refs.");
7792         foreach_view (view, i)
7793                 argv_from_env(view->ops->argv, view->cmd_env);
7795         init_display();
7797         if (request != REQ_NONE)
7798                 open_view(NULL, request, OPEN_PREPARED);
7799         request = request == REQ_NONE ? REQ_VIEW_MAIN : REQ_NONE;
7801         while (view_driver(display[current_view], request)) {
7802                 int key = get_input(0);
7804                 view = display[current_view];
7805                 request = get_keybinding(view->keymap, key);
7807                 /* Some low-level request handling. This keeps access to
7808                  * status_win restricted. */
7809                 switch (request) {
7810                 case REQ_PROMPT:
7811                 {
7812                         char *cmd = read_prompt(":");
7814                         if (cmd && isdigit(*cmd)) {
7815                                 int lineno = view->lineno + 1;
7817                                 if (parse_int(&lineno, cmd, 1, view->lines + 1) == OK) {
7818                                         select_view_line(view, lineno - 1);
7819                                         report("");
7820                                 } else {
7821                                         report("Unable to parse '%s' as a line number", cmd);
7822                                 }
7824                         } else if (cmd) {
7825                                 struct view *next = VIEW(REQ_VIEW_PAGER);
7826                                 const char *argv[SIZEOF_ARG] = { "git" };
7827                                 int argc = 1;
7829                                 /* When running random commands, initially show the
7830                                  * command in the title. However, it maybe later be
7831                                  * overwritten if a commit line is selected. */
7832                                 string_ncopy(next->ref, cmd, strlen(cmd));
7834                                 if (!argv_from_string(argv, &argc, cmd)) {
7835                                         report("Too many arguments");
7836                                 } else if (!prepare_update(next, argv, NULL, FORMAT_DASH)) {
7837                                         report("Failed to format command");
7838                                 } else {
7839                                         open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
7840                                 }
7841                         }
7843                         request = REQ_NONE;
7844                         break;
7845                 }
7846                 case REQ_SEARCH:
7847                 case REQ_SEARCH_BACK:
7848                 {
7849                         const char *prompt = request == REQ_SEARCH ? "/" : "?";
7850                         char *search = read_prompt(prompt);
7852                         if (search)
7853                                 string_ncopy(opt_search, search, strlen(search));
7854                         else if (*opt_search)
7855                                 request = request == REQ_SEARCH ?
7856                                         REQ_FIND_NEXT :
7857                                         REQ_FIND_PREV;
7858                         else
7859                                 request = REQ_NONE;
7860                         break;
7861                 }
7862                 default:
7863                         break;
7864                 }
7865         }
7867         quit(0);
7869         return 0;