Code

Remove line_graphic enum
[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 **string, size_t col, int *width, size_t max_width, int *trimmed, bool reserve);
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_list *get_ref_list(const char *id);
142 static void foreach_ref(bool (*visitor)(void *data, const struct ref *ref), void *data);
143 static int load_refs(void);
145 enum format_flags {
146         FORMAT_ALL,             /* Perform replacement in all arguments. */
147         FORMAT_DASH,            /* Perform replacement up until "--". */
148         FORMAT_NONE             /* No replacement should be performed. */
149 };
151 static bool format_argv(const char *dst[], const char *src[], enum format_flags flags);
153 enum input_status {
154         INPUT_OK,
155         INPUT_SKIP,
156         INPUT_STOP,
157         INPUT_CANCEL
158 };
160 typedef enum input_status (*input_handler)(void *data, char *buf, int c);
162 static char *prompt_input(const char *prompt, input_handler handler, void *data);
163 static bool prompt_yesno(const char *prompt);
165 struct menu_item {
166         int hotkey;
167         const char *text;
168         void *data;
169 };
171 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected);
173 /*
174  * Allocation helpers ... Entering macro hell to never be seen again.
175  */
177 #define DEFINE_ALLOCATOR(name, type, chunk_size)                                \
178 static type *                                                                   \
179 name(type **mem, size_t size, size_t increase)                                  \
180 {                                                                               \
181         size_t num_chunks = (size + chunk_size - 1) / chunk_size;               \
182         size_t num_chunks_new = (size + increase + chunk_size - 1) / chunk_size;\
183         type *tmp = *mem;                                                       \
184                                                                                 \
185         if (mem == NULL || num_chunks != num_chunks_new) {                      \
186                 tmp = realloc(tmp, num_chunks_new * chunk_size * sizeof(type)); \
187                 if (tmp)                                                        \
188                         *mem = tmp;                                             \
189         }                                                                       \
190                                                                                 \
191         return tmp;                                                             \
194 /*
195  * String helpers
196  */
198 static inline void
199 string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t srclen)
201         if (srclen > dstlen - 1)
202                 srclen = dstlen - 1;
204         strncpy(dst, src, srclen);
205         dst[srclen] = 0;
208 /* Shorthands for safely copying into a fixed buffer. */
210 #define string_copy(dst, src) \
211         string_ncopy_do(dst, sizeof(dst), src, sizeof(src))
213 #define string_ncopy(dst, src, srclen) \
214         string_ncopy_do(dst, sizeof(dst), src, srclen)
216 #define string_copy_rev(dst, src) \
217         string_ncopy_do(dst, SIZEOF_REV, src, SIZEOF_REV - 1)
219 #define string_add(dst, from, src) \
220         string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src))
222 static void
223 string_expand(char *dst, size_t dstlen, const char *src, int tabsize)
225         size_t size, pos;
227         for (size = pos = 0; size < dstlen - 1 && src[pos]; pos++) {
228                 if (src[pos] == '\t') {
229                         size_t expanded = tabsize - (size % tabsize);
231                         if (expanded + size >= dstlen - 1)
232                                 expanded = dstlen - size - 1;
233                         memcpy(dst + size, "        ", expanded);
234                         size += expanded;
235                 } else {
236                         dst[size++] = src[pos];
237                 }
238         }
240         dst[size] = 0;
243 static char *
244 chomp_string(char *name)
246         int namelen;
248         while (isspace(*name))
249                 name++;
251         namelen = strlen(name) - 1;
252         while (namelen > 0 && isspace(name[namelen]))
253                 name[namelen--] = 0;
255         return name;
258 static bool
259 string_nformat(char *buf, size_t bufsize, size_t *bufpos, const char *fmt, ...)
261         va_list args;
262         size_t pos = bufpos ? *bufpos : 0;
264         va_start(args, fmt);
265         pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
266         va_end(args);
268         if (bufpos)
269                 *bufpos = pos;
271         return pos >= bufsize ? FALSE : TRUE;
274 #define string_format(buf, fmt, args...) \
275         string_nformat(buf, sizeof(buf), NULL, fmt, args)
277 #define string_format_from(buf, from, fmt, args...) \
278         string_nformat(buf, sizeof(buf), from, fmt, args)
280 static int
281 string_enum_compare(const char *str1, const char *str2, int len)
283         size_t i;
285 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
287         /* Diff-Header == DIFF_HEADER */
288         for (i = 0; i < len; i++) {
289                 if (toupper(str1[i]) == toupper(str2[i]))
290                         continue;
292                 if (string_enum_sep(str1[i]) &&
293                     string_enum_sep(str2[i]))
294                         continue;
296                 return str1[i] - str2[i];
297         }
299         return 0;
302 #define enum_equals(entry, str, len) \
303         ((entry).namelen == (len) && !string_enum_compare((entry).name, str, len))
305 struct enum_map {
306         const char *name;
307         int namelen;
308         int value;
309 };
311 #define ENUM_MAP(name, value) { name, STRING_SIZE(name), value }
313 static char *
314 enum_map_name(const char *name, size_t namelen)
316         static char buf[SIZEOF_STR];
317         int bufpos;
319         for (bufpos = 0; bufpos <= namelen; bufpos++) {
320                 buf[bufpos] = tolower(name[bufpos]);
321                 if (buf[bufpos] == '_')
322                         buf[bufpos] = '-';
323         }
325         buf[bufpos] = 0;
326         return buf;
329 #define enum_name(entry) enum_map_name((entry).name, (entry).namelen)
331 static bool
332 map_enum_do(const struct enum_map *map, size_t map_size, int *value, const char *name)
334         size_t namelen = strlen(name);
335         int i;
337         for (i = 0; i < map_size; i++)
338                 if (enum_equals(map[i], name, namelen)) {
339                         *value = map[i].value;
340                         return TRUE;
341                 }
343         return FALSE;
346 #define map_enum(attr, map, name) \
347         map_enum_do(map, ARRAY_SIZE(map), attr, name)
349 #define prefixcmp(str1, str2) \
350         strncmp(str1, str2, STRING_SIZE(str2))
352 static inline int
353 suffixcmp(const char *str, int slen, const char *suffix)
355         size_t len = slen >= 0 ? slen : strlen(str);
356         size_t suffixlen = strlen(suffix);
358         return suffixlen < len ? strcmp(str + len - suffixlen, suffix) : -1;
362 #define DATE_INFO \
363         DATE_(NO), \
364         DATE_(DEFAULT), \
365         DATE_(RELATIVE), \
366         DATE_(SHORT)
368 enum date {
369 #define DATE_(name) DATE_##name
370         DATE_INFO
371 #undef  DATE_
372 };
374 static const struct enum_map date_map[] = {
375 #define DATE_(name) ENUM_MAP(#name, DATE_##name)
376         DATE_INFO
377 #undef  DATE_
378 };
380 struct time {
381         time_t sec;
382         int tz;
383 };
385 static inline int timecmp(const struct time *t1, const struct time *t2)
387         return t1->sec - t2->sec;
390 static const char *
391 string_date(const struct time *time, enum date date)
393         static char buf[DATE_COLS + 1];
394         static const struct enum_map reldate[] = {
395                 { "second", 1,                  60 * 2 },
396                 { "minute", 60,                 60 * 60 * 2 },
397                 { "hour",   60 * 60,            60 * 60 * 24 * 2 },
398                 { "day",    60 * 60 * 24,       60 * 60 * 24 * 7 * 2 },
399                 { "week",   60 * 60 * 24 * 7,   60 * 60 * 24 * 7 * 5 },
400                 { "month",  60 * 60 * 24 * 30,  60 * 60 * 24 * 30 * 12 },
401         };
402         struct tm tm;
404         if (date == DATE_RELATIVE) {
405                 struct timeval now;
406                 time_t date = time->sec + time->tz;
407                 time_t seconds;
408                 int i;
410                 gettimeofday(&now, NULL);
411                 seconds = now.tv_sec < date ? date - now.tv_sec : now.tv_sec - date;
412                 for (i = 0; i < ARRAY_SIZE(reldate); i++) {
413                         if (seconds >= reldate[i].value)
414                                 continue;
416                         seconds /= reldate[i].namelen;
417                         if (!string_format(buf, "%ld %s%s %s",
418                                            seconds, reldate[i].name,
419                                            seconds > 1 ? "s" : "",
420                                            now.tv_sec >= date ? "ago" : "ahead"))
421                                 break;
422                         return buf;
423                 }
424         }
426         gmtime_r(&time->sec, &tm);
427         return strftime(buf, sizeof(buf), DATE_FORMAT, &tm) ? buf : NULL;
431 #define AUTHOR_VALUES \
432         AUTHOR_(NO), \
433         AUTHOR_(FULL), \
434         AUTHOR_(ABBREVIATED)
436 enum author {
437 #define AUTHOR_(name) AUTHOR_##name
438         AUTHOR_VALUES,
439 #undef  AUTHOR_
440         AUTHOR_DEFAULT = AUTHOR_FULL
441 };
443 static const struct enum_map author_map[] = {
444 #define AUTHOR_(name) ENUM_MAP(#name, AUTHOR_##name)
445         AUTHOR_VALUES
446 #undef  AUTHOR_
447 };
449 static const char *
450 get_author_initials(const char *author)
452         static char initials[AUTHOR_COLS * 6 + 1];
453         size_t pos = 0;
454         const char *end = strchr(author, '\0');
456 #define is_initial_sep(c) (isspace(c) || ispunct(c) || (c) == '@' || (c) == '-')
458         memset(initials, 0, sizeof(initials));
459         while (author < end) {
460                 unsigned char bytes;
461                 size_t i;
463                 while (is_initial_sep(*author))
464                         author++;
466                 bytes = utf8_char_length(author, end);
467                 if (bytes < sizeof(initials) - 1 - pos) {
468                         while (bytes--) {
469                                 initials[pos++] = *author++;
470                         }
471                 }
473                 for (i = pos; author < end && !is_initial_sep(*author); author++) {
474                         if (i < sizeof(initials) - 1)
475                                 initials[i++] = *author;
476                 }
478                 initials[i++] = 0;
479         }
481         return initials;
485 static bool
486 argv_from_string(const char *argv[SIZEOF_ARG], int *argc, char *cmd)
488         int valuelen;
490         while (*cmd && *argc < SIZEOF_ARG && (valuelen = strcspn(cmd, " \t"))) {
491                 bool advance = cmd[valuelen] != 0;
493                 cmd[valuelen] = 0;
494                 argv[(*argc)++] = chomp_string(cmd);
495                 cmd = chomp_string(cmd + valuelen + advance);
496         }
498         if (*argc < SIZEOF_ARG)
499                 argv[*argc] = NULL;
500         return *argc < SIZEOF_ARG;
503 static void
504 argv_from_env(const char **argv, const char *name)
506         char *env = argv ? getenv(name) : NULL;
507         int argc = 0;
509         if (env && *env)
510                 env = strdup(env);
511         if (env && !argv_from_string(argv, &argc, env))
512                 die("Too many arguments in the `%s` environment variable", name);
516 /*
517  * Executing external commands.
518  */
520 enum io_type {
521         IO_FD,                  /* File descriptor based IO. */
522         IO_BG,                  /* Execute command in the background. */
523         IO_FG,                  /* Execute command with same std{in,out,err}. */
524         IO_RD,                  /* Read only fork+exec IO. */
525         IO_WR,                  /* Write only fork+exec IO. */
526         IO_AP,                  /* Append fork+exec output to file. */
527 };
529 struct io {
530         enum io_type type;      /* The requested type of pipe. */
531         const char *dir;        /* Directory from which to execute. */
532         pid_t pid;              /* Pipe for reading or writing. */
533         int pipe;               /* Pipe end for reading or writing. */
534         int error;              /* Error status. */
535         const char *argv[SIZEOF_ARG];   /* Shell command arguments. */
536         char *buf;              /* Read buffer. */
537         size_t bufalloc;        /* Allocated buffer size. */
538         size_t bufsize;         /* Buffer content size. */
539         char *bufpos;           /* Current buffer position. */
540         unsigned int eof:1;     /* Has end of file been reached. */
541 };
543 static void
544 reset_io(struct io *io)
546         io->pipe = -1;
547         io->pid = 0;
548         io->buf = io->bufpos = NULL;
549         io->bufalloc = io->bufsize = 0;
550         io->error = 0;
551         io->eof = 0;
554 static void
555 init_io(struct io *io, const char *dir, enum io_type type)
557         reset_io(io);
558         io->type = type;
559         io->dir = dir;
562 static bool
563 init_io_rd(struct io *io, const char *argv[], const char *dir,
564                 enum format_flags flags)
566         init_io(io, dir, IO_RD);
567         return format_argv(io->argv, argv, flags);
570 static bool
571 io_open(struct io *io, const char *fmt, ...)
573         char name[SIZEOF_STR] = "";
574         bool fits;
575         va_list args;
577         init_io(io, NULL, IO_FD);
579         va_start(args, fmt);
580         fits = vsnprintf(name, sizeof(name), fmt, args) < sizeof(name);
581         va_end(args);
583         if (!fits) {
584                 io->error = ENAMETOOLONG;
585                 return FALSE;
586         }
587         io->pipe = *name ? open(name, O_RDONLY) : STDIN_FILENO;
588         if (io->pipe == -1)
589                 io->error = errno;
590         return io->pipe != -1;
593 static bool
594 kill_io(struct io *io)
596         return io->pid == 0 || kill(io->pid, SIGKILL) != -1;
599 static bool
600 done_io(struct io *io)
602         pid_t pid = io->pid;
604         if (io->pipe != -1)
605                 close(io->pipe);
606         free(io->buf);
607         reset_io(io);
609         while (pid > 0) {
610                 int status;
611                 pid_t waiting = waitpid(pid, &status, 0);
613                 if (waiting < 0) {
614                         if (errno == EINTR)
615                                 continue;
616                         report("waitpid failed (%s)", strerror(errno));
617                         return FALSE;
618                 }
620                 return waiting == pid &&
621                        !WIFSIGNALED(status) &&
622                        WIFEXITED(status) &&
623                        !WEXITSTATUS(status);
624         }
626         return TRUE;
629 static bool
630 start_io(struct io *io)
632         int pipefds[2] = { -1, -1 };
634         if (io->type == IO_FD)
635                 return TRUE;
637         if ((io->type == IO_RD || io->type == IO_WR) &&
638             pipe(pipefds) < 0)
639                 return FALSE;
640         else if (io->type == IO_AP)
641                 pipefds[1] = io->pipe;
643         if ((io->pid = fork())) {
644                 if (pipefds[!(io->type == IO_WR)] != -1)
645                         close(pipefds[!(io->type == IO_WR)]);
646                 if (io->pid != -1) {
647                         io->pipe = pipefds[!!(io->type == IO_WR)];
648                         return TRUE;
649                 }
651         } else {
652                 if (io->type != IO_FG) {
653                         int devnull = open("/dev/null", O_RDWR);
654                         int readfd  = io->type == IO_WR ? pipefds[0] : devnull;
655                         int writefd = (io->type == IO_RD || io->type == IO_AP)
656                                                         ? pipefds[1] : devnull;
658                         dup2(readfd,  STDIN_FILENO);
659                         dup2(writefd, STDOUT_FILENO);
660                         dup2(devnull, STDERR_FILENO);
662                         close(devnull);
663                         if (pipefds[0] != -1)
664                                 close(pipefds[0]);
665                         if (pipefds[1] != -1)
666                                 close(pipefds[1]);
667                 }
669                 if (io->dir && *io->dir && chdir(io->dir) == -1)
670                         die("Failed to change directory: %s", strerror(errno));
672                 execvp(io->argv[0], (char *const*) io->argv);
673                 die("Failed to execute program: %s", strerror(errno));
674         }
676         if (pipefds[!!(io->type == IO_WR)] != -1)
677                 close(pipefds[!!(io->type == IO_WR)]);
678         return FALSE;
681 static bool
682 run_io(struct io *io, const char **argv, const char *dir, enum io_type type)
684         init_io(io, dir, type);
685         if (!format_argv(io->argv, argv, FORMAT_NONE))
686                 return FALSE;
687         return start_io(io);
690 static int
691 run_io_do(struct io *io)
693         return start_io(io) && done_io(io);
696 static int
697 run_io_bg(const char **argv)
699         struct io io = {};
701         init_io(&io, NULL, IO_BG);
702         if (!format_argv(io.argv, argv, FORMAT_NONE))
703                 return FALSE;
704         return run_io_do(&io);
707 static bool
708 run_io_fg(const char **argv, const char *dir)
710         struct io io = {};
712         init_io(&io, dir, IO_FG);
713         if (!format_argv(io.argv, argv, FORMAT_NONE))
714                 return FALSE;
715         return run_io_do(&io);
718 static bool
719 run_io_append(const char **argv, enum format_flags flags, int fd)
721         struct io io = {};
723         init_io(&io, NULL, IO_AP);
724         io.pipe = fd;
725         if (format_argv(io.argv, argv, flags))
726                 return run_io_do(&io);
727         close(fd);
728         return FALSE;
731 static bool
732 run_io_rd(struct io *io, const char **argv, const char *dir, enum format_flags flags)
734         return init_io_rd(io, argv, dir, flags) && start_io(io);
737 static bool
738 io_eof(struct io *io)
740         return io->eof;
743 static int
744 io_error(struct io *io)
746         return io->error;
749 static char *
750 io_strerror(struct io *io)
752         return strerror(io->error);
755 static bool
756 io_can_read(struct io *io)
758         struct timeval tv = { 0, 500 };
759         fd_set fds;
761         FD_ZERO(&fds);
762         FD_SET(io->pipe, &fds);
764         return select(io->pipe + 1, &fds, NULL, NULL, &tv) > 0;
767 static ssize_t
768 io_read(struct io *io, void *buf, size_t bufsize)
770         do {
771                 ssize_t readsize = read(io->pipe, buf, bufsize);
773                 if (readsize < 0 && (errno == EAGAIN || errno == EINTR))
774                         continue;
775                 else if (readsize == -1)
776                         io->error = errno;
777                 else if (readsize == 0)
778                         io->eof = 1;
779                 return readsize;
780         } while (1);
783 DEFINE_ALLOCATOR(realloc_io_buf, char, BUFSIZ)
785 static char *
786 io_get(struct io *io, int c, bool can_read)
788         char *eol;
789         ssize_t readsize;
791         while (TRUE) {
792                 if (io->bufsize > 0) {
793                         eol = memchr(io->bufpos, c, io->bufsize);
794                         if (eol) {
795                                 char *line = io->bufpos;
797                                 *eol = 0;
798                                 io->bufpos = eol + 1;
799                                 io->bufsize -= io->bufpos - line;
800                                 return line;
801                         }
802                 }
804                 if (io_eof(io)) {
805                         if (io->bufsize) {
806                                 io->bufpos[io->bufsize] = 0;
807                                 io->bufsize = 0;
808                                 return io->bufpos;
809                         }
810                         return NULL;
811                 }
813                 if (!can_read)
814                         return NULL;
816                 if (io->bufsize > 0 && io->bufpos > io->buf)
817                         memmove(io->buf, io->bufpos, io->bufsize);
819                 if (io->bufalloc == io->bufsize) {
820                         if (!realloc_io_buf(&io->buf, io->bufalloc, BUFSIZ))
821                                 return NULL;
822                         io->bufalloc += BUFSIZ;
823                 }
825                 io->bufpos = io->buf;
826                 readsize = io_read(io, io->buf + io->bufsize, io->bufalloc - io->bufsize);
827                 if (io_error(io))
828                         return NULL;
829                 io->bufsize += readsize;
830         }
833 static bool
834 io_write(struct io *io, const void *buf, size_t bufsize)
836         size_t written = 0;
838         while (!io_error(io) && written < bufsize) {
839                 ssize_t size;
841                 size = write(io->pipe, buf + written, bufsize - written);
842                 if (size < 0 && (errno == EAGAIN || errno == EINTR))
843                         continue;
844                 else if (size == -1)
845                         io->error = errno;
846                 else
847                         written += size;
848         }
850         return written == bufsize;
853 static bool
854 io_read_buf(struct io *io, char buf[], size_t bufsize)
856         char *result = io_get(io, '\n', TRUE);
858         if (result) {
859                 result = chomp_string(result);
860                 string_ncopy_do(buf, bufsize, result, strlen(result));
861         }
863         return done_io(io) && result;
866 static bool
867 run_io_buf(const char **argv, char buf[], size_t bufsize)
869         struct io io = {};
871         return run_io_rd(&io, argv, NULL, FORMAT_NONE)
872             && io_read_buf(&io, buf, bufsize);
875 static int
876 io_load(struct io *io, const char *separators,
877         int (*read_property)(char *, size_t, char *, size_t))
879         char *name;
880         int state = OK;
882         if (!start_io(io))
883                 return ERR;
885         while (state == OK && (name = io_get(io, '\n', TRUE))) {
886                 char *value;
887                 size_t namelen;
888                 size_t valuelen;
890                 name = chomp_string(name);
891                 namelen = strcspn(name, separators);
893                 if (name[namelen]) {
894                         name[namelen] = 0;
895                         value = chomp_string(name + namelen + 1);
896                         valuelen = strlen(value);
898                 } else {
899                         value = "";
900                         valuelen = 0;
901                 }
903                 state = read_property(name, namelen, value, valuelen);
904         }
906         if (state != ERR && io_error(io))
907                 state = ERR;
908         done_io(io);
910         return state;
913 static int
914 run_io_load(const char **argv, const char *separators,
915             int (*read_property)(char *, size_t, char *, size_t))
917         struct io io = {};
919         return init_io_rd(&io, argv, NULL, FORMAT_NONE)
920                 ? io_load(&io, separators, read_property) : ERR;
924 /*
925  * User requests
926  */
928 #define REQ_INFO \
929         /* XXX: Keep the view request first and in sync with views[]. */ \
930         REQ_GROUP("View switching") \
931         REQ_(VIEW_MAIN,         "Show main view"), \
932         REQ_(VIEW_DIFF,         "Show diff view"), \
933         REQ_(VIEW_LOG,          "Show log view"), \
934         REQ_(VIEW_TREE,         "Show tree view"), \
935         REQ_(VIEW_BLOB,         "Show blob view"), \
936         REQ_(VIEW_BLAME,        "Show blame view"), \
937         REQ_(VIEW_BRANCH,       "Show branch view"), \
938         REQ_(VIEW_HELP,         "Show help page"), \
939         REQ_(VIEW_PAGER,        "Show pager view"), \
940         REQ_(VIEW_STATUS,       "Show status view"), \
941         REQ_(VIEW_STAGE,        "Show stage view"), \
942         \
943         REQ_GROUP("View manipulation") \
944         REQ_(ENTER,             "Enter current line and scroll"), \
945         REQ_(NEXT,              "Move to next"), \
946         REQ_(PREVIOUS,          "Move to previous"), \
947         REQ_(PARENT,            "Move to parent"), \
948         REQ_(VIEW_NEXT,         "Move focus to next view"), \
949         REQ_(REFRESH,           "Reload and refresh"), \
950         REQ_(MAXIMIZE,          "Maximize the current view"), \
951         REQ_(VIEW_CLOSE,        "Close the current view"), \
952         REQ_(QUIT,              "Close all views and quit"), \
953         \
954         REQ_GROUP("View specific requests") \
955         REQ_(STATUS_UPDATE,     "Update file status"), \
956         REQ_(STATUS_REVERT,     "Revert file changes"), \
957         REQ_(STATUS_MERGE,      "Merge file using external tool"), \
958         REQ_(STAGE_NEXT,        "Find next chunk to stage"), \
959         \
960         REQ_GROUP("Cursor navigation") \
961         REQ_(MOVE_UP,           "Move cursor one line up"), \
962         REQ_(MOVE_DOWN,         "Move cursor one line down"), \
963         REQ_(MOVE_PAGE_DOWN,    "Move cursor one page down"), \
964         REQ_(MOVE_PAGE_UP,      "Move cursor one page up"), \
965         REQ_(MOVE_FIRST_LINE,   "Move cursor to first line"), \
966         REQ_(MOVE_LAST_LINE,    "Move cursor to last line"), \
967         \
968         REQ_GROUP("Scrolling") \
969         REQ_(SCROLL_LEFT,       "Scroll two columns left"), \
970         REQ_(SCROLL_RIGHT,      "Scroll two columns right"), \
971         REQ_(SCROLL_LINE_UP,    "Scroll one line up"), \
972         REQ_(SCROLL_LINE_DOWN,  "Scroll one line down"), \
973         REQ_(SCROLL_PAGE_UP,    "Scroll one page up"), \
974         REQ_(SCROLL_PAGE_DOWN,  "Scroll one page down"), \
975         \
976         REQ_GROUP("Searching") \
977         REQ_(SEARCH,            "Search the view"), \
978         REQ_(SEARCH_BACK,       "Search backwards in the view"), \
979         REQ_(FIND_NEXT,         "Find next search match"), \
980         REQ_(FIND_PREV,         "Find previous search match"), \
981         \
982         REQ_GROUP("Option manipulation") \
983         REQ_(OPTIONS,           "Open option menu"), \
984         REQ_(TOGGLE_LINENO,     "Toggle line numbers"), \
985         REQ_(TOGGLE_DATE,       "Toggle date display"), \
986         REQ_(TOGGLE_DATE_SHORT, "Toggle short (date-only) dates"), \
987         REQ_(TOGGLE_AUTHOR,     "Toggle author display"), \
988         REQ_(TOGGLE_REV_GRAPH,  "Toggle revision graph visualization"), \
989         REQ_(TOGGLE_REFS,       "Toggle reference display (tags/branches)"), \
990         REQ_(TOGGLE_SORT_ORDER, "Toggle ascending/descending sort order"), \
991         REQ_(TOGGLE_SORT_FIELD, "Toggle field to sort by"), \
992         \
993         REQ_GROUP("Misc") \
994         REQ_(PROMPT,            "Bring up the prompt"), \
995         REQ_(SCREEN_REDRAW,     "Redraw the screen"), \
996         REQ_(SHOW_VERSION,      "Show version information"), \
997         REQ_(STOP_LOADING,      "Stop all loading views"), \
998         REQ_(EDIT,              "Open in editor"), \
999         REQ_(NONE,              "Do nothing")
1002 /* User action requests. */
1003 enum request {
1004 #define REQ_GROUP(help)
1005 #define REQ_(req, help) REQ_##req
1007         /* Offset all requests to avoid conflicts with ncurses getch values. */
1008         REQ_OFFSET = KEY_MAX + 1,
1009         REQ_INFO
1011 #undef  REQ_GROUP
1012 #undef  REQ_
1013 };
1015 struct request_info {
1016         enum request request;
1017         const char *name;
1018         int namelen;
1019         const char *help;
1020 };
1022 static const struct request_info req_info[] = {
1023 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
1024 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
1025         REQ_INFO
1026 #undef  REQ_GROUP
1027 #undef  REQ_
1028 };
1030 static enum request
1031 get_request(const char *name)
1033         int namelen = strlen(name);
1034         int i;
1036         for (i = 0; i < ARRAY_SIZE(req_info); i++)
1037                 if (enum_equals(req_info[i], name, namelen))
1038                         return req_info[i].request;
1040         return REQ_NONE;
1044 /*
1045  * Options
1046  */
1048 /* Option and state variables. */
1049 static enum date opt_date               = DATE_DEFAULT;
1050 static enum author opt_author           = AUTHOR_DEFAULT;
1051 static bool opt_line_number             = FALSE;
1052 static bool opt_line_graphics           = TRUE;
1053 static bool opt_rev_graph               = FALSE;
1054 static bool opt_show_refs               = TRUE;
1055 static int opt_num_interval             = 5;
1056 static double opt_hscroll               = 0.50;
1057 static double opt_scale_split_view      = 2.0 / 3.0;
1058 static int opt_tab_size                 = 8;
1059 static int opt_author_cols              = AUTHOR_COLS;
1060 static char opt_path[SIZEOF_STR]        = "";
1061 static char opt_file[SIZEOF_STR]        = "";
1062 static char opt_ref[SIZEOF_REF]         = "";
1063 static char opt_head[SIZEOF_REF]        = "";
1064 static char opt_head_rev[SIZEOF_REV]    = "";
1065 static char opt_remote[SIZEOF_REF]      = "";
1066 static char opt_encoding[20]            = "UTF-8";
1067 static char opt_codeset[20]             = "UTF-8";
1068 static iconv_t opt_iconv_in             = ICONV_NONE;
1069 static iconv_t opt_iconv_out            = ICONV_NONE;
1070 static char opt_search[SIZEOF_STR]      = "";
1071 static char opt_cdup[SIZEOF_STR]        = "";
1072 static char opt_prefix[SIZEOF_STR]      = "";
1073 static char opt_git_dir[SIZEOF_STR]     = "";
1074 static signed char opt_is_inside_work_tree      = -1; /* set to TRUE or FALSE */
1075 static char opt_editor[SIZEOF_STR]      = "";
1076 static FILE *opt_tty                    = NULL;
1078 #define is_initial_commit()     (!*opt_head_rev)
1079 #define is_head_commit(rev)     (!strcmp((rev), "HEAD") || !strcmp(opt_head_rev, (rev)))
1080 #define mkdate(time)            string_date(time, opt_date)
1083 /*
1084  * Line-oriented content detection.
1085  */
1087 #define LINE_INFO \
1088 LINE(DIFF_HEADER,  "diff --git ",       COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1089 LINE(DIFF_CHUNK,   "@@",                COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1090 LINE(DIFF_ADD,     "+",                 COLOR_GREEN,    COLOR_DEFAULT,  0), \
1091 LINE(DIFF_DEL,     "-",                 COLOR_RED,      COLOR_DEFAULT,  0), \
1092 LINE(DIFF_INDEX,        "index ",         COLOR_BLUE,   COLOR_DEFAULT,  0), \
1093 LINE(DIFF_OLDMODE,      "old file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
1094 LINE(DIFF_NEWMODE,      "new file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
1095 LINE(DIFF_COPY_FROM,    "copy from",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
1096 LINE(DIFF_COPY_TO,      "copy to",        COLOR_YELLOW, COLOR_DEFAULT,  0), \
1097 LINE(DIFF_RENAME_FROM,  "rename from",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
1098 LINE(DIFF_RENAME_TO,    "rename to",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
1099 LINE(DIFF_SIMILARITY,   "similarity ",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
1100 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
1101 LINE(DIFF_TREE,         "diff-tree ",     COLOR_BLUE,   COLOR_DEFAULT,  0), \
1102 LINE(PP_AUTHOR,    "Author: ",          COLOR_CYAN,     COLOR_DEFAULT,  0), \
1103 LINE(PP_COMMIT,    "Commit: ",          COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1104 LINE(PP_MERGE,     "Merge: ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
1105 LINE(PP_DATE,      "Date:   ",          COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1106 LINE(PP_ADATE,     "AuthorDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1107 LINE(PP_CDATE,     "CommitDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1108 LINE(PP_REFS,      "Refs: ",            COLOR_RED,      COLOR_DEFAULT,  0), \
1109 LINE(COMMIT,       "commit ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
1110 LINE(PARENT,       "parent ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
1111 LINE(TREE,         "tree ",             COLOR_BLUE,     COLOR_DEFAULT,  0), \
1112 LINE(AUTHOR,       "author ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
1113 LINE(COMMITTER,    "committer ",        COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1114 LINE(SIGNOFF,      "    Signed-off-by", COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1115 LINE(ACKED,        "    Acked-by",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1116 LINE(DEFAULT,      "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
1117 LINE(CURSOR,       "",                  COLOR_WHITE,    COLOR_GREEN,    A_BOLD), \
1118 LINE(STATUS,       "",                  COLOR_GREEN,    COLOR_DEFAULT,  0), \
1119 LINE(DELIMITER,    "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1120 LINE(DATE,         "",                  COLOR_BLUE,     COLOR_DEFAULT,  0), \
1121 LINE(MODE,         "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1122 LINE(LINE_NUMBER,  "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1123 LINE(TITLE_BLUR,   "",                  COLOR_WHITE,    COLOR_BLUE,     0), \
1124 LINE(TITLE_FOCUS,  "",                  COLOR_WHITE,    COLOR_BLUE,     A_BOLD), \
1125 LINE(MAIN_COMMIT,  "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
1126 LINE(MAIN_TAG,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  A_BOLD), \
1127 LINE(MAIN_LOCAL_TAG,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1128 LINE(MAIN_REMOTE,  "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1129 LINE(MAIN_TRACKED, "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_BOLD), \
1130 LINE(MAIN_REF,     "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1131 LINE(MAIN_HEAD,    "",                  COLOR_CYAN,     COLOR_DEFAULT,  A_BOLD), \
1132 LINE(MAIN_REVGRAPH,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1133 LINE(TREE_HEAD,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_BOLD), \
1134 LINE(TREE_DIR,     "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_NORMAL), \
1135 LINE(TREE_FILE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
1136 LINE(STAT_HEAD,    "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1137 LINE(STAT_SECTION, "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1138 LINE(STAT_NONE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
1139 LINE(STAT_STAGED,  "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1140 LINE(STAT_UNSTAGED,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1141 LINE(STAT_UNTRACKED,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1142 LINE(HELP_KEYMAP,  "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1143 LINE(HELP_GROUP,   "",                  COLOR_BLUE,     COLOR_DEFAULT,  0), \
1144 LINE(BLAME_ID,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0)
1146 enum line_type {
1147 #define LINE(type, line, fg, bg, attr) \
1148         LINE_##type
1149         LINE_INFO,
1150         LINE_NONE
1151 #undef  LINE
1152 };
1154 struct line_info {
1155         const char *name;       /* Option name. */
1156         int namelen;            /* Size of option name. */
1157         const char *line;       /* The start of line to match. */
1158         int linelen;            /* Size of string to match. */
1159         int fg, bg, attr;       /* Color and text attributes for the lines. */
1160 };
1162 static struct line_info line_info[] = {
1163 #define LINE(type, line, fg, bg, attr) \
1164         { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
1165         LINE_INFO
1166 #undef  LINE
1167 };
1169 static enum line_type
1170 get_line_type(const char *line)
1172         int linelen = strlen(line);
1173         enum line_type type;
1175         for (type = 0; type < ARRAY_SIZE(line_info); type++)
1176                 /* Case insensitive search matches Signed-off-by lines better. */
1177                 if (linelen >= line_info[type].linelen &&
1178                     !strncasecmp(line_info[type].line, line, line_info[type].linelen))
1179                         return type;
1181         return LINE_DEFAULT;
1184 static inline int
1185 get_line_attr(enum line_type type)
1187         assert(type < ARRAY_SIZE(line_info));
1188         return COLOR_PAIR(type) | line_info[type].attr;
1191 static struct line_info *
1192 get_line_info(const char *name)
1194         size_t namelen = strlen(name);
1195         enum line_type type;
1197         for (type = 0; type < ARRAY_SIZE(line_info); type++)
1198                 if (enum_equals(line_info[type], name, namelen))
1199                         return &line_info[type];
1201         return NULL;
1204 static void
1205 init_colors(void)
1207         int default_bg = line_info[LINE_DEFAULT].bg;
1208         int default_fg = line_info[LINE_DEFAULT].fg;
1209         enum line_type type;
1211         start_color();
1213         if (assume_default_colors(default_fg, default_bg) == ERR) {
1214                 default_bg = COLOR_BLACK;
1215                 default_fg = COLOR_WHITE;
1216         }
1218         for (type = 0; type < ARRAY_SIZE(line_info); type++) {
1219                 struct line_info *info = &line_info[type];
1220                 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
1221                 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
1223                 init_pair(type, fg, bg);
1224         }
1227 struct line {
1228         enum line_type type;
1230         /* State flags */
1231         unsigned int selected:1;
1232         unsigned int dirty:1;
1233         unsigned int cleareol:1;
1234         unsigned int other:16;
1236         void *data;             /* User data */
1237 };
1240 /*
1241  * Keys
1242  */
1244 struct keybinding {
1245         int alias;
1246         enum request request;
1247 };
1249 static const struct keybinding default_keybindings[] = {
1250         /* View switching */
1251         { 'm',          REQ_VIEW_MAIN },
1252         { 'd',          REQ_VIEW_DIFF },
1253         { 'l',          REQ_VIEW_LOG },
1254         { 't',          REQ_VIEW_TREE },
1255         { 'f',          REQ_VIEW_BLOB },
1256         { 'B',          REQ_VIEW_BLAME },
1257         { 'H',          REQ_VIEW_BRANCH },
1258         { 'p',          REQ_VIEW_PAGER },
1259         { 'h',          REQ_VIEW_HELP },
1260         { 'S',          REQ_VIEW_STATUS },
1261         { 'c',          REQ_VIEW_STAGE },
1263         /* View manipulation */
1264         { 'q',          REQ_VIEW_CLOSE },
1265         { KEY_TAB,      REQ_VIEW_NEXT },
1266         { KEY_RETURN,   REQ_ENTER },
1267         { KEY_UP,       REQ_PREVIOUS },
1268         { KEY_DOWN,     REQ_NEXT },
1269         { 'R',          REQ_REFRESH },
1270         { KEY_F(5),     REQ_REFRESH },
1271         { 'O',          REQ_MAXIMIZE },
1273         /* Cursor navigation */
1274         { 'k',          REQ_MOVE_UP },
1275         { 'j',          REQ_MOVE_DOWN },
1276         { KEY_HOME,     REQ_MOVE_FIRST_LINE },
1277         { KEY_END,      REQ_MOVE_LAST_LINE },
1278         { KEY_NPAGE,    REQ_MOVE_PAGE_DOWN },
1279         { ' ',          REQ_MOVE_PAGE_DOWN },
1280         { KEY_PPAGE,    REQ_MOVE_PAGE_UP },
1281         { 'b',          REQ_MOVE_PAGE_UP },
1282         { '-',          REQ_MOVE_PAGE_UP },
1284         /* Scrolling */
1285         { KEY_LEFT,     REQ_SCROLL_LEFT },
1286         { KEY_RIGHT,    REQ_SCROLL_RIGHT },
1287         { KEY_IC,       REQ_SCROLL_LINE_UP },
1288         { KEY_DC,       REQ_SCROLL_LINE_DOWN },
1289         { 'w',          REQ_SCROLL_PAGE_UP },
1290         { 's',          REQ_SCROLL_PAGE_DOWN },
1292         /* Searching */
1293         { '/',          REQ_SEARCH },
1294         { '?',          REQ_SEARCH_BACK },
1295         { 'n',          REQ_FIND_NEXT },
1296         { 'N',          REQ_FIND_PREV },
1298         /* Misc */
1299         { 'Q',          REQ_QUIT },
1300         { 'z',          REQ_STOP_LOADING },
1301         { 'v',          REQ_SHOW_VERSION },
1302         { 'r',          REQ_SCREEN_REDRAW },
1303         { 'o',          REQ_OPTIONS },
1304         { '.',          REQ_TOGGLE_LINENO },
1305         { 'D',          REQ_TOGGLE_DATE },
1306         { 'A',          REQ_TOGGLE_AUTHOR },
1307         { 'g',          REQ_TOGGLE_REV_GRAPH },
1308         { 'F',          REQ_TOGGLE_REFS },
1309         { 'I',          REQ_TOGGLE_SORT_ORDER },
1310         { 'i',          REQ_TOGGLE_SORT_FIELD },
1311         { ':',          REQ_PROMPT },
1312         { 'u',          REQ_STATUS_UPDATE },
1313         { '!',          REQ_STATUS_REVERT },
1314         { 'M',          REQ_STATUS_MERGE },
1315         { '@',          REQ_STAGE_NEXT },
1316         { ',',          REQ_PARENT },
1317         { 'e',          REQ_EDIT },
1318 };
1320 #define KEYMAP_INFO \
1321         KEYMAP_(GENERIC), \
1322         KEYMAP_(MAIN), \
1323         KEYMAP_(DIFF), \
1324         KEYMAP_(LOG), \
1325         KEYMAP_(TREE), \
1326         KEYMAP_(BLOB), \
1327         KEYMAP_(BLAME), \
1328         KEYMAP_(BRANCH), \
1329         KEYMAP_(PAGER), \
1330         KEYMAP_(HELP), \
1331         KEYMAP_(STATUS), \
1332         KEYMAP_(STAGE)
1334 enum keymap {
1335 #define KEYMAP_(name) KEYMAP_##name
1336         KEYMAP_INFO
1337 #undef  KEYMAP_
1338 };
1340 static const struct enum_map keymap_table[] = {
1341 #define KEYMAP_(name) ENUM_MAP(#name, KEYMAP_##name)
1342         KEYMAP_INFO
1343 #undef  KEYMAP_
1344 };
1346 #define set_keymap(map, name) map_enum(map, keymap_table, name)
1348 struct keybinding_table {
1349         struct keybinding *data;
1350         size_t size;
1351 };
1353 static struct keybinding_table keybindings[ARRAY_SIZE(keymap_table)];
1355 static void
1356 add_keybinding(enum keymap keymap, enum request request, int key)
1358         struct keybinding_table *table = &keybindings[keymap];
1360         table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data));
1361         if (!table->data)
1362                 die("Failed to allocate keybinding");
1363         table->data[table->size].alias = key;
1364         table->data[table->size++].request = request;
1367 /* Looks for a key binding first in the given map, then in the generic map, and
1368  * lastly in the default keybindings. */
1369 static enum request
1370 get_keybinding(enum keymap keymap, int key)
1372         size_t i;
1374         for (i = 0; i < keybindings[keymap].size; i++)
1375                 if (keybindings[keymap].data[i].alias == key)
1376                         return keybindings[keymap].data[i].request;
1378         for (i = 0; i < keybindings[KEYMAP_GENERIC].size; i++)
1379                 if (keybindings[KEYMAP_GENERIC].data[i].alias == key)
1380                         return keybindings[KEYMAP_GENERIC].data[i].request;
1382         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1383                 if (default_keybindings[i].alias == key)
1384                         return default_keybindings[i].request;
1386         return (enum request) key;
1390 struct key {
1391         const char *name;
1392         int value;
1393 };
1395 static const struct key key_table[] = {
1396         { "Enter",      KEY_RETURN },
1397         { "Space",      ' ' },
1398         { "Backspace",  KEY_BACKSPACE },
1399         { "Tab",        KEY_TAB },
1400         { "Escape",     KEY_ESC },
1401         { "Left",       KEY_LEFT },
1402         { "Right",      KEY_RIGHT },
1403         { "Up",         KEY_UP },
1404         { "Down",       KEY_DOWN },
1405         { "Insert",     KEY_IC },
1406         { "Delete",     KEY_DC },
1407         { "Hash",       '#' },
1408         { "Home",       KEY_HOME },
1409         { "End",        KEY_END },
1410         { "PageUp",     KEY_PPAGE },
1411         { "PageDown",   KEY_NPAGE },
1412         { "F1",         KEY_F(1) },
1413         { "F2",         KEY_F(2) },
1414         { "F3",         KEY_F(3) },
1415         { "F4",         KEY_F(4) },
1416         { "F5",         KEY_F(5) },
1417         { "F6",         KEY_F(6) },
1418         { "F7",         KEY_F(7) },
1419         { "F8",         KEY_F(8) },
1420         { "F9",         KEY_F(9) },
1421         { "F10",        KEY_F(10) },
1422         { "F11",        KEY_F(11) },
1423         { "F12",        KEY_F(12) },
1424 };
1426 static int
1427 get_key_value(const char *name)
1429         int i;
1431         for (i = 0; i < ARRAY_SIZE(key_table); i++)
1432                 if (!strcasecmp(key_table[i].name, name))
1433                         return key_table[i].value;
1435         if (strlen(name) == 1 && isprint(*name))
1436                 return (int) *name;
1438         return ERR;
1441 static const char *
1442 get_key_name(int key_value)
1444         static char key_char[] = "'X'";
1445         const char *seq = NULL;
1446         int key;
1448         for (key = 0; key < ARRAY_SIZE(key_table); key++)
1449                 if (key_table[key].value == key_value)
1450                         seq = key_table[key].name;
1452         if (seq == NULL &&
1453             key_value < 127 &&
1454             isprint(key_value)) {
1455                 key_char[1] = (char) key_value;
1456                 seq = key_char;
1457         }
1459         return seq ? seq : "(no key)";
1462 static bool
1463 append_key(char *buf, size_t *pos, const struct keybinding *keybinding)
1465         const char *sep = *pos > 0 ? ", " : "";
1466         const char *keyname = get_key_name(keybinding->alias);
1468         return string_nformat(buf, BUFSIZ, pos, "%s%s", sep, keyname);
1471 static bool
1472 append_keymap_request_keys(char *buf, size_t *pos, enum request request,
1473                            enum keymap keymap, bool all)
1475         int i;
1477         for (i = 0; i < keybindings[keymap].size; i++) {
1478                 if (keybindings[keymap].data[i].request == request) {
1479                         if (!append_key(buf, pos, &keybindings[keymap].data[i]))
1480                                 return FALSE;
1481                         if (!all)
1482                                 break;
1483                 }
1484         }
1486         return TRUE;
1489 #define get_key(keymap, request) get_keys(keymap, request, FALSE)
1491 static const char *
1492 get_keys(enum keymap keymap, enum request request, bool all)
1494         static char buf[BUFSIZ];
1495         size_t pos = 0;
1496         int i;
1498         buf[pos] = 0;
1500         if (!append_keymap_request_keys(buf, &pos, request, keymap, all))
1501                 return "Too many keybindings!";
1502         if (pos > 0 && !all)
1503                 return buf;
1505         if (keymap != KEYMAP_GENERIC) {
1506                 /* Only the generic keymap includes the default keybindings when
1507                  * listing all keys. */
1508                 if (all)
1509                         return buf;
1511                 if (!append_keymap_request_keys(buf, &pos, request, KEYMAP_GENERIC, all))
1512                         return "Too many keybindings!";
1513                 if (pos)
1514                         return buf;
1515         }
1517         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1518                 if (default_keybindings[i].request == request) {
1519                         if (!append_key(buf, &pos, &default_keybindings[i]))
1520                                 return "Too many keybindings!";
1521                         if (!all)
1522                                 return buf;
1523                 }
1524         }
1526         return buf;
1529 struct run_request {
1530         enum keymap keymap;
1531         int key;
1532         const char *argv[SIZEOF_ARG];
1533 };
1535 static struct run_request *run_request;
1536 static size_t run_requests;
1538 DEFINE_ALLOCATOR(realloc_run_requests, struct run_request, 8)
1540 static enum request
1541 add_run_request(enum keymap keymap, int key, int argc, const char **argv)
1543         struct run_request *req;
1545         if (argc >= ARRAY_SIZE(req->argv) - 1)
1546                 return REQ_NONE;
1548         if (!realloc_run_requests(&run_request, run_requests, 1))
1549                 return REQ_NONE;
1551         req = &run_request[run_requests];
1552         req->keymap = keymap;
1553         req->key = key;
1554         req->argv[0] = NULL;
1556         if (!format_argv(req->argv, argv, FORMAT_NONE))
1557                 return REQ_NONE;
1559         return REQ_NONE + ++run_requests;
1562 static struct run_request *
1563 get_run_request(enum request request)
1565         if (request <= REQ_NONE)
1566                 return NULL;
1567         return &run_request[request - REQ_NONE - 1];
1570 static void
1571 add_builtin_run_requests(void)
1573         const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1574         const char *commit[] = { "git", "commit", NULL };
1575         const char *gc[] = { "git", "gc", NULL };
1576         struct {
1577                 enum keymap keymap;
1578                 int key;
1579                 int argc;
1580                 const char **argv;
1581         } reqs[] = {
1582                 { KEYMAP_MAIN,    'C', ARRAY_SIZE(cherry_pick) - 1, cherry_pick },
1583                 { KEYMAP_STATUS,  'C', ARRAY_SIZE(commit) - 1, commit },
1584                 { KEYMAP_GENERIC, 'G', ARRAY_SIZE(gc) - 1, gc },
1585         };
1586         int i;
1588         for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1589                 enum request req;
1591                 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argc, reqs[i].argv);
1592                 if (req != REQ_NONE)
1593                         add_keybinding(reqs[i].keymap, req, reqs[i].key);
1594         }
1597 /*
1598  * User config file handling.
1599  */
1601 static int   config_lineno;
1602 static bool  config_errors;
1603 static const char *config_msg;
1605 static const struct enum_map color_map[] = {
1606 #define COLOR_MAP(name) ENUM_MAP(#name, COLOR_##name)
1607         COLOR_MAP(DEFAULT),
1608         COLOR_MAP(BLACK),
1609         COLOR_MAP(BLUE),
1610         COLOR_MAP(CYAN),
1611         COLOR_MAP(GREEN),
1612         COLOR_MAP(MAGENTA),
1613         COLOR_MAP(RED),
1614         COLOR_MAP(WHITE),
1615         COLOR_MAP(YELLOW),
1616 };
1618 static const struct enum_map attr_map[] = {
1619 #define ATTR_MAP(name) ENUM_MAP(#name, A_##name)
1620         ATTR_MAP(NORMAL),
1621         ATTR_MAP(BLINK),
1622         ATTR_MAP(BOLD),
1623         ATTR_MAP(DIM),
1624         ATTR_MAP(REVERSE),
1625         ATTR_MAP(STANDOUT),
1626         ATTR_MAP(UNDERLINE),
1627 };
1629 #define set_attribute(attr, name)       map_enum(attr, attr_map, name)
1631 static int parse_step(double *opt, const char *arg)
1633         *opt = atoi(arg);
1634         if (!strchr(arg, '%'))
1635                 return OK;
1637         /* "Shift down" so 100% and 1 does not conflict. */
1638         *opt = (*opt - 1) / 100;
1639         if (*opt >= 1.0) {
1640                 *opt = 0.99;
1641                 config_msg = "Step value larger than 100%";
1642                 return ERR;
1643         }
1644         if (*opt < 0.0) {
1645                 *opt = 1;
1646                 config_msg = "Invalid step value";
1647                 return ERR;
1648         }
1649         return OK;
1652 static int
1653 parse_int(int *opt, const char *arg, int min, int max)
1655         int value = atoi(arg);
1657         if (min <= value && value <= max) {
1658                 *opt = value;
1659                 return OK;
1660         }
1662         config_msg = "Integer value out of bound";
1663         return ERR;
1666 static bool
1667 set_color(int *color, const char *name)
1669         if (map_enum(color, color_map, name))
1670                 return TRUE;
1671         if (!prefixcmp(name, "color"))
1672                 return parse_int(color, name + 5, 0, 255) == OK;
1673         return FALSE;
1676 /* Wants: object fgcolor bgcolor [attribute] */
1677 static int
1678 option_color_command(int argc, const char *argv[])
1680         struct line_info *info;
1682         if (argc < 3) {
1683                 config_msg = "Wrong number of arguments given to color command";
1684                 return ERR;
1685         }
1687         info = get_line_info(argv[0]);
1688         if (!info) {
1689                 static const struct enum_map obsolete[] = {
1690                         ENUM_MAP("main-delim",  LINE_DELIMITER),
1691                         ENUM_MAP("main-date",   LINE_DATE),
1692                         ENUM_MAP("main-author", LINE_AUTHOR),
1693                 };
1694                 int index;
1696                 if (!map_enum(&index, obsolete, argv[0])) {
1697                         config_msg = "Unknown color name";
1698                         return ERR;
1699                 }
1700                 info = &line_info[index];
1701         }
1703         if (!set_color(&info->fg, argv[1]) ||
1704             !set_color(&info->bg, argv[2])) {
1705                 config_msg = "Unknown color";
1706                 return ERR;
1707         }
1709         info->attr = 0;
1710         while (argc-- > 3) {
1711                 int attr;
1713                 if (!set_attribute(&attr, argv[argc])) {
1714                         config_msg = "Unknown attribute";
1715                         return ERR;
1716                 }
1717                 info->attr |= attr;
1718         }
1720         return OK;
1723 static int parse_bool(bool *opt, const char *arg)
1725         *opt = (!strcmp(arg, "1") || !strcmp(arg, "true") || !strcmp(arg, "yes"))
1726                 ? TRUE : FALSE;
1727         return OK;
1730 static int parse_enum_do(unsigned int *opt, const char *arg,
1731                          const struct enum_map *map, size_t map_size)
1733         bool is_true;
1735         assert(map_size > 1);
1737         if (map_enum_do(map, map_size, (int *) opt, arg))
1738                 return OK;
1740         if (parse_bool(&is_true, arg) != OK)
1741                 return ERR;
1743         *opt = is_true ? map[1].value : map[0].value;
1744         return OK;
1747 #define parse_enum(opt, arg, map) \
1748         parse_enum_do(opt, arg, map, ARRAY_SIZE(map))
1750 static int
1751 parse_string(char *opt, const char *arg, size_t optsize)
1753         int arglen = strlen(arg);
1755         switch (arg[0]) {
1756         case '\"':
1757         case '\'':
1758                 if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1759                         config_msg = "Unmatched quotation";
1760                         return ERR;
1761                 }
1762                 arg += 1; arglen -= 2;
1763         default:
1764                 string_ncopy_do(opt, optsize, arg, arglen);
1765                 return OK;
1766         }
1769 /* Wants: name = value */
1770 static int
1771 option_set_command(int argc, const char *argv[])
1773         if (argc != 3) {
1774                 config_msg = "Wrong number of arguments given to set command";
1775                 return ERR;
1776         }
1778         if (strcmp(argv[1], "=")) {
1779                 config_msg = "No value assigned";
1780                 return ERR;
1781         }
1783         if (!strcmp(argv[0], "show-author"))
1784                 return parse_enum(&opt_author, argv[2], author_map);
1786         if (!strcmp(argv[0], "show-date"))
1787                 return parse_enum(&opt_date, argv[2], date_map);
1789         if (!strcmp(argv[0], "show-rev-graph"))
1790                 return parse_bool(&opt_rev_graph, argv[2]);
1792         if (!strcmp(argv[0], "show-refs"))
1793                 return parse_bool(&opt_show_refs, argv[2]);
1795         if (!strcmp(argv[0], "show-line-numbers"))
1796                 return parse_bool(&opt_line_number, argv[2]);
1798         if (!strcmp(argv[0], "line-graphics"))
1799                 return parse_bool(&opt_line_graphics, argv[2]);
1801         if (!strcmp(argv[0], "line-number-interval"))
1802                 return parse_int(&opt_num_interval, argv[2], 1, 1024);
1804         if (!strcmp(argv[0], "author-width"))
1805                 return parse_int(&opt_author_cols, argv[2], 0, 1024);
1807         if (!strcmp(argv[0], "horizontal-scroll"))
1808                 return parse_step(&opt_hscroll, argv[2]);
1810         if (!strcmp(argv[0], "split-view-height"))
1811                 return parse_step(&opt_scale_split_view, argv[2]);
1813         if (!strcmp(argv[0], "tab-size"))
1814                 return parse_int(&opt_tab_size, argv[2], 1, 1024);
1816         if (!strcmp(argv[0], "commit-encoding"))
1817                 return parse_string(opt_encoding, argv[2], sizeof(opt_encoding));
1819         config_msg = "Unknown variable name";
1820         return ERR;
1823 /* Wants: mode request key */
1824 static int
1825 option_bind_command(int argc, const char *argv[])
1827         enum request request;
1828         int keymap = -1;
1829         int key;
1831         if (argc < 3) {
1832                 config_msg = "Wrong number of arguments given to bind command";
1833                 return ERR;
1834         }
1836         if (set_keymap(&keymap, argv[0]) == ERR) {
1837                 config_msg = "Unknown key map";
1838                 return ERR;
1839         }
1841         key = get_key_value(argv[1]);
1842         if (key == ERR) {
1843                 config_msg = "Unknown key";
1844                 return ERR;
1845         }
1847         request = get_request(argv[2]);
1848         if (request == REQ_NONE) {
1849                 static const struct enum_map obsolete[] = {
1850                         ENUM_MAP("cherry-pick",         REQ_NONE),
1851                         ENUM_MAP("screen-resize",       REQ_NONE),
1852                         ENUM_MAP("tree-parent",         REQ_PARENT),
1853                 };
1854                 int alias;
1856                 if (map_enum(&alias, obsolete, argv[2])) {
1857                         if (alias != REQ_NONE)
1858                                 add_keybinding(keymap, alias, key);
1859                         config_msg = "Obsolete request name";
1860                         return ERR;
1861                 }
1862         }
1863         if (request == REQ_NONE && *argv[2]++ == '!')
1864                 request = add_run_request(keymap, key, argc - 2, argv + 2);
1865         if (request == REQ_NONE) {
1866                 config_msg = "Unknown request name";
1867                 return ERR;
1868         }
1870         add_keybinding(keymap, request, key);
1872         return OK;
1875 static int
1876 set_option(const char *opt, char *value)
1878         const char *argv[SIZEOF_ARG];
1879         int argc = 0;
1881         if (!argv_from_string(argv, &argc, value)) {
1882                 config_msg = "Too many option arguments";
1883                 return ERR;
1884         }
1886         if (!strcmp(opt, "color"))
1887                 return option_color_command(argc, argv);
1889         if (!strcmp(opt, "set"))
1890                 return option_set_command(argc, argv);
1892         if (!strcmp(opt, "bind"))
1893                 return option_bind_command(argc, argv);
1895         config_msg = "Unknown option command";
1896         return ERR;
1899 static int
1900 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
1902         int status = OK;
1904         config_lineno++;
1905         config_msg = "Internal error";
1907         /* Check for comment markers, since read_properties() will
1908          * only ensure opt and value are split at first " \t". */
1909         optlen = strcspn(opt, "#");
1910         if (optlen == 0)
1911                 return OK;
1913         if (opt[optlen] != 0) {
1914                 config_msg = "No option value";
1915                 status = ERR;
1917         }  else {
1918                 /* Look for comment endings in the value. */
1919                 size_t len = strcspn(value, "#");
1921                 if (len < valuelen) {
1922                         valuelen = len;
1923                         value[valuelen] = 0;
1924                 }
1926                 status = set_option(opt, value);
1927         }
1929         if (status == ERR) {
1930                 warn("Error on line %d, near '%.*s': %s",
1931                      config_lineno, (int) optlen, opt, config_msg);
1932                 config_errors = TRUE;
1933         }
1935         /* Always keep going if errors are encountered. */
1936         return OK;
1939 static void
1940 load_option_file(const char *path)
1942         struct io io = {};
1944         /* It's OK that the file doesn't exist. */
1945         if (!io_open(&io, "%s", path))
1946                 return;
1948         config_lineno = 0;
1949         config_errors = FALSE;
1951         if (io_load(&io, " \t", read_option) == ERR ||
1952             config_errors == TRUE)
1953                 warn("Errors while loading %s.", path);
1956 static int
1957 load_options(void)
1959         const char *home = getenv("HOME");
1960         const char *tigrc_user = getenv("TIGRC_USER");
1961         const char *tigrc_system = getenv("TIGRC_SYSTEM");
1962         char buf[SIZEOF_STR];
1964         add_builtin_run_requests();
1966         if (!tigrc_system)
1967                 tigrc_system = SYSCONFDIR "/tigrc";
1968         load_option_file(tigrc_system);
1970         if (!tigrc_user) {
1971                 if (!home || !string_format(buf, "%s/.tigrc", home))
1972                         return ERR;
1973                 tigrc_user = buf;
1974         }
1975         load_option_file(tigrc_user);
1977         return OK;
1981 /*
1982  * The viewer
1983  */
1985 struct view;
1986 struct view_ops;
1988 /* The display array of active views and the index of the current view. */
1989 static struct view *display[2];
1990 static unsigned int current_view;
1992 #define foreach_displayed_view(view, i) \
1993         for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1995 #define displayed_views()       (display[1] != NULL ? 2 : 1)
1997 /* Current head and commit ID */
1998 static char ref_blob[SIZEOF_REF]        = "";
1999 static char ref_commit[SIZEOF_REF]      = "HEAD";
2000 static char ref_head[SIZEOF_REF]        = "HEAD";
2002 struct view {
2003         const char *name;       /* View name */
2004         const char *cmd_env;    /* Command line set via environment */
2005         const char *id;         /* Points to either of ref_{head,commit,blob} */
2007         struct view_ops *ops;   /* View operations */
2009         enum keymap keymap;     /* What keymap does this view have */
2010         bool git_dir;           /* Whether the view requires a git directory. */
2012         char ref[SIZEOF_REF];   /* Hovered commit reference */
2013         char vid[SIZEOF_REF];   /* View ID. Set to id member when updating. */
2015         int height, width;      /* The width and height of the main window */
2016         WINDOW *win;            /* The main window */
2017         WINDOW *title;          /* The title window living below the main window */
2019         /* Navigation */
2020         unsigned long offset;   /* Offset of the window top */
2021         unsigned long yoffset;  /* Offset from the window side. */
2022         unsigned long lineno;   /* Current line number */
2023         unsigned long p_offset; /* Previous offset of the window top */
2024         unsigned long p_yoffset;/* Previous offset from the window side */
2025         unsigned long p_lineno; /* Previous current line number */
2026         bool p_restore;         /* Should the previous position be restored. */
2028         /* Searching */
2029         char grep[SIZEOF_STR];  /* Search string */
2030         regex_t *regex;         /* Pre-compiled regexp */
2032         /* If non-NULL, points to the view that opened this view. If this view
2033          * is closed tig will switch back to the parent view. */
2034         struct view *parent;
2036         /* Buffering */
2037         size_t lines;           /* Total number of lines */
2038         struct line *line;      /* Line index */
2039         unsigned int digits;    /* Number of digits in the lines member. */
2041         /* Drawing */
2042         struct line *curline;   /* Line currently being drawn. */
2043         enum line_type curtype; /* Attribute currently used for drawing. */
2044         unsigned long col;      /* Column when drawing. */
2045         bool has_scrolled;      /* View was scrolled. */
2047         /* Loading */
2048         struct io io;
2049         struct io *pipe;
2050         time_t start_time;
2051         time_t update_secs;
2052 };
2054 struct view_ops {
2055         /* What type of content being displayed. Used in the title bar. */
2056         const char *type;
2057         /* Default command arguments. */
2058         const char **argv;
2059         /* Open and reads in all view content. */
2060         bool (*open)(struct view *view);
2061         /* Read one line; updates view->line. */
2062         bool (*read)(struct view *view, char *data);
2063         /* Draw one line; @lineno must be < view->height. */
2064         bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
2065         /* Depending on view handle a special requests. */
2066         enum request (*request)(struct view *view, enum request request, struct line *line);
2067         /* Search for regexp in a line. */
2068         bool (*grep)(struct view *view, struct line *line);
2069         /* Select line */
2070         void (*select)(struct view *view, struct line *line);
2071         /* Prepare view for loading */
2072         bool (*prepare)(struct view *view);
2073 };
2075 static struct view_ops blame_ops;
2076 static struct view_ops blob_ops;
2077 static struct view_ops diff_ops;
2078 static struct view_ops help_ops;
2079 static struct view_ops log_ops;
2080 static struct view_ops main_ops;
2081 static struct view_ops pager_ops;
2082 static struct view_ops stage_ops;
2083 static struct view_ops status_ops;
2084 static struct view_ops tree_ops;
2085 static struct view_ops branch_ops;
2087 #define VIEW_STR(name, env, ref, ops, map, git) \
2088         { name, #env, ref, ops, map, git }
2090 #define VIEW_(id, name, ops, git, ref) \
2091         VIEW_STR(name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
2094 static struct view views[] = {
2095         VIEW_(MAIN,   "main",   &main_ops,   TRUE,  ref_head),
2096         VIEW_(DIFF,   "diff",   &diff_ops,   TRUE,  ref_commit),
2097         VIEW_(LOG,    "log",    &log_ops,    TRUE,  ref_head),
2098         VIEW_(TREE,   "tree",   &tree_ops,   TRUE,  ref_commit),
2099         VIEW_(BLOB,   "blob",   &blob_ops,   TRUE,  ref_blob),
2100         VIEW_(BLAME,  "blame",  &blame_ops,  TRUE,  ref_commit),
2101         VIEW_(BRANCH, "branch", &branch_ops, TRUE,  ref_head),
2102         VIEW_(HELP,   "help",   &help_ops,   FALSE, ""),
2103         VIEW_(PAGER,  "pager",  &pager_ops,  FALSE, "stdin"),
2104         VIEW_(STATUS, "status", &status_ops, TRUE,  ""),
2105         VIEW_(STAGE,  "stage",  &stage_ops,  TRUE,  ""),
2106 };
2108 #define VIEW(req)       (&views[(req) - REQ_OFFSET - 1])
2109 #define VIEW_REQ(view)  ((view) - views + REQ_OFFSET + 1)
2111 #define foreach_view(view, i) \
2112         for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
2114 #define view_is_displayed(view) \
2115         (view == display[0] || view == display[1])
2118 static inline void
2119 set_view_attr(struct view *view, enum line_type type)
2121         if (!view->curline->selected && view->curtype != type) {
2122                 (void) wattrset(view->win, get_line_attr(type));
2123                 wchgat(view->win, -1, 0, type, NULL);
2124                 view->curtype = type;
2125         }
2128 static int
2129 draw_chars(struct view *view, enum line_type type, const char *string,
2130            int max_len, bool use_tilde)
2132         static char out_buffer[BUFSIZ * 2];
2133         int len = 0;
2134         int col = 0;
2135         int trimmed = FALSE;
2136         size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2138         if (max_len <= 0)
2139                 return 0;
2141         len = utf8_length(&string, skip, &col, max_len, &trimmed, use_tilde);
2143         set_view_attr(view, type);
2144         if (len > 0) {
2145                 if (opt_iconv_out != ICONV_NONE) {
2146                         ICONV_CONST char *inbuf = (ICONV_CONST char *) string;
2147                         size_t inlen = len + 1;
2149                         char *outbuf = out_buffer;
2150                         size_t outlen = sizeof(out_buffer);
2152                         size_t ret;
2154                         ret = iconv(opt_iconv_out, &inbuf, &inlen, &outbuf, &outlen);
2155                         if (ret != (size_t) -1) {
2156                                 string = out_buffer;
2157                                 len = sizeof(out_buffer) - outlen;
2158                         }
2159                 }
2161                 waddnstr(view->win, string, len);
2162         }
2163         if (trimmed && use_tilde) {
2164                 set_view_attr(view, LINE_DELIMITER);
2165                 waddch(view->win, '~');
2166                 col++;
2167         }
2169         return col;
2172 static int
2173 draw_space(struct view *view, enum line_type type, int max, int spaces)
2175         static char space[] = "                    ";
2176         int col = 0;
2178         spaces = MIN(max, spaces);
2180         while (spaces > 0) {
2181                 int len = MIN(spaces, sizeof(space) - 1);
2183                 col += draw_chars(view, type, space, len, FALSE);
2184                 spaces -= len;
2185         }
2187         return col;
2190 static bool
2191 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
2193         view->col += draw_chars(view, type, string, view->width + view->yoffset - view->col, trim);
2194         return view->width + view->yoffset <= view->col;
2197 static bool
2198 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
2200         size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2201         int max = view->width + view->yoffset - view->col;
2202         int i;
2204         if (max < size)
2205                 size = max;
2207         set_view_attr(view, type);
2208         /* Using waddch() instead of waddnstr() ensures that
2209          * they'll be rendered correctly for the cursor line. */
2210         for (i = skip; i < size; i++)
2211                 waddch(view->win, graphic[i]);
2213         view->col += size;
2214         if (size < max && skip <= size)
2215                 waddch(view->win, ' ');
2216         view->col++;
2218         return view->width + view->yoffset <= view->col;
2221 static bool
2222 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
2224         int max = MIN(view->width + view->yoffset - view->col, len);
2225         int col;
2227         if (text)
2228                 col = draw_chars(view, type, text, max - 1, trim);
2229         else
2230                 col = draw_space(view, type, max - 1, max - 1);
2232         view->col += col;
2233         view->col += draw_space(view, LINE_DEFAULT, max - col, max - col);
2234         return view->width + view->yoffset <= view->col;
2237 static bool
2238 draw_date(struct view *view, struct time *time)
2240         const char *date = time && time->sec ? mkdate(time) : "";
2241         int cols = opt_date == DATE_SHORT ? DATE_SHORT_COLS : DATE_COLS;
2243         return draw_field(view, LINE_DATE, date, cols, FALSE);
2246 static bool
2247 draw_author(struct view *view, const char *author)
2249         bool trim = opt_author_cols == 0 || opt_author_cols > 5;
2250         bool abbreviate = opt_author == AUTHOR_ABBREVIATED || !trim;
2252         if (abbreviate && author)
2253                 author = get_author_initials(author);
2255         return draw_field(view, LINE_AUTHOR, author, opt_author_cols, trim);
2258 static bool
2259 draw_mode(struct view *view, mode_t mode)
2261         const char *str;
2263         if (S_ISDIR(mode))
2264                 str = "drwxr-xr-x";
2265         else if (S_ISLNK(mode))
2266                 str = "lrwxrwxrwx";
2267         else if (S_ISGITLINK(mode))
2268                 str = "m---------";
2269         else if (S_ISREG(mode) && mode & S_IXUSR)
2270                 str = "-rwxr-xr-x";
2271         else if (S_ISREG(mode))
2272                 str = "-rw-r--r--";
2273         else
2274                 str = "----------";
2276         return draw_field(view, LINE_MODE, str, STRING_SIZE("-rw-r--r-- "), FALSE);
2279 static bool
2280 draw_lineno(struct view *view, unsigned int lineno)
2282         char number[10];
2283         int digits3 = view->digits < 3 ? 3 : view->digits;
2284         int max = MIN(view->width + view->yoffset - view->col, digits3);
2285         char *text = NULL;
2286         chtype separator = opt_line_graphics ? ACS_VLINE : '|';
2288         lineno += view->offset + 1;
2289         if (lineno == 1 || (lineno % opt_num_interval) == 0) {
2290                 static char fmt[] = "%1ld";
2292                 fmt[1] = '0' + (view->digits <= 9 ? digits3 : 1);
2293                 if (string_format(number, fmt, lineno))
2294                         text = number;
2295         }
2296         if (text)
2297                 view->col += draw_chars(view, LINE_LINE_NUMBER, text, max, TRUE);
2298         else
2299                 view->col += draw_space(view, LINE_LINE_NUMBER, max, digits3);
2300         return draw_graphic(view, LINE_DEFAULT, &separator, 1);
2303 static bool
2304 draw_view_line(struct view *view, unsigned int lineno)
2306         struct line *line;
2307         bool selected = (view->offset + lineno == view->lineno);
2309         assert(view_is_displayed(view));
2311         if (view->offset + lineno >= view->lines)
2312                 return FALSE;
2314         line = &view->line[view->offset + lineno];
2316         wmove(view->win, lineno, 0);
2317         if (line->cleareol)
2318                 wclrtoeol(view->win);
2319         view->col = 0;
2320         view->curline = line;
2321         view->curtype = LINE_NONE;
2322         line->selected = FALSE;
2323         line->dirty = line->cleareol = 0;
2325         if (selected) {
2326                 set_view_attr(view, LINE_CURSOR);
2327                 line->selected = TRUE;
2328                 view->ops->select(view, line);
2329         }
2331         return view->ops->draw(view, line, lineno);
2334 static void
2335 redraw_view_dirty(struct view *view)
2337         bool dirty = FALSE;
2338         int lineno;
2340         for (lineno = 0; lineno < view->height; lineno++) {
2341                 if (view->offset + lineno >= view->lines)
2342                         break;
2343                 if (!view->line[view->offset + lineno].dirty)
2344                         continue;
2345                 dirty = TRUE;
2346                 if (!draw_view_line(view, lineno))
2347                         break;
2348         }
2350         if (!dirty)
2351                 return;
2352         wnoutrefresh(view->win);
2355 static void
2356 redraw_view_from(struct view *view, int lineno)
2358         assert(0 <= lineno && lineno < view->height);
2360         for (; lineno < view->height; lineno++) {
2361                 if (!draw_view_line(view, lineno))
2362                         break;
2363         }
2365         wnoutrefresh(view->win);
2368 static void
2369 redraw_view(struct view *view)
2371         werase(view->win);
2372         redraw_view_from(view, 0);
2376 static void
2377 update_view_title(struct view *view)
2379         char buf[SIZEOF_STR];
2380         char state[SIZEOF_STR];
2381         size_t bufpos = 0, statelen = 0;
2383         assert(view_is_displayed(view));
2385         if (view != VIEW(REQ_VIEW_STATUS) && view->lines) {
2386                 unsigned int view_lines = view->offset + view->height;
2387                 unsigned int lines = view->lines
2388                                    ? MIN(view_lines, view->lines) * 100 / view->lines
2389                                    : 0;
2391                 string_format_from(state, &statelen, " - %s %d of %d (%d%%)",
2392                                    view->ops->type,
2393                                    view->lineno + 1,
2394                                    view->lines,
2395                                    lines);
2397         }
2399         if (view->pipe) {
2400                 time_t secs = time(NULL) - view->start_time;
2402                 /* Three git seconds are a long time ... */
2403                 if (secs > 2)
2404                         string_format_from(state, &statelen, " loading %lds", secs);
2405         }
2407         string_format_from(buf, &bufpos, "[%s]", view->name);
2408         if (*view->ref && bufpos < view->width) {
2409                 size_t refsize = strlen(view->ref);
2410                 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2412                 if (minsize < view->width)
2413                         refsize = view->width - minsize + 7;
2414                 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2415         }
2417         if (statelen && bufpos < view->width) {
2418                 string_format_from(buf, &bufpos, "%s", state);
2419         }
2421         if (view == display[current_view])
2422                 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2423         else
2424                 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2426         mvwaddnstr(view->title, 0, 0, buf, bufpos);
2427         wclrtoeol(view->title);
2428         wnoutrefresh(view->title);
2431 static int
2432 apply_step(double step, int value)
2434         if (step >= 1)
2435                 return (int) step;
2436         value *= step + 0.01;
2437         return value ? value : 1;
2440 static void
2441 resize_display(void)
2443         int offset, i;
2444         struct view *base = display[0];
2445         struct view *view = display[1] ? display[1] : display[0];
2447         /* Setup window dimensions */
2449         getmaxyx(stdscr, base->height, base->width);
2451         /* Make room for the status window. */
2452         base->height -= 1;
2454         if (view != base) {
2455                 /* Horizontal split. */
2456                 view->width   = base->width;
2457                 view->height  = apply_step(opt_scale_split_view, base->height);
2458                 view->height  = MAX(view->height, MIN_VIEW_HEIGHT);
2459                 view->height  = MIN(view->height, base->height - MIN_VIEW_HEIGHT);
2460                 base->height -= view->height;
2462                 /* Make room for the title bar. */
2463                 view->height -= 1;
2464         }
2466         /* Make room for the title bar. */
2467         base->height -= 1;
2469         offset = 0;
2471         foreach_displayed_view (view, i) {
2472                 if (!view->win) {
2473                         view->win = newwin(view->height, 0, offset, 0);
2474                         if (!view->win)
2475                                 die("Failed to create %s view", view->name);
2477                         scrollok(view->win, FALSE);
2479                         view->title = newwin(1, 0, offset + view->height, 0);
2480                         if (!view->title)
2481                                 die("Failed to create title window");
2483                 } else {
2484                         wresize(view->win, view->height, view->width);
2485                         mvwin(view->win,   offset, 0);
2486                         mvwin(view->title, offset + view->height, 0);
2487                 }
2489                 offset += view->height + 1;
2490         }
2493 static void
2494 redraw_display(bool clear)
2496         struct view *view;
2497         int i;
2499         foreach_displayed_view (view, i) {
2500                 if (clear)
2501                         wclear(view->win);
2502                 redraw_view(view);
2503                 update_view_title(view);
2504         }
2507 static void
2508 toggle_enum_option_do(unsigned int *opt, const char *help,
2509                       const struct enum_map *map, size_t size)
2511         *opt = (*opt + 1) % size;
2512         redraw_display(FALSE);
2513         report("Displaying %s %s", enum_name(map[*opt]), help);
2516 #define toggle_enum_option(opt, help, map) \
2517         toggle_enum_option_do(opt, help, map, ARRAY_SIZE(map))
2519 #define toggle_date() toggle_enum_option(&opt_date, "dates", date_map)
2520 #define toggle_author() toggle_enum_option(&opt_author, "author names", author_map)
2522 static void
2523 toggle_view_option(bool *option, const char *help)
2525         *option = !*option;
2526         redraw_display(FALSE);
2527         report("%sabling %s", *option ? "En" : "Dis", help);
2530 static void
2531 open_option_menu(void)
2533         const struct menu_item menu[] = {
2534                 { '.', "line numbers", &opt_line_number },
2535                 { 'D', "date display", &opt_date },
2536                 { 'A', "author display", &opt_author },
2537                 { 'g', "revision graph display", &opt_rev_graph },
2538                 { 'F', "reference display", &opt_show_refs },
2539                 { 0 }
2540         };
2541         int selected = 0;
2543         if (prompt_menu("Toggle option", menu, &selected)) {
2544                 if (menu[selected].data == &opt_date)
2545                         toggle_date();
2546                 else if (menu[selected].data == &opt_author)
2547                         toggle_author();
2548                 else
2549                         toggle_view_option(menu[selected].data, menu[selected].text);
2550         }
2553 static void
2554 maximize_view(struct view *view)
2556         memset(display, 0, sizeof(display));
2557         current_view = 0;
2558         display[current_view] = view;
2559         resize_display();
2560         redraw_display(FALSE);
2561         report("");
2565 /*
2566  * Navigation
2567  */
2569 static bool
2570 goto_view_line(struct view *view, unsigned long offset, unsigned long lineno)
2572         if (lineno >= view->lines)
2573                 lineno = view->lines > 0 ? view->lines - 1 : 0;
2575         if (offset > lineno || offset + view->height <= lineno) {
2576                 unsigned long half = view->height / 2;
2578                 if (lineno > half)
2579                         offset = lineno - half;
2580                 else
2581                         offset = 0;
2582         }
2584         if (offset != view->offset || lineno != view->lineno) {
2585                 view->offset = offset;
2586                 view->lineno = lineno;
2587                 return TRUE;
2588         }
2590         return FALSE;
2593 /* Scrolling backend */
2594 static void
2595 do_scroll_view(struct view *view, int lines)
2597         bool redraw_current_line = FALSE;
2599         /* The rendering expects the new offset. */
2600         view->offset += lines;
2602         assert(0 <= view->offset && view->offset < view->lines);
2603         assert(lines);
2605         /* Move current line into the view. */
2606         if (view->lineno < view->offset) {
2607                 view->lineno = view->offset;
2608                 redraw_current_line = TRUE;
2609         } else if (view->lineno >= view->offset + view->height) {
2610                 view->lineno = view->offset + view->height - 1;
2611                 redraw_current_line = TRUE;
2612         }
2614         assert(view->offset <= view->lineno && view->lineno < view->lines);
2616         /* Redraw the whole screen if scrolling is pointless. */
2617         if (view->height < ABS(lines)) {
2618                 redraw_view(view);
2620         } else {
2621                 int line = lines > 0 ? view->height - lines : 0;
2622                 int end = line + ABS(lines);
2624                 scrollok(view->win, TRUE);
2625                 wscrl(view->win, lines);
2626                 scrollok(view->win, FALSE);
2628                 while (line < end && draw_view_line(view, line))
2629                         line++;
2631                 if (redraw_current_line)
2632                         draw_view_line(view, view->lineno - view->offset);
2633                 wnoutrefresh(view->win);
2634         }
2636         view->has_scrolled = TRUE;
2637         report("");
2640 /* Scroll frontend */
2641 static void
2642 scroll_view(struct view *view, enum request request)
2644         int lines = 1;
2646         assert(view_is_displayed(view));
2648         switch (request) {
2649         case REQ_SCROLL_LEFT:
2650                 if (view->yoffset == 0) {
2651                         report("Cannot scroll beyond the first column");
2652                         return;
2653                 }
2654                 if (view->yoffset <= apply_step(opt_hscroll, view->width))
2655                         view->yoffset = 0;
2656                 else
2657                         view->yoffset -= apply_step(opt_hscroll, view->width);
2658                 redraw_view_from(view, 0);
2659                 report("");
2660                 return;
2661         case REQ_SCROLL_RIGHT:
2662                 view->yoffset += apply_step(opt_hscroll, view->width);
2663                 redraw_view(view);
2664                 report("");
2665                 return;
2666         case REQ_SCROLL_PAGE_DOWN:
2667                 lines = view->height;
2668         case REQ_SCROLL_LINE_DOWN:
2669                 if (view->offset + lines > view->lines)
2670                         lines = view->lines - view->offset;
2672                 if (lines == 0 || view->offset + view->height >= view->lines) {
2673                         report("Cannot scroll beyond the last line");
2674                         return;
2675                 }
2676                 break;
2678         case REQ_SCROLL_PAGE_UP:
2679                 lines = view->height;
2680         case REQ_SCROLL_LINE_UP:
2681                 if (lines > view->offset)
2682                         lines = view->offset;
2684                 if (lines == 0) {
2685                         report("Cannot scroll beyond the first line");
2686                         return;
2687                 }
2689                 lines = -lines;
2690                 break;
2692         default:
2693                 die("request %d not handled in switch", request);
2694         }
2696         do_scroll_view(view, lines);
2699 /* Cursor moving */
2700 static void
2701 move_view(struct view *view, enum request request)
2703         int scroll_steps = 0;
2704         int steps;
2706         switch (request) {
2707         case REQ_MOVE_FIRST_LINE:
2708                 steps = -view->lineno;
2709                 break;
2711         case REQ_MOVE_LAST_LINE:
2712                 steps = view->lines - view->lineno - 1;
2713                 break;
2715         case REQ_MOVE_PAGE_UP:
2716                 steps = view->height > view->lineno
2717                       ? -view->lineno : -view->height;
2718                 break;
2720         case REQ_MOVE_PAGE_DOWN:
2721                 steps = view->lineno + view->height >= view->lines
2722                       ? view->lines - view->lineno - 1 : view->height;
2723                 break;
2725         case REQ_MOVE_UP:
2726                 steps = -1;
2727                 break;
2729         case REQ_MOVE_DOWN:
2730                 steps = 1;
2731                 break;
2733         default:
2734                 die("request %d not handled in switch", request);
2735         }
2737         if (steps <= 0 && view->lineno == 0) {
2738                 report("Cannot move beyond the first line");
2739                 return;
2741         } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2742                 report("Cannot move beyond the last line");
2743                 return;
2744         }
2746         /* Move the current line */
2747         view->lineno += steps;
2748         assert(0 <= view->lineno && view->lineno < view->lines);
2750         /* Check whether the view needs to be scrolled */
2751         if (view->lineno < view->offset ||
2752             view->lineno >= view->offset + view->height) {
2753                 scroll_steps = steps;
2754                 if (steps < 0 && -steps > view->offset) {
2755                         scroll_steps = -view->offset;
2757                 } else if (steps > 0) {
2758                         if (view->lineno == view->lines - 1 &&
2759                             view->lines > view->height) {
2760                                 scroll_steps = view->lines - view->offset - 1;
2761                                 if (scroll_steps >= view->height)
2762                                         scroll_steps -= view->height - 1;
2763                         }
2764                 }
2765         }
2767         if (!view_is_displayed(view)) {
2768                 view->offset += scroll_steps;
2769                 assert(0 <= view->offset && view->offset < view->lines);
2770                 view->ops->select(view, &view->line[view->lineno]);
2771                 return;
2772         }
2774         /* Repaint the old "current" line if we be scrolling */
2775         if (ABS(steps) < view->height)
2776                 draw_view_line(view, view->lineno - steps - view->offset);
2778         if (scroll_steps) {
2779                 do_scroll_view(view, scroll_steps);
2780                 return;
2781         }
2783         /* Draw the current line */
2784         draw_view_line(view, view->lineno - view->offset);
2786         wnoutrefresh(view->win);
2787         report("");
2791 /*
2792  * Searching
2793  */
2795 static void search_view(struct view *view, enum request request);
2797 static bool
2798 grep_text(struct view *view, const char *text[])
2800         regmatch_t pmatch;
2801         size_t i;
2803         for (i = 0; text[i]; i++)
2804                 if (*text[i] &&
2805                     regexec(view->regex, text[i], 1, &pmatch, 0) != REG_NOMATCH)
2806                         return TRUE;
2807         return FALSE;
2810 static void
2811 select_view_line(struct view *view, unsigned long lineno)
2813         unsigned long old_lineno = view->lineno;
2814         unsigned long old_offset = view->offset;
2816         if (goto_view_line(view, view->offset, lineno)) {
2817                 if (view_is_displayed(view)) {
2818                         if (old_offset != view->offset) {
2819                                 redraw_view(view);
2820                         } else {
2821                                 draw_view_line(view, old_lineno - view->offset);
2822                                 draw_view_line(view, view->lineno - view->offset);
2823                                 wnoutrefresh(view->win);
2824                         }
2825                 } else {
2826                         view->ops->select(view, &view->line[view->lineno]);
2827                 }
2828         }
2831 static void
2832 find_next(struct view *view, enum request request)
2834         unsigned long lineno = view->lineno;
2835         int direction;
2837         if (!*view->grep) {
2838                 if (!*opt_search)
2839                         report("No previous search");
2840                 else
2841                         search_view(view, request);
2842                 return;
2843         }
2845         switch (request) {
2846         case REQ_SEARCH:
2847         case REQ_FIND_NEXT:
2848                 direction = 1;
2849                 break;
2851         case REQ_SEARCH_BACK:
2852         case REQ_FIND_PREV:
2853                 direction = -1;
2854                 break;
2856         default:
2857                 return;
2858         }
2860         if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
2861                 lineno += direction;
2863         /* Note, lineno is unsigned long so will wrap around in which case it
2864          * will become bigger than view->lines. */
2865         for (; lineno < view->lines; lineno += direction) {
2866                 if (view->ops->grep(view, &view->line[lineno])) {
2867                         select_view_line(view, lineno);
2868                         report("Line %ld matches '%s'", lineno + 1, view->grep);
2869                         return;
2870                 }
2871         }
2873         report("No match found for '%s'", view->grep);
2876 static void
2877 search_view(struct view *view, enum request request)
2879         int regex_err;
2881         if (view->regex) {
2882                 regfree(view->regex);
2883                 *view->grep = 0;
2884         } else {
2885                 view->regex = calloc(1, sizeof(*view->regex));
2886                 if (!view->regex)
2887                         return;
2888         }
2890         regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
2891         if (regex_err != 0) {
2892                 char buf[SIZEOF_STR] = "unknown error";
2894                 regerror(regex_err, view->regex, buf, sizeof(buf));
2895                 report("Search failed: %s", buf);
2896                 return;
2897         }
2899         string_copy(view->grep, opt_search);
2901         find_next(view, request);
2904 /*
2905  * Incremental updating
2906  */
2908 static void
2909 reset_view(struct view *view)
2911         int i;
2913         for (i = 0; i < view->lines; i++)
2914                 free(view->line[i].data);
2915         free(view->line);
2917         view->p_offset = view->offset;
2918         view->p_yoffset = view->yoffset;
2919         view->p_lineno = view->lineno;
2921         view->line = NULL;
2922         view->offset = 0;
2923         view->yoffset = 0;
2924         view->lines  = 0;
2925         view->lineno = 0;
2926         view->vid[0] = 0;
2927         view->update_secs = 0;
2930 static void
2931 free_argv(const char *argv[])
2933         int argc;
2935         for (argc = 0; argv[argc]; argc++)
2936                 free((void *) argv[argc]);
2939 static const char *
2940 format_arg(const char *name)
2942         static struct {
2943                 const char *name;
2944                 size_t namelen;
2945                 const char *value;
2946                 const char *value_if_empty;
2947         } vars[] = {
2948 #define FORMAT_VAR(name, value, value_if_empty) \
2949         { name, STRING_SIZE(name), value, value_if_empty }
2950                 FORMAT_VAR("%(directory)",      opt_path,       ""),
2951                 FORMAT_VAR("%(file)",           opt_file,       ""),
2952                 FORMAT_VAR("%(ref)",            opt_ref,        "HEAD"),
2953                 FORMAT_VAR("%(head)",           ref_head,       ""),
2954                 FORMAT_VAR("%(commit)",         ref_commit,     ""),
2955                 FORMAT_VAR("%(blob)",           ref_blob,       ""),
2956         };
2957         int i;
2959         for (i = 0; i < ARRAY_SIZE(vars); i++)
2960                 if (!strncmp(name, vars[i].name, vars[i].namelen))
2961                         return *vars[i].value ? vars[i].value : vars[i].value_if_empty;
2963         return NULL;
2965 static bool
2966 format_argv(const char *dst_argv[], const char *src_argv[], enum format_flags flags)
2968         char buf[SIZEOF_STR];
2969         int argc;
2970         bool noreplace = flags == FORMAT_NONE;
2972         free_argv(dst_argv);
2974         for (argc = 0; src_argv[argc]; argc++) {
2975                 const char *arg = src_argv[argc];
2976                 size_t bufpos = 0;
2978                 while (arg) {
2979                         char *next = strstr(arg, "%(");
2980                         int len = next - arg;
2981                         const char *value;
2983                         if (!next || noreplace) {
2984                                 if (flags == FORMAT_DASH && !strcmp(arg, "--"))
2985                                         noreplace = TRUE;
2986                                 len = strlen(arg);
2987                                 value = "";
2989                         } else {
2990                                 value = format_arg(next);
2992                                 if (!value) {
2993                                         report("Unknown replacement: `%s`", next);
2994                                         return FALSE;
2995                                 }
2996                         }
2998                         if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
2999                                 return FALSE;
3001                         arg = next && !noreplace ? strchr(next, ')') + 1 : NULL;
3002                 }
3004                 dst_argv[argc] = strdup(buf);
3005                 if (!dst_argv[argc])
3006                         break;
3007         }
3009         dst_argv[argc] = NULL;
3011         return src_argv[argc] == NULL;
3014 static bool
3015 restore_view_position(struct view *view)
3017         if (!view->p_restore || (view->pipe && view->lines <= view->p_lineno))
3018                 return FALSE;
3020         /* Changing the view position cancels the restoring. */
3021         /* FIXME: Changing back to the first line is not detected. */
3022         if (view->offset != 0 || view->lineno != 0) {
3023                 view->p_restore = FALSE;
3024                 return FALSE;
3025         }
3027         if (goto_view_line(view, view->p_offset, view->p_lineno) &&
3028             view_is_displayed(view))
3029                 werase(view->win);
3031         view->yoffset = view->p_yoffset;
3032         view->p_restore = FALSE;
3034         return TRUE;
3037 static void
3038 end_update(struct view *view, bool force)
3040         if (!view->pipe)
3041                 return;
3042         while (!view->ops->read(view, NULL))
3043                 if (!force)
3044                         return;
3045         set_nonblocking_input(FALSE);
3046         if (force)
3047                 kill_io(view->pipe);
3048         done_io(view->pipe);
3049         view->pipe = NULL;
3052 static void
3053 setup_update(struct view *view, const char *vid)
3055         set_nonblocking_input(TRUE);
3056         reset_view(view);
3057         string_copy_rev(view->vid, vid);
3058         view->pipe = &view->io;
3059         view->start_time = time(NULL);
3062 static bool
3063 prepare_update(struct view *view, const char *argv[], const char *dir,
3064                enum format_flags flags)
3066         if (view->pipe)
3067                 end_update(view, TRUE);
3068         return init_io_rd(&view->io, argv, dir, flags);
3071 static bool
3072 prepare_update_file(struct view *view, const char *name)
3074         if (view->pipe)
3075                 end_update(view, TRUE);
3076         return io_open(&view->io, "%s/%s", opt_cdup[0] ? opt_cdup : ".", name);
3079 static bool
3080 begin_update(struct view *view, bool refresh)
3082         if (view->pipe)
3083                 end_update(view, TRUE);
3085         if (!refresh) {
3086                 if (view->ops->prepare) {
3087                         if (!view->ops->prepare(view))
3088                                 return FALSE;
3089                 } else if (!init_io_rd(&view->io, view->ops->argv, NULL, FORMAT_ALL)) {
3090                         return FALSE;
3091                 }
3093                 /* Put the current ref_* value to the view title ref
3094                  * member. This is needed by the blob view. Most other
3095                  * views sets it automatically after loading because the
3096                  * first line is a commit line. */
3097                 string_copy_rev(view->ref, view->id);
3098         }
3100         if (!start_io(&view->io))
3101                 return FALSE;
3103         setup_update(view, view->id);
3105         return TRUE;
3108 static bool
3109 update_view(struct view *view)
3111         char out_buffer[BUFSIZ * 2];
3112         char *line;
3113         /* Clear the view and redraw everything since the tree sorting
3114          * might have rearranged things. */
3115         bool redraw = view->lines == 0;
3116         bool can_read = TRUE;
3118         if (!view->pipe)
3119                 return TRUE;
3121         if (!io_can_read(view->pipe)) {
3122                 if (view->lines == 0 && view_is_displayed(view)) {
3123                         time_t secs = time(NULL) - view->start_time;
3125                         if (secs > 1 && secs > view->update_secs) {
3126                                 if (view->update_secs == 0)
3127                                         redraw_view(view);
3128                                 update_view_title(view);
3129                                 view->update_secs = secs;
3130                         }
3131                 }
3132                 return TRUE;
3133         }
3135         for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
3136                 if (opt_iconv_in != ICONV_NONE) {
3137                         ICONV_CONST char *inbuf = line;
3138                         size_t inlen = strlen(line) + 1;
3140                         char *outbuf = out_buffer;
3141                         size_t outlen = sizeof(out_buffer);
3143                         size_t ret;
3145                         ret = iconv(opt_iconv_in, &inbuf, &inlen, &outbuf, &outlen);
3146                         if (ret != (size_t) -1)
3147                                 line = out_buffer;
3148                 }
3150                 if (!view->ops->read(view, line)) {
3151                         report("Allocation failure");
3152                         end_update(view, TRUE);
3153                         return FALSE;
3154                 }
3155         }
3157         {
3158                 unsigned long lines = view->lines;
3159                 int digits;
3161                 for (digits = 0; lines; digits++)
3162                         lines /= 10;
3164                 /* Keep the displayed view in sync with line number scaling. */
3165                 if (digits != view->digits) {
3166                         view->digits = digits;
3167                         if (opt_line_number || view == VIEW(REQ_VIEW_BLAME))
3168                                 redraw = TRUE;
3169                 }
3170         }
3172         if (io_error(view->pipe)) {
3173                 report("Failed to read: %s", io_strerror(view->pipe));
3174                 end_update(view, TRUE);
3176         } else if (io_eof(view->pipe)) {
3177                 report("");
3178                 end_update(view, FALSE);
3179         }
3181         if (restore_view_position(view))
3182                 redraw = TRUE;
3184         if (!view_is_displayed(view))
3185                 return TRUE;
3187         if (redraw)
3188                 redraw_view_from(view, 0);
3189         else
3190                 redraw_view_dirty(view);
3192         /* Update the title _after_ the redraw so that if the redraw picks up a
3193          * commit reference in view->ref it'll be available here. */
3194         update_view_title(view);
3195         return TRUE;
3198 DEFINE_ALLOCATOR(realloc_lines, struct line, 256)
3200 static struct line *
3201 add_line_data(struct view *view, void *data, enum line_type type)
3203         struct line *line;
3205         if (!realloc_lines(&view->line, view->lines, 1))
3206                 return NULL;
3208         line = &view->line[view->lines++];
3209         memset(line, 0, sizeof(*line));
3210         line->type = type;
3211         line->data = data;
3212         line->dirty = 1;
3214         return line;
3217 static struct line *
3218 add_line_text(struct view *view, const char *text, enum line_type type)
3220         char *data = text ? strdup(text) : NULL;
3222         return data ? add_line_data(view, data, type) : NULL;
3225 static struct line *
3226 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
3228         char buf[SIZEOF_STR];
3229         va_list args;
3231         va_start(args, fmt);
3232         if (vsnprintf(buf, sizeof(buf), fmt, args) >= sizeof(buf))
3233                 buf[0] = 0;
3234         va_end(args);
3236         return buf[0] ? add_line_text(view, buf, type) : NULL;
3239 /*
3240  * View opening
3241  */
3243 enum open_flags {
3244         OPEN_DEFAULT = 0,       /* Use default view switching. */
3245         OPEN_SPLIT = 1,         /* Split current view. */
3246         OPEN_RELOAD = 4,        /* Reload view even if it is the current. */
3247         OPEN_REFRESH = 16,      /* Refresh view using previous command. */
3248         OPEN_PREPARED = 32,     /* Open already prepared command. */
3249 };
3251 static void
3252 open_view(struct view *prev, enum request request, enum open_flags flags)
3254         bool split = !!(flags & OPEN_SPLIT);
3255         bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
3256         bool nomaximize = !!(flags & OPEN_REFRESH);
3257         struct view *view = VIEW(request);
3258         int nviews = displayed_views();
3259         struct view *base_view = display[0];
3261         if (view == prev && nviews == 1 && !reload) {
3262                 report("Already in %s view", view->name);
3263                 return;
3264         }
3266         if (view->git_dir && !opt_git_dir[0]) {
3267                 report("The %s view is disabled in pager view", view->name);
3268                 return;
3269         }
3271         if (split) {
3272                 display[1] = view;
3273                 current_view = 1;
3274         } else if (!nomaximize) {
3275                 /* Maximize the current view. */
3276                 memset(display, 0, sizeof(display));
3277                 current_view = 0;
3278                 display[current_view] = view;
3279         }
3281         /* No parent signals that this is the first loaded view. */
3282         if (prev && view != prev) {
3283                 view->parent = prev;
3284         }
3286         /* Resize the view when switching between split- and full-screen,
3287          * or when switching between two different full-screen views. */
3288         if (nviews != displayed_views() ||
3289             (nviews == 1 && base_view != display[0]))
3290                 resize_display();
3292         if (view->ops->open) {
3293                 if (view->pipe)
3294                         end_update(view, TRUE);
3295                 if (!view->ops->open(view)) {
3296                         report("Failed to load %s view", view->name);
3297                         return;
3298                 }
3299                 restore_view_position(view);
3301         } else if ((reload || strcmp(view->vid, view->id)) &&
3302                    !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
3303                 report("Failed to load %s view", view->name);
3304                 return;
3305         }
3307         if (split && prev->lineno - prev->offset >= prev->height) {
3308                 /* Take the title line into account. */
3309                 int lines = prev->lineno - prev->offset - prev->height + 1;
3311                 /* Scroll the view that was split if the current line is
3312                  * outside the new limited view. */
3313                 do_scroll_view(prev, lines);
3314         }
3316         if (prev && view != prev && split && view_is_displayed(prev)) {
3317                 /* "Blur" the previous view. */
3318                 update_view_title(prev);
3319         }
3321         if (view->pipe && view->lines == 0) {
3322                 /* Clear the old view and let the incremental updating refill
3323                  * the screen. */
3324                 werase(view->win);
3325                 view->p_restore = flags & (OPEN_RELOAD | OPEN_REFRESH);
3326                 report("");
3327         } else if (view_is_displayed(view)) {
3328                 redraw_view(view);
3329                 report("");
3330         }
3333 static void
3334 open_external_viewer(const char *argv[], const char *dir)
3336         def_prog_mode();           /* save current tty modes */
3337         endwin();                  /* restore original tty modes */
3338         run_io_fg(argv, dir);
3339         fprintf(stderr, "Press Enter to continue");
3340         getc(opt_tty);
3341         reset_prog_mode();
3342         redraw_display(TRUE);
3345 static void
3346 open_mergetool(const char *file)
3348         const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
3350         open_external_viewer(mergetool_argv, opt_cdup);
3353 static void
3354 open_editor(const char *file)
3356         const char *editor_argv[] = { "vi", file, NULL };
3357         const char *editor;
3359         editor = getenv("GIT_EDITOR");
3360         if (!editor && *opt_editor)
3361                 editor = opt_editor;
3362         if (!editor)
3363                 editor = getenv("VISUAL");
3364         if (!editor)
3365                 editor = getenv("EDITOR");
3366         if (!editor)
3367                 editor = "vi";
3369         editor_argv[0] = editor;
3370         open_external_viewer(editor_argv, opt_cdup);
3373 static void
3374 open_run_request(enum request request)
3376         struct run_request *req = get_run_request(request);
3377         const char *argv[ARRAY_SIZE(req->argv)] = { NULL };
3379         if (!req) {
3380                 report("Unknown run request");
3381                 return;
3382         }
3384         if (format_argv(argv, req->argv, FORMAT_ALL))
3385                 open_external_viewer(argv, NULL);
3386         free_argv(argv);
3389 /*
3390  * User request switch noodle
3391  */
3393 static int
3394 view_driver(struct view *view, enum request request)
3396         int i;
3398         if (request == REQ_NONE)
3399                 return TRUE;
3401         if (request > REQ_NONE) {
3402                 open_run_request(request);
3403                 /* FIXME: When all views can refresh always do this. */
3404                 if (view == VIEW(REQ_VIEW_STATUS) ||
3405                     view == VIEW(REQ_VIEW_MAIN) ||
3406                     view == VIEW(REQ_VIEW_LOG) ||
3407                     view == VIEW(REQ_VIEW_BRANCH) ||
3408                     view == VIEW(REQ_VIEW_STAGE))
3409                         request = REQ_REFRESH;
3410                 else
3411                         return TRUE;
3412         }
3414         if (view && view->lines) {
3415                 request = view->ops->request(view, request, &view->line[view->lineno]);
3416                 if (request == REQ_NONE)
3417                         return TRUE;
3418         }
3420         switch (request) {
3421         case REQ_MOVE_UP:
3422         case REQ_MOVE_DOWN:
3423         case REQ_MOVE_PAGE_UP:
3424         case REQ_MOVE_PAGE_DOWN:
3425         case REQ_MOVE_FIRST_LINE:
3426         case REQ_MOVE_LAST_LINE:
3427                 move_view(view, request);
3428                 break;
3430         case REQ_SCROLL_LEFT:
3431         case REQ_SCROLL_RIGHT:
3432         case REQ_SCROLL_LINE_DOWN:
3433         case REQ_SCROLL_LINE_UP:
3434         case REQ_SCROLL_PAGE_DOWN:
3435         case REQ_SCROLL_PAGE_UP:
3436                 scroll_view(view, request);
3437                 break;
3439         case REQ_VIEW_BLAME:
3440                 if (!opt_file[0]) {
3441                         report("No file chosen, press %s to open tree view",
3442                                get_key(view->keymap, REQ_VIEW_TREE));
3443                         break;
3444                 }
3445                 open_view(view, request, OPEN_DEFAULT);
3446                 break;
3448         case REQ_VIEW_BLOB:
3449                 if (!ref_blob[0]) {
3450                         report("No file chosen, press %s to open tree view",
3451                                get_key(view->keymap, REQ_VIEW_TREE));
3452                         break;
3453                 }
3454                 open_view(view, request, OPEN_DEFAULT);
3455                 break;
3457         case REQ_VIEW_PAGER:
3458                 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3459                         report("No pager content, press %s to run command from prompt",
3460                                get_key(view->keymap, REQ_PROMPT));
3461                         break;
3462                 }
3463                 open_view(view, request, OPEN_DEFAULT);
3464                 break;
3466         case REQ_VIEW_STAGE:
3467                 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3468                         report("No stage content, press %s to open the status view and choose file",
3469                                get_key(view->keymap, REQ_VIEW_STATUS));
3470                         break;
3471                 }
3472                 open_view(view, request, OPEN_DEFAULT);
3473                 break;
3475         case REQ_VIEW_STATUS:
3476                 if (opt_is_inside_work_tree == FALSE) {
3477                         report("The status view requires a working tree");
3478                         break;
3479                 }
3480                 open_view(view, request, OPEN_DEFAULT);
3481                 break;
3483         case REQ_VIEW_MAIN:
3484         case REQ_VIEW_DIFF:
3485         case REQ_VIEW_LOG:
3486         case REQ_VIEW_TREE:
3487         case REQ_VIEW_HELP:
3488         case REQ_VIEW_BRANCH:
3489                 open_view(view, request, OPEN_DEFAULT);
3490                 break;
3492         case REQ_NEXT:
3493         case REQ_PREVIOUS:
3494                 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3496                 if ((view == VIEW(REQ_VIEW_DIFF) &&
3497                      view->parent == VIEW(REQ_VIEW_MAIN)) ||
3498                    (view == VIEW(REQ_VIEW_DIFF) &&
3499                      view->parent == VIEW(REQ_VIEW_BLAME)) ||
3500                    (view == VIEW(REQ_VIEW_STAGE) &&
3501                      view->parent == VIEW(REQ_VIEW_STATUS)) ||
3502                    (view == VIEW(REQ_VIEW_BLOB) &&
3503                      view->parent == VIEW(REQ_VIEW_TREE)) ||
3504                    (view == VIEW(REQ_VIEW_MAIN) &&
3505                      view->parent == VIEW(REQ_VIEW_BRANCH))) {
3506                         int line;
3508                         view = view->parent;
3509                         line = view->lineno;
3510                         move_view(view, request);
3511                         if (view_is_displayed(view))
3512                                 update_view_title(view);
3513                         if (line != view->lineno)
3514                                 view->ops->request(view, REQ_ENTER,
3515                                                    &view->line[view->lineno]);
3517                 } else {
3518                         move_view(view, request);
3519                 }
3520                 break;
3522         case REQ_VIEW_NEXT:
3523         {
3524                 int nviews = displayed_views();
3525                 int next_view = (current_view + 1) % nviews;
3527                 if (next_view == current_view) {
3528                         report("Only one view is displayed");
3529                         break;
3530                 }
3532                 current_view = next_view;
3533                 /* Blur out the title of the previous view. */
3534                 update_view_title(view);
3535                 report("");
3536                 break;
3537         }
3538         case REQ_REFRESH:
3539                 report("Refreshing is not yet supported for the %s view", view->name);
3540                 break;
3542         case REQ_MAXIMIZE:
3543                 if (displayed_views() == 2)
3544                         maximize_view(view);
3545                 break;
3547         case REQ_OPTIONS:
3548                 open_option_menu();
3549                 break;
3551         case REQ_TOGGLE_LINENO:
3552                 toggle_view_option(&opt_line_number, "line numbers");
3553                 break;
3555         case REQ_TOGGLE_DATE:
3556                 toggle_date();
3557                 break;
3559         case REQ_TOGGLE_AUTHOR:
3560                 toggle_author();
3561                 break;
3563         case REQ_TOGGLE_REV_GRAPH:
3564                 toggle_view_option(&opt_rev_graph, "revision graph display");
3565                 break;
3567         case REQ_TOGGLE_REFS:
3568                 toggle_view_option(&opt_show_refs, "reference display");
3569                 break;
3571         case REQ_TOGGLE_SORT_FIELD:
3572         case REQ_TOGGLE_SORT_ORDER:
3573                 report("Sorting is not yet supported for the %s view", view->name);
3574                 break;
3576         case REQ_SEARCH:
3577         case REQ_SEARCH_BACK:
3578                 search_view(view, request);
3579                 break;
3581         case REQ_FIND_NEXT:
3582         case REQ_FIND_PREV:
3583                 find_next(view, request);
3584                 break;
3586         case REQ_STOP_LOADING:
3587                 for (i = 0; i < ARRAY_SIZE(views); i++) {
3588                         view = &views[i];
3589                         if (view->pipe)
3590                                 report("Stopped loading the %s view", view->name),
3591                         end_update(view, TRUE);
3592                 }
3593                 break;
3595         case REQ_SHOW_VERSION:
3596                 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3597                 return TRUE;
3599         case REQ_SCREEN_REDRAW:
3600                 redraw_display(TRUE);
3601                 break;
3603         case REQ_EDIT:
3604                 report("Nothing to edit");
3605                 break;
3607         case REQ_ENTER:
3608                 report("Nothing to enter");
3609                 break;
3611         case REQ_VIEW_CLOSE:
3612                 /* XXX: Mark closed views by letting view->parent point to the
3613                  * view itself. Parents to closed view should never be
3614                  * followed. */
3615                 if (view->parent &&
3616                     view->parent->parent != view->parent) {
3617                         maximize_view(view->parent);
3618                         view->parent = view;
3619                         break;
3620                 }
3621                 /* Fall-through */
3622         case REQ_QUIT:
3623                 return FALSE;
3625         default:
3626                 report("Unknown key, press %s for help",
3627                        get_key(view->keymap, REQ_VIEW_HELP));
3628                 return TRUE;
3629         }
3631         return TRUE;
3635 /*
3636  * View backend utilities
3637  */
3639 enum sort_field {
3640         ORDERBY_NAME,
3641         ORDERBY_DATE,
3642         ORDERBY_AUTHOR,
3643 };
3645 struct sort_state {
3646         const enum sort_field *fields;
3647         size_t size, current;
3648         bool reverse;
3649 };
3651 #define SORT_STATE(fields) { fields, ARRAY_SIZE(fields), 0 }
3652 #define get_sort_field(state) ((state).fields[(state).current])
3653 #define sort_order(state, result) ((state).reverse ? -(result) : (result))
3655 static void
3656 sort_view(struct view *view, enum request request, struct sort_state *state,
3657           int (*compare)(const void *, const void *))
3659         switch (request) {
3660         case REQ_TOGGLE_SORT_FIELD:
3661                 state->current = (state->current + 1) % state->size;
3662                 break;
3664         case REQ_TOGGLE_SORT_ORDER:
3665                 state->reverse = !state->reverse;
3666                 break;
3667         default:
3668                 die("Not a sort request");
3669         }
3671         qsort(view->line, view->lines, sizeof(*view->line), compare);
3672         redraw_view(view);
3675 DEFINE_ALLOCATOR(realloc_authors, const char *, 256)
3677 /* Small author cache to reduce memory consumption. It uses binary
3678  * search to lookup or find place to position new entries. No entries
3679  * are ever freed. */
3680 static const char *
3681 get_author(const char *name)
3683         static const char **authors;
3684         static size_t authors_size;
3685         int from = 0, to = authors_size - 1;
3687         while (from <= to) {
3688                 size_t pos = (to + from) / 2;
3689                 int cmp = strcmp(name, authors[pos]);
3691                 if (!cmp)
3692                         return authors[pos];
3694                 if (cmp < 0)
3695                         to = pos - 1;
3696                 else
3697                         from = pos + 1;
3698         }
3700         if (!realloc_authors(&authors, authors_size, 1))
3701                 return NULL;
3702         name = strdup(name);
3703         if (!name)
3704                 return NULL;
3706         memmove(authors + from + 1, authors + from, (authors_size - from) * sizeof(*authors));
3707         authors[from] = name;
3708         authors_size++;
3710         return name;
3713 static void
3714 parse_timesec(struct time *time, const char *sec)
3716         time->sec = (time_t) atol(sec);
3719 static void
3720 parse_timezone(struct time *time, const char *zone)
3722         long tz;
3724         tz  = ('0' - zone[1]) * 60 * 60 * 10;
3725         tz += ('0' - zone[2]) * 60 * 60;
3726         tz += ('0' - zone[3]) * 60;
3727         tz += ('0' - zone[4]);
3729         if (zone[0] == '-')
3730                 tz = -tz;
3732         time->tz = tz;
3733         time->sec -= tz;
3736 /* Parse author lines where the name may be empty:
3737  *      author  <email@address.tld> 1138474660 +0100
3738  */
3739 static void
3740 parse_author_line(char *ident, const char **author, struct time *time)
3742         char *nameend = strchr(ident, '<');
3743         char *emailend = strchr(ident, '>');
3745         if (nameend && emailend)
3746                 *nameend = *emailend = 0;
3747         ident = chomp_string(ident);
3748         if (!*ident) {
3749                 if (nameend)
3750                         ident = chomp_string(nameend + 1);
3751                 if (!*ident)
3752                         ident = "Unknown";
3753         }
3755         *author = get_author(ident);
3757         /* Parse epoch and timezone */
3758         if (emailend && emailend[1] == ' ') {
3759                 char *secs = emailend + 2;
3760                 char *zone = strchr(secs, ' ');
3762                 parse_timesec(time, secs);
3764                 if (zone && strlen(zone) == STRING_SIZE(" +0700"))
3765                         parse_timezone(time, zone + 1);
3766         }
3769 static bool
3770 open_commit_parent_menu(char buf[SIZEOF_STR], int *parents)
3772         char rev[SIZEOF_REV];
3773         const char *revlist_argv[] = {
3774                 "git", "log", "--no-color", "-1", "--pretty=format:%s", rev, NULL
3775         };
3776         struct menu_item *items;
3777         char text[SIZEOF_STR];
3778         bool ok = TRUE;
3779         int i;
3781         items = calloc(*parents + 1, sizeof(*items));
3782         if (!items)
3783                 return FALSE;
3785         for (i = 0; i < *parents; i++) {
3786                 string_copy_rev(rev, &buf[SIZEOF_REV * i]);
3787                 if (!run_io_buf(revlist_argv, text, sizeof(text)) ||
3788                     !(items[i].text = strdup(text))) {
3789                         ok = FALSE;
3790                         break;
3791                 }
3792         }
3794         if (ok) {
3795                 *parents = 0;
3796                 ok = prompt_menu("Select parent", items, parents);
3797         }
3798         for (i = 0; items[i].text; i++)
3799                 free((char *) items[i].text);
3800         free(items);
3801         return ok;
3804 static bool
3805 select_commit_parent(const char *id, char rev[SIZEOF_REV], const char *path)
3807         char buf[SIZEOF_STR * 4];
3808         const char *revlist_argv[] = {
3809                 "git", "log", "--no-color", "-1",
3810                         "--pretty=format:%P", id, "--", path, NULL
3811         };
3812         int parents;
3814         if (!run_io_buf(revlist_argv, buf, sizeof(buf)) ||
3815             (parents = strlen(buf) / 40) < 0) {
3816                 report("Failed to get parent information");
3817                 return FALSE;
3819         } else if (parents == 0) {
3820                 if (path)
3821                         report("Path '%s' does not exist in the parent", path);
3822                 else
3823                         report("The selected commit has no parents");
3824                 return FALSE;
3825         }
3827         if (parents > 1 && !open_commit_parent_menu(buf, &parents))
3828                 return FALSE;
3830         string_copy_rev(rev, &buf[41 * parents]);
3831         return TRUE;
3834 /*
3835  * Pager backend
3836  */
3838 static bool
3839 pager_draw(struct view *view, struct line *line, unsigned int lineno)
3841         char text[SIZEOF_STR];
3843         if (opt_line_number && draw_lineno(view, lineno))
3844                 return TRUE;
3846         string_expand(text, sizeof(text), line->data, opt_tab_size);
3847         draw_text(view, line->type, text, TRUE);
3848         return TRUE;
3851 static bool
3852 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
3854         const char *describe_argv[] = { "git", "describe", commit_id, NULL };
3855         char ref[SIZEOF_STR];
3857         if (!run_io_buf(describe_argv, ref, sizeof(ref)) || !*ref)
3858                 return TRUE;
3860         /* This is the only fatal call, since it can "corrupt" the buffer. */
3861         if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
3862                 return FALSE;
3864         return TRUE;
3867 static void
3868 add_pager_refs(struct view *view, struct line *line)
3870         char buf[SIZEOF_STR];
3871         char *commit_id = (char *)line->data + STRING_SIZE("commit ");
3872         struct ref_list *list;
3873         size_t bufpos = 0, i;
3874         const char *sep = "Refs: ";
3875         bool is_tag = FALSE;
3877         assert(line->type == LINE_COMMIT);
3879         list = get_ref_list(commit_id);
3880         if (!list) {
3881                 if (view == VIEW(REQ_VIEW_DIFF))
3882                         goto try_add_describe_ref;
3883                 return;
3884         }
3886         for (i = 0; i < list->size; i++) {
3887                 struct ref *ref = list->refs[i];
3888                 const char *fmt = ref->tag    ? "%s[%s]" :
3889                                   ref->remote ? "%s<%s>" : "%s%s";
3891                 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
3892                         return;
3893                 sep = ", ";
3894                 if (ref->tag)
3895                         is_tag = TRUE;
3896         }
3898         if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
3899 try_add_describe_ref:
3900                 /* Add <tag>-g<commit_id> "fake" reference. */
3901                 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
3902                         return;
3903         }
3905         if (bufpos == 0)
3906                 return;
3908         add_line_text(view, buf, LINE_PP_REFS);
3911 static bool
3912 pager_read(struct view *view, char *data)
3914         struct line *line;
3916         if (!data)
3917                 return TRUE;
3919         line = add_line_text(view, data, get_line_type(data));
3920         if (!line)
3921                 return FALSE;
3923         if (line->type == LINE_COMMIT &&
3924             (view == VIEW(REQ_VIEW_DIFF) ||
3925              view == VIEW(REQ_VIEW_LOG)))
3926                 add_pager_refs(view, line);
3928         return TRUE;
3931 static enum request
3932 pager_request(struct view *view, enum request request, struct line *line)
3934         int split = 0;
3936         if (request != REQ_ENTER)
3937                 return request;
3939         if (line->type == LINE_COMMIT &&
3940            (view == VIEW(REQ_VIEW_LOG) ||
3941             view == VIEW(REQ_VIEW_PAGER))) {
3942                 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
3943                 split = 1;
3944         }
3946         /* Always scroll the view even if it was split. That way
3947          * you can use Enter to scroll through the log view and
3948          * split open each commit diff. */
3949         scroll_view(view, REQ_SCROLL_LINE_DOWN);
3951         /* FIXME: A minor workaround. Scrolling the view will call report("")
3952          * but if we are scrolling a non-current view this won't properly
3953          * update the view title. */
3954         if (split)
3955                 update_view_title(view);
3957         return REQ_NONE;
3960 static bool
3961 pager_grep(struct view *view, struct line *line)
3963         const char *text[] = { line->data, NULL };
3965         return grep_text(view, text);
3968 static void
3969 pager_select(struct view *view, struct line *line)
3971         if (line->type == LINE_COMMIT) {
3972                 char *text = (char *)line->data + STRING_SIZE("commit ");
3974                 if (view != VIEW(REQ_VIEW_PAGER))
3975                         string_copy_rev(view->ref, text);
3976                 string_copy_rev(ref_commit, text);
3977         }
3980 static struct view_ops pager_ops = {
3981         "line",
3982         NULL,
3983         NULL,
3984         pager_read,
3985         pager_draw,
3986         pager_request,
3987         pager_grep,
3988         pager_select,
3989 };
3991 static const char *log_argv[SIZEOF_ARG] = {
3992         "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
3993 };
3995 static enum request
3996 log_request(struct view *view, enum request request, struct line *line)
3998         switch (request) {
3999         case REQ_REFRESH:
4000                 load_refs();
4001                 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
4002                 return REQ_NONE;
4003         default:
4004                 return pager_request(view, request, line);
4005         }
4008 static struct view_ops log_ops = {
4009         "line",
4010         log_argv,
4011         NULL,
4012         pager_read,
4013         pager_draw,
4014         log_request,
4015         pager_grep,
4016         pager_select,
4017 };
4019 static const char *diff_argv[SIZEOF_ARG] = {
4020         "git", "show", "--pretty=fuller", "--no-color", "--root",
4021                 "--patch-with-stat", "--find-copies-harder", "-C", "%(commit)", NULL
4022 };
4024 static struct view_ops diff_ops = {
4025         "line",
4026         diff_argv,
4027         NULL,
4028         pager_read,
4029         pager_draw,
4030         pager_request,
4031         pager_grep,
4032         pager_select,
4033 };
4035 /*
4036  * Help backend
4037  */
4039 static bool help_keymap_hidden[ARRAY_SIZE(keymap_table)];
4041 static bool
4042 help_open_keymap_title(struct view *view, enum keymap keymap)
4044         struct line *line;
4046         line = add_line_format(view, LINE_HELP_KEYMAP, "[%c] %s bindings",
4047                                help_keymap_hidden[keymap] ? '+' : '-',
4048                                enum_name(keymap_table[keymap]));
4049         if (line)
4050                 line->other = keymap;
4052         return help_keymap_hidden[keymap];
4055 static void
4056 help_open_keymap(struct view *view, enum keymap keymap)
4058         const char *group = NULL;
4059         char buf[SIZEOF_STR];
4060         size_t bufpos;
4061         bool add_title = TRUE;
4062         int i;
4064         for (i = 0; i < ARRAY_SIZE(req_info); i++) {
4065                 const char *key = NULL;
4067                 if (req_info[i].request == REQ_NONE)
4068                         continue;
4070                 if (!req_info[i].request) {
4071                         group = req_info[i].help;
4072                         continue;
4073                 }
4075                 key = get_keys(keymap, req_info[i].request, TRUE);
4076                 if (!key || !*key)
4077                         continue;
4079                 if (add_title && help_open_keymap_title(view, keymap))
4080                         return;
4081                 add_title = FALSE;
4083                 if (group) {
4084                         add_line_text(view, group, LINE_HELP_GROUP);
4085                         group = NULL;
4086                 }
4088                 add_line_format(view, LINE_DEFAULT, "    %-25s %-20s %s", key,
4089                                 enum_name(req_info[i]), req_info[i].help);
4090         }
4092         group = "External commands:";
4094         for (i = 0; i < run_requests; i++) {
4095                 struct run_request *req = get_run_request(REQ_NONE + i + 1);
4096                 const char *key;
4097                 int argc;
4099                 if (!req || req->keymap != keymap)
4100                         continue;
4102                 key = get_key_name(req->key);
4103                 if (!*key)
4104                         key = "(no key defined)";
4106                 if (add_title && help_open_keymap_title(view, keymap))
4107                         return;
4108                 if (group) {
4109                         add_line_text(view, group, LINE_HELP_GROUP);
4110                         group = NULL;
4111                 }
4113                 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
4114                         if (!string_format_from(buf, &bufpos, "%s%s",
4115                                                 argc ? " " : "", req->argv[argc]))
4116                                 return;
4118                 add_line_format(view, LINE_DEFAULT, "    %-25s `%s`", key, buf);
4119         }
4122 static bool
4123 help_open(struct view *view)
4125         enum keymap keymap;
4127         reset_view(view);
4128         add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
4129         add_line_text(view, "", LINE_DEFAULT);
4131         for (keymap = 0; keymap < ARRAY_SIZE(keymap_table); keymap++)
4132                 help_open_keymap(view, keymap);
4134         return TRUE;
4137 static enum request
4138 help_request(struct view *view, enum request request, struct line *line)
4140         switch (request) {
4141         case REQ_ENTER:
4142                 if (line->type == LINE_HELP_KEYMAP) {
4143                         help_keymap_hidden[line->other] =
4144                                 !help_keymap_hidden[line->other];
4145                         view->p_restore = TRUE;
4146                         open_view(view, REQ_VIEW_HELP, OPEN_REFRESH);
4147                 }
4149                 return REQ_NONE;
4150         default:
4151                 return pager_request(view, request, line);
4152         }
4155 static struct view_ops help_ops = {
4156         "line",
4157         NULL,
4158         help_open,
4159         NULL,
4160         pager_draw,
4161         help_request,
4162         pager_grep,
4163         pager_select,
4164 };
4167 /*
4168  * Tree backend
4169  */
4171 struct tree_stack_entry {
4172         struct tree_stack_entry *prev;  /* Entry below this in the stack */
4173         unsigned long lineno;           /* Line number to restore */
4174         char *name;                     /* Position of name in opt_path */
4175 };
4177 /* The top of the path stack. */
4178 static struct tree_stack_entry *tree_stack = NULL;
4179 unsigned long tree_lineno = 0;
4181 static void
4182 pop_tree_stack_entry(void)
4184         struct tree_stack_entry *entry = tree_stack;
4186         tree_lineno = entry->lineno;
4187         entry->name[0] = 0;
4188         tree_stack = entry->prev;
4189         free(entry);
4192 static void
4193 push_tree_stack_entry(const char *name, unsigned long lineno)
4195         struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
4196         size_t pathlen = strlen(opt_path);
4198         if (!entry)
4199                 return;
4201         entry->prev = tree_stack;
4202         entry->name = opt_path + pathlen;
4203         tree_stack = entry;
4205         if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
4206                 pop_tree_stack_entry();
4207                 return;
4208         }
4210         /* Move the current line to the first tree entry. */
4211         tree_lineno = 1;
4212         entry->lineno = lineno;
4215 /* Parse output from git-ls-tree(1):
4216  *
4217  * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
4218  */
4220 #define SIZEOF_TREE_ATTR \
4221         STRING_SIZE("100644 blob f931e1d229c3e185caad4449bf5b66ed72462657\t")
4223 #define SIZEOF_TREE_MODE \
4224         STRING_SIZE("100644 ")
4226 #define TREE_ID_OFFSET \
4227         STRING_SIZE("100644 blob ")
4229 struct tree_entry {
4230         char id[SIZEOF_REV];
4231         mode_t mode;
4232         struct time time;               /* Date from the author ident. */
4233         const char *author;             /* Author of the commit. */
4234         char name[1];
4235 };
4237 static const char *
4238 tree_path(const struct line *line)
4240         return ((struct tree_entry *) line->data)->name;
4243 static int
4244 tree_compare_entry(const struct line *line1, const struct line *line2)
4246         if (line1->type != line2->type)
4247                 return line1->type == LINE_TREE_DIR ? -1 : 1;
4248         return strcmp(tree_path(line1), tree_path(line2));
4251 static const enum sort_field tree_sort_fields[] = {
4252         ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
4253 };
4254 static struct sort_state tree_sort_state = SORT_STATE(tree_sort_fields);
4256 static int
4257 tree_compare(const void *l1, const void *l2)
4259         const struct line *line1 = (const struct line *) l1;
4260         const struct line *line2 = (const struct line *) l2;
4261         const struct tree_entry *entry1 = ((const struct line *) l1)->data;
4262         const struct tree_entry *entry2 = ((const struct line *) l2)->data;
4264         if (line1->type == LINE_TREE_HEAD)
4265                 return -1;
4266         if (line2->type == LINE_TREE_HEAD)
4267                 return 1;
4269         switch (get_sort_field(tree_sort_state)) {
4270         case ORDERBY_DATE:
4271                 return sort_order(tree_sort_state, timecmp(&entry1->time, &entry2->time));
4273         case ORDERBY_AUTHOR:
4274                 return sort_order(tree_sort_state, strcmp(entry1->author, entry2->author));
4276         case ORDERBY_NAME:
4277         default:
4278                 return sort_order(tree_sort_state, tree_compare_entry(line1, line2));
4279         }
4283 static struct line *
4284 tree_entry(struct view *view, enum line_type type, const char *path,
4285            const char *mode, const char *id)
4287         struct tree_entry *entry = calloc(1, sizeof(*entry) + strlen(path));
4288         struct line *line = entry ? add_line_data(view, entry, type) : NULL;
4290         if (!entry || !line) {
4291                 free(entry);
4292                 return NULL;
4293         }
4295         strncpy(entry->name, path, strlen(path));
4296         if (mode)
4297                 entry->mode = strtoul(mode, NULL, 8);
4298         if (id)
4299                 string_copy_rev(entry->id, id);
4301         return line;
4304 static bool
4305 tree_read_date(struct view *view, char *text, bool *read_date)
4307         static const char *author_name;
4308         static struct time author_time;
4310         if (!text && *read_date) {
4311                 *read_date = FALSE;
4312                 return TRUE;
4314         } else if (!text) {
4315                 char *path = *opt_path ? opt_path : ".";
4316                 /* Find next entry to process */
4317                 const char *log_file[] = {
4318                         "git", "log", "--no-color", "--pretty=raw",
4319                                 "--cc", "--raw", view->id, "--", path, NULL
4320                 };
4321                 struct io io = {};
4323                 if (!view->lines) {
4324                         tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL);
4325                         report("Tree is empty");
4326                         return TRUE;
4327                 }
4329                 if (!run_io_rd(&io, log_file, opt_cdup, FORMAT_NONE)) {
4330                         report("Failed to load tree data");
4331                         return TRUE;
4332                 }
4334                 done_io(view->pipe);
4335                 view->io = io;
4336                 *read_date = TRUE;
4337                 return FALSE;
4339         } else if (*text == 'a' && get_line_type(text) == LINE_AUTHOR) {
4340                 parse_author_line(text + STRING_SIZE("author "),
4341                                   &author_name, &author_time);
4343         } else if (*text == ':') {
4344                 char *pos;
4345                 size_t annotated = 1;
4346                 size_t i;
4348                 pos = strchr(text, '\t');
4349                 if (!pos)
4350                         return TRUE;
4351                 text = pos + 1;
4352                 if (*opt_path && !strncmp(text, opt_path, strlen(opt_path)))
4353                         text += strlen(opt_path);
4354                 pos = strchr(text, '/');
4355                 if (pos)
4356                         *pos = 0;
4358                 for (i = 1; i < view->lines; i++) {
4359                         struct line *line = &view->line[i];
4360                         struct tree_entry *entry = line->data;
4362                         annotated += !!entry->author;
4363                         if (entry->author || strcmp(entry->name, text))
4364                                 continue;
4366                         entry->author = author_name;
4367                         entry->time = author_time;
4368                         line->dirty = 1;
4369                         break;
4370                 }
4372                 if (annotated == view->lines)
4373                         kill_io(view->pipe);
4374         }
4375         return TRUE;
4378 static bool
4379 tree_read(struct view *view, char *text)
4381         static bool read_date = FALSE;
4382         struct tree_entry *data;
4383         struct line *entry, *line;
4384         enum line_type type;
4385         size_t textlen = text ? strlen(text) : 0;
4386         char *path = text + SIZEOF_TREE_ATTR;
4388         if (read_date || !text)
4389                 return tree_read_date(view, text, &read_date);
4391         if (textlen <= SIZEOF_TREE_ATTR)
4392                 return FALSE;
4393         if (view->lines == 0 &&
4394             !tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL))
4395                 return FALSE;
4397         /* Strip the path part ... */
4398         if (*opt_path) {
4399                 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
4400                 size_t striplen = strlen(opt_path);
4402                 if (pathlen > striplen)
4403                         memmove(path, path + striplen,
4404                                 pathlen - striplen + 1);
4406                 /* Insert "link" to parent directory. */
4407                 if (view->lines == 1 &&
4408                     !tree_entry(view, LINE_TREE_DIR, "..", "040000", view->ref))
4409                         return FALSE;
4410         }
4412         type = text[SIZEOF_TREE_MODE] == 't' ? LINE_TREE_DIR : LINE_TREE_FILE;
4413         entry = tree_entry(view, type, path, text, text + TREE_ID_OFFSET);
4414         if (!entry)
4415                 return FALSE;
4416         data = entry->data;
4418         /* Skip "Directory ..." and ".." line. */
4419         for (line = &view->line[1 + !!*opt_path]; line < entry; line++) {
4420                 if (tree_compare_entry(line, entry) <= 0)
4421                         continue;
4423                 memmove(line + 1, line, (entry - line) * sizeof(*entry));
4425                 line->data = data;
4426                 line->type = type;
4427                 for (; line <= entry; line++)
4428                         line->dirty = line->cleareol = 1;
4429                 return TRUE;
4430         }
4432         if (tree_lineno > view->lineno) {
4433                 view->lineno = tree_lineno;
4434                 tree_lineno = 0;
4435         }
4437         return TRUE;
4440 static bool
4441 tree_draw(struct view *view, struct line *line, unsigned int lineno)
4443         struct tree_entry *entry = line->data;
4445         if (line->type == LINE_TREE_HEAD) {
4446                 if (draw_text(view, line->type, "Directory path /", TRUE))
4447                         return TRUE;
4448         } else {
4449                 if (draw_mode(view, entry->mode))
4450                         return TRUE;
4452                 if (opt_author && draw_author(view, entry->author))
4453                         return TRUE;
4455                 if (opt_date && draw_date(view, &entry->time))
4456                         return TRUE;
4457         }
4458         if (draw_text(view, line->type, entry->name, TRUE))
4459                 return TRUE;
4460         return TRUE;
4463 static void
4464 open_blob_editor()
4466         char file[SIZEOF_STR] = "/tmp/tigblob.XXXXXX";
4467         int fd = mkstemp(file);
4469         if (fd == -1)
4470                 report("Failed to create temporary file");
4471         else if (!run_io_append(blob_ops.argv, FORMAT_ALL, fd))
4472                 report("Failed to save blob data to file");
4473         else
4474                 open_editor(file);
4475         if (fd != -1)
4476                 unlink(file);
4479 static enum request
4480 tree_request(struct view *view, enum request request, struct line *line)
4482         enum open_flags flags;
4484         switch (request) {
4485         case REQ_VIEW_BLAME:
4486                 if (line->type != LINE_TREE_FILE) {
4487                         report("Blame only supported for files");
4488                         return REQ_NONE;
4489                 }
4491                 string_copy(opt_ref, view->vid);
4492                 return request;
4494         case REQ_EDIT:
4495                 if (line->type != LINE_TREE_FILE) {
4496                         report("Edit only supported for files");
4497                 } else if (!is_head_commit(view->vid)) {
4498                         open_blob_editor();
4499                 } else {
4500                         open_editor(opt_file);
4501                 }
4502                 return REQ_NONE;
4504         case REQ_TOGGLE_SORT_FIELD:
4505         case REQ_TOGGLE_SORT_ORDER:
4506                 sort_view(view, request, &tree_sort_state, tree_compare);
4507                 return REQ_NONE;
4509         case REQ_PARENT:
4510                 if (!*opt_path) {
4511                         /* quit view if at top of tree */
4512                         return REQ_VIEW_CLOSE;
4513                 }
4514                 /* fake 'cd  ..' */
4515                 line = &view->line[1];
4516                 break;
4518         case REQ_ENTER:
4519                 break;
4521         default:
4522                 return request;
4523         }
4525         /* Cleanup the stack if the tree view is at a different tree. */
4526         while (!*opt_path && tree_stack)
4527                 pop_tree_stack_entry();
4529         switch (line->type) {
4530         case LINE_TREE_DIR:
4531                 /* Depending on whether it is a subdirectory or parent link
4532                  * mangle the path buffer. */
4533                 if (line == &view->line[1] && *opt_path) {
4534                         pop_tree_stack_entry();
4536                 } else {
4537                         const char *basename = tree_path(line);
4539                         push_tree_stack_entry(basename, view->lineno);
4540                 }
4542                 /* Trees and subtrees share the same ID, so they are not not
4543                  * unique like blobs. */
4544                 flags = OPEN_RELOAD;
4545                 request = REQ_VIEW_TREE;
4546                 break;
4548         case LINE_TREE_FILE:
4549                 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4550                 request = REQ_VIEW_BLOB;
4551                 break;
4553         default:
4554                 return REQ_NONE;
4555         }
4557         open_view(view, request, flags);
4558         if (request == REQ_VIEW_TREE)
4559                 view->lineno = tree_lineno;
4561         return REQ_NONE;
4564 static bool
4565 tree_grep(struct view *view, struct line *line)
4567         struct tree_entry *entry = line->data;
4568         const char *text[] = {
4569                 entry->name,
4570                 opt_author ? entry->author : "",
4571                 opt_date ? mkdate(&entry->time) : "",
4572                 NULL
4573         };
4575         return grep_text(view, text);
4578 static void
4579 tree_select(struct view *view, struct line *line)
4581         struct tree_entry *entry = line->data;
4583         if (line->type == LINE_TREE_FILE) {
4584                 string_copy_rev(ref_blob, entry->id);
4585                 string_format(opt_file, "%s%s", opt_path, tree_path(line));
4587         } else if (line->type != LINE_TREE_DIR) {
4588                 return;
4589         }
4591         string_copy_rev(view->ref, entry->id);
4594 static bool
4595 tree_prepare(struct view *view)
4597         if (view->lines == 0 && opt_prefix[0]) {
4598                 char *pos = opt_prefix;
4600                 while (pos && *pos) {
4601                         char *end = strchr(pos, '/');
4603                         if (end)
4604                                 *end = 0;
4605                         push_tree_stack_entry(pos, 0);
4606                         pos = end;
4607                         if (end) {
4608                                 *end = '/';
4609                                 pos++;
4610                         }
4611                 }
4613         } else if (strcmp(view->vid, view->id)) {
4614                 opt_path[0] = 0;
4615         }
4617         return init_io_rd(&view->io, view->ops->argv, opt_cdup, FORMAT_ALL);
4620 static const char *tree_argv[SIZEOF_ARG] = {
4621         "git", "ls-tree", "%(commit)", "%(directory)", NULL
4622 };
4624 static struct view_ops tree_ops = {
4625         "file",
4626         tree_argv,
4627         NULL,
4628         tree_read,
4629         tree_draw,
4630         tree_request,
4631         tree_grep,
4632         tree_select,
4633         tree_prepare,
4634 };
4636 static bool
4637 blob_read(struct view *view, char *line)
4639         if (!line)
4640                 return TRUE;
4641         return add_line_text(view, line, LINE_DEFAULT) != NULL;
4644 static enum request
4645 blob_request(struct view *view, enum request request, struct line *line)
4647         switch (request) {
4648         case REQ_EDIT:
4649                 open_blob_editor();
4650                 return REQ_NONE;
4651         default:
4652                 return pager_request(view, request, line);
4653         }
4656 static const char *blob_argv[SIZEOF_ARG] = {
4657         "git", "cat-file", "blob", "%(blob)", NULL
4658 };
4660 static struct view_ops blob_ops = {
4661         "line",
4662         blob_argv,
4663         NULL,
4664         blob_read,
4665         pager_draw,
4666         blob_request,
4667         pager_grep,
4668         pager_select,
4669 };
4671 /*
4672  * Blame backend
4673  *
4674  * Loading the blame view is a two phase job:
4675  *
4676  *  1. File content is read either using opt_file from the
4677  *     filesystem or using git-cat-file.
4678  *  2. Then blame information is incrementally added by
4679  *     reading output from git-blame.
4680  */
4682 static const char *blame_head_argv[] = {
4683         "git", "blame", "--incremental", "--", "%(file)", NULL
4684 };
4686 static const char *blame_ref_argv[] = {
4687         "git", "blame", "--incremental", "%(ref)", "--", "%(file)", NULL
4688 };
4690 static const char *blame_cat_file_argv[] = {
4691         "git", "cat-file", "blob", "%(ref):%(file)", NULL
4692 };
4694 struct blame_commit {
4695         char id[SIZEOF_REV];            /* SHA1 ID. */
4696         char title[128];                /* First line of the commit message. */
4697         const char *author;             /* Author of the commit. */
4698         struct time time;               /* Date from the author ident. */
4699         char filename[128];             /* Name of file. */
4700         bool has_previous;              /* Was a "previous" line detected. */
4701 };
4703 struct blame {
4704         struct blame_commit *commit;
4705         unsigned long lineno;
4706         char text[1];
4707 };
4709 static bool
4710 blame_open(struct view *view)
4712         char path[SIZEOF_STR];
4714         if (!view->parent && *opt_prefix) {
4715                 string_copy(path, opt_file);
4716                 if (!string_format(opt_file, "%s%s", opt_prefix, path))
4717                         return FALSE;
4718         }
4720         if (*opt_ref || !io_open(&view->io, "%s%s", opt_cdup, opt_file)) {
4721                 if (!run_io_rd(&view->io, blame_cat_file_argv, opt_cdup, FORMAT_ALL))
4722                         return FALSE;
4723         }
4725         setup_update(view, opt_file);
4726         string_format(view->ref, "%s ...", opt_file);
4728         return TRUE;
4731 static struct blame_commit *
4732 get_blame_commit(struct view *view, const char *id)
4734         size_t i;
4736         for (i = 0; i < view->lines; i++) {
4737                 struct blame *blame = view->line[i].data;
4739                 if (!blame->commit)
4740                         continue;
4742                 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
4743                         return blame->commit;
4744         }
4746         {
4747                 struct blame_commit *commit = calloc(1, sizeof(*commit));
4749                 if (commit)
4750                         string_ncopy(commit->id, id, SIZEOF_REV);
4751                 return commit;
4752         }
4755 static bool
4756 parse_number(const char **posref, size_t *number, size_t min, size_t max)
4758         const char *pos = *posref;
4760         *posref = NULL;
4761         pos = strchr(pos + 1, ' ');
4762         if (!pos || !isdigit(pos[1]))
4763                 return FALSE;
4764         *number = atoi(pos + 1);
4765         if (*number < min || *number > max)
4766                 return FALSE;
4768         *posref = pos;
4769         return TRUE;
4772 static struct blame_commit *
4773 parse_blame_commit(struct view *view, const char *text, int *blamed)
4775         struct blame_commit *commit;
4776         struct blame *blame;
4777         const char *pos = text + SIZEOF_REV - 2;
4778         size_t orig_lineno = 0;
4779         size_t lineno;
4780         size_t group;
4782         if (strlen(text) <= SIZEOF_REV || pos[1] != ' ')
4783                 return NULL;
4785         if (!parse_number(&pos, &orig_lineno, 1, 9999999) ||
4786             !parse_number(&pos, &lineno, 1, view->lines) ||
4787             !parse_number(&pos, &group, 1, view->lines - lineno + 1))
4788                 return NULL;
4790         commit = get_blame_commit(view, text);
4791         if (!commit)
4792                 return NULL;
4794         *blamed += group;
4795         while (group--) {
4796                 struct line *line = &view->line[lineno + group - 1];
4798                 blame = line->data;
4799                 blame->commit = commit;
4800                 blame->lineno = orig_lineno + group - 1;
4801                 line->dirty = 1;
4802         }
4804         return commit;
4807 static bool
4808 blame_read_file(struct view *view, const char *line, bool *read_file)
4810         if (!line) {
4811                 const char **argv = *opt_ref ? blame_ref_argv : blame_head_argv;
4812                 struct io io = {};
4814                 if (view->lines == 0 && !view->parent)
4815                         die("No blame exist for %s", view->vid);
4817                 if (view->lines == 0 || !run_io_rd(&io, argv, opt_cdup, FORMAT_ALL)) {
4818                         report("Failed to load blame data");
4819                         return TRUE;
4820                 }
4822                 done_io(view->pipe);
4823                 view->io = io;
4824                 *read_file = FALSE;
4825                 return FALSE;
4827         } else {
4828                 size_t linelen = strlen(line);
4829                 struct blame *blame = malloc(sizeof(*blame) + linelen);
4831                 if (!blame)
4832                         return FALSE;
4834                 blame->commit = NULL;
4835                 strncpy(blame->text, line, linelen);
4836                 blame->text[linelen] = 0;
4837                 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
4838         }
4841 static bool
4842 match_blame_header(const char *name, char **line)
4844         size_t namelen = strlen(name);
4845         bool matched = !strncmp(name, *line, namelen);
4847         if (matched)
4848                 *line += namelen;
4850         return matched;
4853 static bool
4854 blame_read(struct view *view, char *line)
4856         static struct blame_commit *commit = NULL;
4857         static int blamed = 0;
4858         static bool read_file = TRUE;
4860         if (read_file)
4861                 return blame_read_file(view, line, &read_file);
4863         if (!line) {
4864                 /* Reset all! */
4865                 commit = NULL;
4866                 blamed = 0;
4867                 read_file = TRUE;
4868                 string_format(view->ref, "%s", view->vid);
4869                 if (view_is_displayed(view)) {
4870                         update_view_title(view);
4871                         redraw_view_from(view, 0);
4872                 }
4873                 return TRUE;
4874         }
4876         if (!commit) {
4877                 commit = parse_blame_commit(view, line, &blamed);
4878                 string_format(view->ref, "%s %2d%%", view->vid,
4879                               view->lines ? blamed * 100 / view->lines : 0);
4881         } else if (match_blame_header("author ", &line)) {
4882                 commit->author = get_author(line);
4884         } else if (match_blame_header("author-time ", &line)) {
4885                 parse_timesec(&commit->time, line);
4887         } else if (match_blame_header("author-tz ", &line)) {
4888                 parse_timezone(&commit->time, line);
4890         } else if (match_blame_header("summary ", &line)) {
4891                 string_ncopy(commit->title, line, strlen(line));
4893         } else if (match_blame_header("previous ", &line)) {
4894                 commit->has_previous = TRUE;
4896         } else if (match_blame_header("filename ", &line)) {
4897                 string_ncopy(commit->filename, line, strlen(line));
4898                 commit = NULL;
4899         }
4901         return TRUE;
4904 static bool
4905 blame_draw(struct view *view, struct line *line, unsigned int lineno)
4907         struct blame *blame = line->data;
4908         struct time *time = NULL;
4909         const char *id = NULL, *author = NULL;
4910         char text[SIZEOF_STR];
4912         if (blame->commit && *blame->commit->filename) {
4913                 id = blame->commit->id;
4914                 author = blame->commit->author;
4915                 time = &blame->commit->time;
4916         }
4918         if (opt_date && draw_date(view, time))
4919                 return TRUE;
4921         if (opt_author && draw_author(view, author))
4922                 return TRUE;
4924         if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
4925                 return TRUE;
4927         if (draw_lineno(view, lineno))
4928                 return TRUE;
4930         string_expand(text, sizeof(text), blame->text, opt_tab_size);
4931         draw_text(view, LINE_DEFAULT, text, TRUE);
4932         return TRUE;
4935 static bool
4936 check_blame_commit(struct blame *blame, bool check_null_id)
4938         if (!blame->commit)
4939                 report("Commit data not loaded yet");
4940         else if (check_null_id && !strcmp(blame->commit->id, NULL_ID))
4941                 report("No commit exist for the selected line");
4942         else
4943                 return TRUE;
4944         return FALSE;
4947 static void
4948 setup_blame_parent_line(struct view *view, struct blame *blame)
4950         const char *diff_tree_argv[] = {
4951                 "git", "diff-tree", "-U0", blame->commit->id,
4952                         "--", blame->commit->filename, NULL
4953         };
4954         struct io io = {};
4955         int parent_lineno = -1;
4956         int blamed_lineno = -1;
4957         char *line;
4959         if (!run_io(&io, diff_tree_argv, NULL, IO_RD))
4960                 return;
4962         while ((line = io_get(&io, '\n', TRUE))) {
4963                 if (*line == '@') {
4964                         char *pos = strchr(line, '+');
4966                         parent_lineno = atoi(line + 4);
4967                         if (pos)
4968                                 blamed_lineno = atoi(pos + 1);
4970                 } else if (*line == '+' && parent_lineno != -1) {
4971                         if (blame->lineno == blamed_lineno - 1 &&
4972                             !strcmp(blame->text, line + 1)) {
4973                                 view->lineno = parent_lineno ? parent_lineno - 1 : 0;
4974                                 break;
4975                         }
4976                         blamed_lineno++;
4977                 }
4978         }
4980         done_io(&io);
4983 static enum request
4984 blame_request(struct view *view, enum request request, struct line *line)
4986         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4987         struct blame *blame = line->data;
4989         switch (request) {
4990         case REQ_VIEW_BLAME:
4991                 if (check_blame_commit(blame, TRUE)) {
4992                         string_copy(opt_ref, blame->commit->id);
4993                         string_copy(opt_file, blame->commit->filename);
4994                         if (blame->lineno)
4995                                 view->lineno = blame->lineno;
4996                         open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4997                 }
4998                 break;
5000         case REQ_PARENT:
5001                 if (check_blame_commit(blame, TRUE) &&
5002                     select_commit_parent(blame->commit->id, opt_ref,
5003                                          blame->commit->filename)) {
5004                         string_copy(opt_file, blame->commit->filename);
5005                         setup_blame_parent_line(view, blame);
5006                         open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
5007                 }
5008                 break;
5010         case REQ_ENTER:
5011                 if (!check_blame_commit(blame, FALSE))
5012                         break;
5014                 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
5015                     !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
5016                         break;
5018                 if (!strcmp(blame->commit->id, NULL_ID)) {
5019                         struct view *diff = VIEW(REQ_VIEW_DIFF);
5020                         const char *diff_index_argv[] = {
5021                                 "git", "diff-index", "--root", "--patch-with-stat",
5022                                         "-C", "-M", "HEAD", "--", view->vid, NULL
5023                         };
5025                         if (!blame->commit->has_previous) {
5026                                 diff_index_argv[1] = "diff";
5027                                 diff_index_argv[2] = "--no-color";
5028                                 diff_index_argv[6] = "--";
5029                                 diff_index_argv[7] = "/dev/null";
5030                         }
5032                         if (!prepare_update(diff, diff_index_argv, NULL, FORMAT_DASH)) {
5033                                 report("Failed to allocate diff command");
5034                                 break;
5035                         }
5036                         flags |= OPEN_PREPARED;
5037                 }
5039                 open_view(view, REQ_VIEW_DIFF, flags);
5040                 if (VIEW(REQ_VIEW_DIFF)->pipe && !strcmp(blame->commit->id, NULL_ID))
5041                         string_copy_rev(VIEW(REQ_VIEW_DIFF)->ref, NULL_ID);
5042                 break;
5044         default:
5045                 return request;
5046         }
5048         return REQ_NONE;
5051 static bool
5052 blame_grep(struct view *view, struct line *line)
5054         struct blame *blame = line->data;
5055         struct blame_commit *commit = blame->commit;
5056         const char *text[] = {
5057                 blame->text,
5058                 commit ? commit->title : "",
5059                 commit ? commit->id : "",
5060                 commit && opt_author ? commit->author : "",
5061                 commit && opt_date ? mkdate(&commit->time) : "",
5062                 NULL
5063         };
5065         return grep_text(view, text);
5068 static void
5069 blame_select(struct view *view, struct line *line)
5071         struct blame *blame = line->data;
5072         struct blame_commit *commit = blame->commit;
5074         if (!commit)
5075                 return;
5077         if (!strcmp(commit->id, NULL_ID))
5078                 string_ncopy(ref_commit, "HEAD", 4);
5079         else
5080                 string_copy_rev(ref_commit, commit->id);
5083 static struct view_ops blame_ops = {
5084         "line",
5085         NULL,
5086         blame_open,
5087         blame_read,
5088         blame_draw,
5089         blame_request,
5090         blame_grep,
5091         blame_select,
5092 };
5094 /*
5095  * Branch backend
5096  */
5098 struct branch {
5099         const char *author;             /* Author of the last commit. */
5100         struct time time;               /* Date of the last activity. */
5101         const struct ref *ref;          /* Name and commit ID information. */
5102 };
5104 static const struct ref branch_all;
5106 static const enum sort_field branch_sort_fields[] = {
5107         ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
5108 };
5109 static struct sort_state branch_sort_state = SORT_STATE(branch_sort_fields);
5111 static int
5112 branch_compare(const void *l1, const void *l2)
5114         const struct branch *branch1 = ((const struct line *) l1)->data;
5115         const struct branch *branch2 = ((const struct line *) l2)->data;
5117         switch (get_sort_field(branch_sort_state)) {
5118         case ORDERBY_DATE:
5119                 return sort_order(branch_sort_state, timecmp(&branch1->time, &branch2->time));
5121         case ORDERBY_AUTHOR:
5122                 return sort_order(branch_sort_state, strcmp(branch1->author, branch2->author));
5124         case ORDERBY_NAME:
5125         default:
5126                 return sort_order(branch_sort_state, strcmp(branch1->ref->name, branch2->ref->name));
5127         }
5130 static bool
5131 branch_draw(struct view *view, struct line *line, unsigned int lineno)
5133         struct branch *branch = line->data;
5134         enum line_type type = branch->ref->head ? LINE_MAIN_HEAD : LINE_DEFAULT;
5136         if (opt_date && draw_date(view, &branch->time))
5137                 return TRUE;
5139         if (opt_author && draw_author(view, branch->author))
5140                 return TRUE;
5142         draw_text(view, type, branch->ref == &branch_all ? "All branches" : branch->ref->name, TRUE);
5143         return TRUE;
5146 static enum request
5147 branch_request(struct view *view, enum request request, struct line *line)
5149         struct branch *branch = line->data;
5151         switch (request) {
5152         case REQ_REFRESH:
5153                 load_refs();
5154                 open_view(view, REQ_VIEW_BRANCH, OPEN_REFRESH);
5155                 return REQ_NONE;
5157         case REQ_TOGGLE_SORT_FIELD:
5158         case REQ_TOGGLE_SORT_ORDER:
5159                 sort_view(view, request, &branch_sort_state, branch_compare);
5160                 return REQ_NONE;
5162         case REQ_ENTER:
5163                 if (branch->ref == &branch_all) {
5164                         const char *all_branches_argv[] = {
5165                                 "git", "log", "--no-color", "--pretty=raw", "--parents",
5166                                       "--topo-order", "--all", NULL
5167                         };
5168                         struct view *main_view = VIEW(REQ_VIEW_MAIN);
5170                         if (!prepare_update(main_view, all_branches_argv, NULL, FORMAT_NONE)) {
5171                                 report("Failed to load view of all branches");
5172                                 return REQ_NONE;
5173                         }
5174                         open_view(view, REQ_VIEW_MAIN, OPEN_PREPARED | OPEN_SPLIT);
5175                 } else {
5176                         open_view(view, REQ_VIEW_MAIN, OPEN_SPLIT);
5177                 }
5178                 return REQ_NONE;
5180         default:
5181                 return request;
5182         }
5185 static bool
5186 branch_read(struct view *view, char *line)
5188         static char id[SIZEOF_REV];
5189         struct branch *reference;
5190         size_t i;
5192         if (!line)
5193                 return TRUE;
5195         switch (get_line_type(line)) {
5196         case LINE_COMMIT:
5197                 string_copy_rev(id, line + STRING_SIZE("commit "));
5198                 return TRUE;
5200         case LINE_AUTHOR:
5201                 for (i = 0, reference = NULL; i < view->lines; i++) {
5202                         struct branch *branch = view->line[i].data;
5204                         if (strcmp(branch->ref->id, id))
5205                                 continue;
5207                         view->line[i].dirty = TRUE;
5208                         if (reference) {
5209                                 branch->author = reference->author;
5210                                 branch->time = reference->time;
5211                                 continue;
5212                         }
5214                         parse_author_line(line + STRING_SIZE("author "),
5215                                           &branch->author, &branch->time);
5216                         reference = branch;
5217                 }
5218                 return TRUE;
5220         default:
5221                 return TRUE;
5222         }
5226 static bool
5227 branch_open_visitor(void *data, const struct ref *ref)
5229         struct view *view = data;
5230         struct branch *branch;
5232         if (ref->tag || ref->ltag || ref->remote)
5233                 return TRUE;
5235         branch = calloc(1, sizeof(*branch));
5236         if (!branch)
5237                 return FALSE;
5239         branch->ref = ref;
5240         return !!add_line_data(view, branch, LINE_DEFAULT);
5243 static bool
5244 branch_open(struct view *view)
5246         const char *branch_log[] = {
5247                 "git", "log", "--no-color", "--pretty=raw",
5248                         "--simplify-by-decoration", "--all", NULL
5249         };
5251         if (!run_io_rd(&view->io, branch_log, NULL, FORMAT_NONE)) {
5252                 report("Failed to load branch data");
5253                 return TRUE;
5254         }
5256         setup_update(view, view->id);
5257         branch_open_visitor(view, &branch_all);
5258         foreach_ref(branch_open_visitor, view);
5259         view->p_restore = TRUE;
5261         return TRUE;
5264 static bool
5265 branch_grep(struct view *view, struct line *line)
5267         struct branch *branch = line->data;
5268         const char *text[] = {
5269                 branch->ref->name,
5270                 branch->author,
5271                 NULL
5272         };
5274         return grep_text(view, text);
5277 static void
5278 branch_select(struct view *view, struct line *line)
5280         struct branch *branch = line->data;
5282         string_copy_rev(view->ref, branch->ref->id);
5283         string_copy_rev(ref_commit, branch->ref->id);
5284         string_copy_rev(ref_head, branch->ref->id);
5287 static struct view_ops branch_ops = {
5288         "branch",
5289         NULL,
5290         branch_open,
5291         branch_read,
5292         branch_draw,
5293         branch_request,
5294         branch_grep,
5295         branch_select,
5296 };
5298 /*
5299  * Status backend
5300  */
5302 struct status {
5303         char status;
5304         struct {
5305                 mode_t mode;
5306                 char rev[SIZEOF_REV];
5307                 char name[SIZEOF_STR];
5308         } old;
5309         struct {
5310                 mode_t mode;
5311                 char rev[SIZEOF_REV];
5312                 char name[SIZEOF_STR];
5313         } new;
5314 };
5316 static char status_onbranch[SIZEOF_STR];
5317 static struct status stage_status;
5318 static enum line_type stage_line_type;
5319 static size_t stage_chunks;
5320 static int *stage_chunk;
5322 DEFINE_ALLOCATOR(realloc_ints, int, 32)
5324 /* This should work even for the "On branch" line. */
5325 static inline bool
5326 status_has_none(struct view *view, struct line *line)
5328         return line < view->line + view->lines && !line[1].data;
5331 /* Get fields from the diff line:
5332  * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
5333  */
5334 static inline bool
5335 status_get_diff(struct status *file, const char *buf, size_t bufsize)
5337         const char *old_mode = buf +  1;
5338         const char *new_mode = buf +  8;
5339         const char *old_rev  = buf + 15;
5340         const char *new_rev  = buf + 56;
5341         const char *status   = buf + 97;
5343         if (bufsize < 98 ||
5344             old_mode[-1] != ':' ||
5345             new_mode[-1] != ' ' ||
5346             old_rev[-1]  != ' ' ||
5347             new_rev[-1]  != ' ' ||
5348             status[-1]   != ' ')
5349                 return FALSE;
5351         file->status = *status;
5353         string_copy_rev(file->old.rev, old_rev);
5354         string_copy_rev(file->new.rev, new_rev);
5356         file->old.mode = strtoul(old_mode, NULL, 8);
5357         file->new.mode = strtoul(new_mode, NULL, 8);
5359         file->old.name[0] = file->new.name[0] = 0;
5361         return TRUE;
5364 static bool
5365 status_run(struct view *view, const char *argv[], char status, enum line_type type)
5367         struct status *unmerged = NULL;
5368         char *buf;
5369         struct io io = {};
5371         if (!run_io(&io, argv, opt_cdup, IO_RD))
5372                 return FALSE;
5374         add_line_data(view, NULL, type);
5376         while ((buf = io_get(&io, 0, TRUE))) {
5377                 struct status *file = unmerged;
5379                 if (!file) {
5380                         file = calloc(1, sizeof(*file));
5381                         if (!file || !add_line_data(view, file, type))
5382                                 goto error_out;
5383                 }
5385                 /* Parse diff info part. */
5386                 if (status) {
5387                         file->status = status;
5388                         if (status == 'A')
5389                                 string_copy(file->old.rev, NULL_ID);
5391                 } else if (!file->status || file == unmerged) {
5392                         if (!status_get_diff(file, buf, strlen(buf)))
5393                                 goto error_out;
5395                         buf = io_get(&io, 0, TRUE);
5396                         if (!buf)
5397                                 break;
5399                         /* Collapse all modified entries that follow an
5400                          * associated unmerged entry. */
5401                         if (unmerged == file) {
5402                                 unmerged->status = 'U';
5403                                 unmerged = NULL;
5404                         } else if (file->status == 'U') {
5405                                 unmerged = file;
5406                         }
5407                 }
5409                 /* Grab the old name for rename/copy. */
5410                 if (!*file->old.name &&
5411                     (file->status == 'R' || file->status == 'C')) {
5412                         string_ncopy(file->old.name, buf, strlen(buf));
5414                         buf = io_get(&io, 0, TRUE);
5415                         if (!buf)
5416                                 break;
5417                 }
5419                 /* git-ls-files just delivers a NUL separated list of
5420                  * file names similar to the second half of the
5421                  * git-diff-* output. */
5422                 string_ncopy(file->new.name, buf, strlen(buf));
5423                 if (!*file->old.name)
5424                         string_copy(file->old.name, file->new.name);
5425                 file = NULL;
5426         }
5428         if (io_error(&io)) {
5429 error_out:
5430                 done_io(&io);
5431                 return FALSE;
5432         }
5434         if (!view->line[view->lines - 1].data)
5435                 add_line_data(view, NULL, LINE_STAT_NONE);
5437         done_io(&io);
5438         return TRUE;
5441 /* Don't show unmerged entries in the staged section. */
5442 static const char *status_diff_index_argv[] = {
5443         "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
5444                              "--cached", "-M", "HEAD", NULL
5445 };
5447 static const char *status_diff_files_argv[] = {
5448         "git", "diff-files", "-z", NULL
5449 };
5451 static const char *status_list_other_argv[] = {
5452         "git", "ls-files", "-z", "--others", "--exclude-standard", opt_prefix, NULL
5453 };
5455 static const char *status_list_no_head_argv[] = {
5456         "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
5457 };
5459 static const char *update_index_argv[] = {
5460         "git", "update-index", "-q", "--unmerged", "--refresh", NULL
5461 };
5463 /* Restore the previous line number to stay in the context or select a
5464  * line with something that can be updated. */
5465 static void
5466 status_restore(struct view *view)
5468         if (view->p_lineno >= view->lines)
5469                 view->p_lineno = view->lines - 1;
5470         while (view->p_lineno < view->lines && !view->line[view->p_lineno].data)
5471                 view->p_lineno++;
5472         while (view->p_lineno > 0 && !view->line[view->p_lineno].data)
5473                 view->p_lineno--;
5475         /* If the above fails, always skip the "On branch" line. */
5476         if (view->p_lineno < view->lines)
5477                 view->lineno = view->p_lineno;
5478         else
5479                 view->lineno = 1;
5481         if (view->lineno < view->offset)
5482                 view->offset = view->lineno;
5483         else if (view->offset + view->height <= view->lineno)
5484                 view->offset = view->lineno - view->height + 1;
5486         view->p_restore = FALSE;
5489 static void
5490 status_update_onbranch(void)
5492         static const char *paths[][2] = {
5493                 { "rebase-apply/rebasing",      "Rebasing" },
5494                 { "rebase-apply/applying",      "Applying mailbox" },
5495                 { "rebase-apply/",              "Rebasing mailbox" },
5496                 { "rebase-merge/interactive",   "Interactive rebase" },
5497                 { "rebase-merge/",              "Rebase merge" },
5498                 { "MERGE_HEAD",                 "Merging" },
5499                 { "BISECT_LOG",                 "Bisecting" },
5500                 { "HEAD",                       "On branch" },
5501         };
5502         char buf[SIZEOF_STR];
5503         struct stat stat;
5504         int i;
5506         if (is_initial_commit()) {
5507                 string_copy(status_onbranch, "Initial commit");
5508                 return;
5509         }
5511         for (i = 0; i < ARRAY_SIZE(paths); i++) {
5512                 char *head = opt_head;
5514                 if (!string_format(buf, "%s/%s", opt_git_dir, paths[i][0]) ||
5515                     lstat(buf, &stat) < 0)
5516                         continue;
5518                 if (!*opt_head) {
5519                         struct io io = {};
5521                         if (io_open(&io, "%s/rebase-merge/head-name", opt_git_dir) &&
5522                             io_read_buf(&io, buf, sizeof(buf))) {
5523                                 head = buf;
5524                                 if (!prefixcmp(head, "refs/heads/"))
5525                                         head += STRING_SIZE("refs/heads/");
5526                         }
5527                 }
5529                 if (!string_format(status_onbranch, "%s %s", paths[i][1], head))
5530                         string_copy(status_onbranch, opt_head);
5531                 return;
5532         }
5534         string_copy(status_onbranch, "Not currently on any branch");
5537 /* First parse staged info using git-diff-index(1), then parse unstaged
5538  * info using git-diff-files(1), and finally untracked files using
5539  * git-ls-files(1). */
5540 static bool
5541 status_open(struct view *view)
5543         reset_view(view);
5545         add_line_data(view, NULL, LINE_STAT_HEAD);
5546         status_update_onbranch();
5548         run_io_bg(update_index_argv);
5550         if (is_initial_commit()) {
5551                 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
5552                         return FALSE;
5553         } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
5554                 return FALSE;
5555         }
5557         if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
5558             !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
5559                 return FALSE;
5561         /* Restore the exact position or use the specialized restore
5562          * mode? */
5563         if (!view->p_restore)
5564                 status_restore(view);
5565         return TRUE;
5568 static bool
5569 status_draw(struct view *view, struct line *line, unsigned int lineno)
5571         struct status *status = line->data;
5572         enum line_type type;
5573         const char *text;
5575         if (!status) {
5576                 switch (line->type) {
5577                 case LINE_STAT_STAGED:
5578                         type = LINE_STAT_SECTION;
5579                         text = "Changes to be committed:";
5580                         break;
5582                 case LINE_STAT_UNSTAGED:
5583                         type = LINE_STAT_SECTION;
5584                         text = "Changed but not updated:";
5585                         break;
5587                 case LINE_STAT_UNTRACKED:
5588                         type = LINE_STAT_SECTION;
5589                         text = "Untracked files:";
5590                         break;
5592                 case LINE_STAT_NONE:
5593                         type = LINE_DEFAULT;
5594                         text = "  (no files)";
5595                         break;
5597                 case LINE_STAT_HEAD:
5598                         type = LINE_STAT_HEAD;
5599                         text = status_onbranch;
5600                         break;
5602                 default:
5603                         return FALSE;
5604                 }
5605         } else {
5606                 static char buf[] = { '?', ' ', ' ', ' ', 0 };
5608                 buf[0] = status->status;
5609                 if (draw_text(view, line->type, buf, TRUE))
5610                         return TRUE;
5611                 type = LINE_DEFAULT;
5612                 text = status->new.name;
5613         }
5615         draw_text(view, type, text, TRUE);
5616         return TRUE;
5619 static enum request
5620 status_load_error(struct view *view, struct view *stage, const char *path)
5622         if (displayed_views() == 2 || display[current_view] != view)
5623                 maximize_view(view);
5624         report("Failed to load '%s': %s", path, io_strerror(&stage->io));
5625         return REQ_NONE;
5628 static enum request
5629 status_enter(struct view *view, struct line *line)
5631         struct status *status = line->data;
5632         const char *oldpath = status ? status->old.name : NULL;
5633         /* Diffs for unmerged entries are empty when passing the new
5634          * path, so leave it empty. */
5635         const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
5636         const char *info;
5637         enum open_flags split;
5638         struct view *stage = VIEW(REQ_VIEW_STAGE);
5640         if (line->type == LINE_STAT_NONE ||
5641             (!status && line[1].type == LINE_STAT_NONE)) {
5642                 report("No file to diff");
5643                 return REQ_NONE;
5644         }
5646         switch (line->type) {
5647         case LINE_STAT_STAGED:
5648                 if (is_initial_commit()) {
5649                         const char *no_head_diff_argv[] = {
5650                                 "git", "diff", "--no-color", "--patch-with-stat",
5651                                         "--", "/dev/null", newpath, NULL
5652                         };
5654                         if (!prepare_update(stage, no_head_diff_argv, opt_cdup, FORMAT_DASH))
5655                                 return status_load_error(view, stage, newpath);
5656                 } else {
5657                         const char *index_show_argv[] = {
5658                                 "git", "diff-index", "--root", "--patch-with-stat",
5659                                         "-C", "-M", "--cached", "HEAD", "--",
5660                                         oldpath, newpath, NULL
5661                         };
5663                         if (!prepare_update(stage, index_show_argv, opt_cdup, FORMAT_DASH))
5664                                 return status_load_error(view, stage, newpath);
5665                 }
5667                 if (status)
5668                         info = "Staged changes to %s";
5669                 else
5670                         info = "Staged changes";
5671                 break;
5673         case LINE_STAT_UNSTAGED:
5674         {
5675                 const char *files_show_argv[] = {
5676                         "git", "diff-files", "--root", "--patch-with-stat",
5677                                 "-C", "-M", "--", oldpath, newpath, NULL
5678                 };
5680                 if (!prepare_update(stage, files_show_argv, opt_cdup, FORMAT_DASH))
5681                         return status_load_error(view, stage, newpath);
5682                 if (status)
5683                         info = "Unstaged changes to %s";
5684                 else
5685                         info = "Unstaged changes";
5686                 break;
5687         }
5688         case LINE_STAT_UNTRACKED:
5689                 if (!newpath) {
5690                         report("No file to show");
5691                         return REQ_NONE;
5692                 }
5694                 if (!suffixcmp(status->new.name, -1, "/")) {
5695                         report("Cannot display a directory");
5696                         return REQ_NONE;
5697                 }
5699                 if (!prepare_update_file(stage, newpath))
5700                         return status_load_error(view, stage, newpath);
5701                 info = "Untracked file %s";
5702                 break;
5704         case LINE_STAT_HEAD:
5705                 return REQ_NONE;
5707         default:
5708                 die("line type %d not handled in switch", line->type);
5709         }
5711         split = view_is_displayed(view) ? OPEN_SPLIT : 0;
5712         open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split);
5713         if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
5714                 if (status) {
5715                         stage_status = *status;
5716                 } else {
5717                         memset(&stage_status, 0, sizeof(stage_status));
5718                 }
5720                 stage_line_type = line->type;
5721                 stage_chunks = 0;
5722                 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
5723         }
5725         return REQ_NONE;
5728 static bool
5729 status_exists(struct status *status, enum line_type type)
5731         struct view *view = VIEW(REQ_VIEW_STATUS);
5732         unsigned long lineno;
5734         for (lineno = 0; lineno < view->lines; lineno++) {
5735                 struct line *line = &view->line[lineno];
5736                 struct status *pos = line->data;
5738                 if (line->type != type)
5739                         continue;
5740                 if (!pos && (!status || !status->status) && line[1].data) {
5741                         select_view_line(view, lineno);
5742                         return TRUE;
5743                 }
5744                 if (pos && !strcmp(status->new.name, pos->new.name)) {
5745                         select_view_line(view, lineno);
5746                         return TRUE;
5747                 }
5748         }
5750         return FALSE;
5754 static bool
5755 status_update_prepare(struct io *io, enum line_type type)
5757         const char *staged_argv[] = {
5758                 "git", "update-index", "-z", "--index-info", NULL
5759         };
5760         const char *others_argv[] = {
5761                 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
5762         };
5764         switch (type) {
5765         case LINE_STAT_STAGED:
5766                 return run_io(io, staged_argv, opt_cdup, IO_WR);
5768         case LINE_STAT_UNSTAGED:
5769         case LINE_STAT_UNTRACKED:
5770                 return run_io(io, others_argv, opt_cdup, IO_WR);
5772         default:
5773                 die("line type %d not handled in switch", type);
5774                 return FALSE;
5775         }
5778 static bool
5779 status_update_write(struct io *io, struct status *status, enum line_type type)
5781         char buf[SIZEOF_STR];
5782         size_t bufsize = 0;
5784         switch (type) {
5785         case LINE_STAT_STAGED:
5786                 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
5787                                         status->old.mode,
5788                                         status->old.rev,
5789                                         status->old.name, 0))
5790                         return FALSE;
5791                 break;
5793         case LINE_STAT_UNSTAGED:
5794         case LINE_STAT_UNTRACKED:
5795                 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
5796                         return FALSE;
5797                 break;
5799         default:
5800                 die("line type %d not handled in switch", type);
5801         }
5803         return io_write(io, buf, bufsize);
5806 static bool
5807 status_update_file(struct status *status, enum line_type type)
5809         struct io io = {};
5810         bool result;
5812         if (!status_update_prepare(&io, type))
5813                 return FALSE;
5815         result = status_update_write(&io, status, type);
5816         return done_io(&io) && result;
5819 static bool
5820 status_update_files(struct view *view, struct line *line)
5822         char buf[sizeof(view->ref)];
5823         struct io io = {};
5824         bool result = TRUE;
5825         struct line *pos = view->line + view->lines;
5826         int files = 0;
5827         int file, done;
5828         int cursor_y = -1, cursor_x = -1;
5830         if (!status_update_prepare(&io, line->type))
5831                 return FALSE;
5833         for (pos = line; pos < view->line + view->lines && pos->data; pos++)
5834                 files++;
5836         string_copy(buf, view->ref);
5837         getsyx(cursor_y, cursor_x);
5838         for (file = 0, done = 5; result && file < files; line++, file++) {
5839                 int almost_done = file * 100 / files;
5841                 if (almost_done > done) {
5842                         done = almost_done;
5843                         string_format(view->ref, "updating file %u of %u (%d%% done)",
5844                                       file, files, done);
5845                         update_view_title(view);
5846                         setsyx(cursor_y, cursor_x);
5847                         doupdate();
5848                 }
5849                 result = status_update_write(&io, line->data, line->type);
5850         }
5851         string_copy(view->ref, buf);
5853         return done_io(&io) && result;
5856 static bool
5857 status_update(struct view *view)
5859         struct line *line = &view->line[view->lineno];
5861         assert(view->lines);
5863         if (!line->data) {
5864                 /* This should work even for the "On branch" line. */
5865                 if (line < view->line + view->lines && !line[1].data) {
5866                         report("Nothing to update");
5867                         return FALSE;
5868                 }
5870                 if (!status_update_files(view, line + 1)) {
5871                         report("Failed to update file status");
5872                         return FALSE;
5873                 }
5875         } else if (!status_update_file(line->data, line->type)) {
5876                 report("Failed to update file status");
5877                 return FALSE;
5878         }
5880         return TRUE;
5883 static bool
5884 status_revert(struct status *status, enum line_type type, bool has_none)
5886         if (!status || type != LINE_STAT_UNSTAGED) {
5887                 if (type == LINE_STAT_STAGED) {
5888                         report("Cannot revert changes to staged files");
5889                 } else if (type == LINE_STAT_UNTRACKED) {
5890                         report("Cannot revert changes to untracked files");
5891                 } else if (has_none) {
5892                         report("Nothing to revert");
5893                 } else {
5894                         report("Cannot revert changes to multiple files");
5895                 }
5897         } else if (prompt_yesno("Are you sure you want to revert changes?")) {
5898                 char mode[10] = "100644";
5899                 const char *reset_argv[] = {
5900                         "git", "update-index", "--cacheinfo", mode,
5901                                 status->old.rev, status->old.name, NULL
5902                 };
5903                 const char *checkout_argv[] = {
5904                         "git", "checkout", "--", status->old.name, NULL
5905                 };
5907                 if (status->status == 'U') {
5908                         string_format(mode, "%5o", status->old.mode);
5910                         if (status->old.mode == 0 && status->new.mode == 0) {
5911                                 reset_argv[2] = "--force-remove";
5912                                 reset_argv[3] = status->old.name;
5913                                 reset_argv[4] = NULL;
5914                         }
5916                         if (!run_io_fg(reset_argv, opt_cdup))
5917                                 return FALSE;
5918                         if (status->old.mode == 0 && status->new.mode == 0)
5919                                 return TRUE;
5920                 }
5922                 return run_io_fg(checkout_argv, opt_cdup);
5923         }
5925         return FALSE;
5928 static enum request
5929 status_request(struct view *view, enum request request, struct line *line)
5931         struct status *status = line->data;
5933         switch (request) {
5934         case REQ_STATUS_UPDATE:
5935                 if (!status_update(view))
5936                         return REQ_NONE;
5937                 break;
5939         case REQ_STATUS_REVERT:
5940                 if (!status_revert(status, line->type, status_has_none(view, line)))
5941                         return REQ_NONE;
5942                 break;
5944         case REQ_STATUS_MERGE:
5945                 if (!status || status->status != 'U') {
5946                         report("Merging only possible for files with unmerged status ('U').");
5947                         return REQ_NONE;
5948                 }
5949                 open_mergetool(status->new.name);
5950                 break;
5952         case REQ_EDIT:
5953                 if (!status)
5954                         return request;
5955                 if (status->status == 'D') {
5956                         report("File has been deleted.");
5957                         return REQ_NONE;
5958                 }
5960                 open_editor(status->new.name);
5961                 break;
5963         case REQ_VIEW_BLAME:
5964                 if (status)
5965                         opt_ref[0] = 0;
5966                 return request;
5968         case REQ_ENTER:
5969                 /* After returning the status view has been split to
5970                  * show the stage view. No further reloading is
5971                  * necessary. */
5972                 return status_enter(view, line);
5974         case REQ_REFRESH:
5975                 /* Simply reload the view. */
5976                 break;
5978         default:
5979                 return request;
5980         }
5982         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
5984         return REQ_NONE;
5987 static void
5988 status_select(struct view *view, struct line *line)
5990         struct status *status = line->data;
5991         char file[SIZEOF_STR] = "all files";
5992         const char *text;
5993         const char *key;
5995         if (status && !string_format(file, "'%s'", status->new.name))
5996                 return;
5998         if (!status && line[1].type == LINE_STAT_NONE)
5999                 line++;
6001         switch (line->type) {
6002         case LINE_STAT_STAGED:
6003                 text = "Press %s to unstage %s for commit";
6004                 break;
6006         case LINE_STAT_UNSTAGED:
6007                 text = "Press %s to stage %s for commit";
6008                 break;
6010         case LINE_STAT_UNTRACKED:
6011                 text = "Press %s to stage %s for addition";
6012                 break;
6014         case LINE_STAT_HEAD:
6015         case LINE_STAT_NONE:
6016                 text = "Nothing to update";
6017                 break;
6019         default:
6020                 die("line type %d not handled in switch", line->type);
6021         }
6023         if (status && status->status == 'U') {
6024                 text = "Press %s to resolve conflict in %s";
6025                 key = get_key(KEYMAP_STATUS, REQ_STATUS_MERGE);
6027         } else {
6028                 key = get_key(KEYMAP_STATUS, REQ_STATUS_UPDATE);
6029         }
6031         string_format(view->ref, text, key, file);
6032         if (status)
6033                 string_copy(opt_file, status->new.name);
6036 static bool
6037 status_grep(struct view *view, struct line *line)
6039         struct status *status = line->data;
6041         if (status) {
6042                 const char buf[2] = { status->status, 0 };
6043                 const char *text[] = { status->new.name, buf, NULL };
6045                 return grep_text(view, text);
6046         }
6048         return FALSE;
6051 static struct view_ops status_ops = {
6052         "file",
6053         NULL,
6054         status_open,
6055         NULL,
6056         status_draw,
6057         status_request,
6058         status_grep,
6059         status_select,
6060 };
6063 static bool
6064 stage_diff_write(struct io *io, struct line *line, struct line *end)
6066         while (line < end) {
6067                 if (!io_write(io, line->data, strlen(line->data)) ||
6068                     !io_write(io, "\n", 1))
6069                         return FALSE;
6070                 line++;
6071                 if (line->type == LINE_DIFF_CHUNK ||
6072                     line->type == LINE_DIFF_HEADER)
6073                         break;
6074         }
6076         return TRUE;
6079 static struct line *
6080 stage_diff_find(struct view *view, struct line *line, enum line_type type)
6082         for (; view->line < line; line--)
6083                 if (line->type == type)
6084                         return line;
6086         return NULL;
6089 static bool
6090 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
6092         const char *apply_argv[SIZEOF_ARG] = {
6093                 "git", "apply", "--whitespace=nowarn", NULL
6094         };
6095         struct line *diff_hdr;
6096         struct io io = {};
6097         int argc = 3;
6099         diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
6100         if (!diff_hdr)
6101                 return FALSE;
6103         if (!revert)
6104                 apply_argv[argc++] = "--cached";
6105         if (revert || stage_line_type == LINE_STAT_STAGED)
6106                 apply_argv[argc++] = "-R";
6107         apply_argv[argc++] = "-";
6108         apply_argv[argc++] = NULL;
6109         if (!run_io(&io, apply_argv, opt_cdup, IO_WR))
6110                 return FALSE;
6112         if (!stage_diff_write(&io, diff_hdr, chunk) ||
6113             !stage_diff_write(&io, chunk, view->line + view->lines))
6114                 chunk = NULL;
6116         done_io(&io);
6117         run_io_bg(update_index_argv);
6119         return chunk ? TRUE : FALSE;
6122 static bool
6123 stage_update(struct view *view, struct line *line)
6125         struct line *chunk = NULL;
6127         if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
6128                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6130         if (chunk) {
6131                 if (!stage_apply_chunk(view, chunk, FALSE)) {
6132                         report("Failed to apply chunk");
6133                         return FALSE;
6134                 }
6136         } else if (!stage_status.status) {
6137                 view = VIEW(REQ_VIEW_STATUS);
6139                 for (line = view->line; line < view->line + view->lines; line++)
6140                         if (line->type == stage_line_type)
6141                                 break;
6143                 if (!status_update_files(view, line + 1)) {
6144                         report("Failed to update files");
6145                         return FALSE;
6146                 }
6148         } else if (!status_update_file(&stage_status, stage_line_type)) {
6149                 report("Failed to update file");
6150                 return FALSE;
6151         }
6153         return TRUE;
6156 static bool
6157 stage_revert(struct view *view, struct line *line)
6159         struct line *chunk = NULL;
6161         if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
6162                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6164         if (chunk) {
6165                 if (!prompt_yesno("Are you sure you want to revert changes?"))
6166                         return FALSE;
6168                 if (!stage_apply_chunk(view, chunk, TRUE)) {
6169                         report("Failed to revert chunk");
6170                         return FALSE;
6171                 }
6172                 return TRUE;
6174         } else {
6175                 return status_revert(stage_status.status ? &stage_status : NULL,
6176                                      stage_line_type, FALSE);
6177         }
6181 static void
6182 stage_next(struct view *view, struct line *line)
6184         int i;
6186         if (!stage_chunks) {
6187                 for (line = view->line; line < view->line + view->lines; line++) {
6188                         if (line->type != LINE_DIFF_CHUNK)
6189                                 continue;
6191                         if (!realloc_ints(&stage_chunk, stage_chunks, 1)) {
6192                                 report("Allocation failure");
6193                                 return;
6194                         }
6196                         stage_chunk[stage_chunks++] = line - view->line;
6197                 }
6198         }
6200         for (i = 0; i < stage_chunks; i++) {
6201                 if (stage_chunk[i] > view->lineno) {
6202                         do_scroll_view(view, stage_chunk[i] - view->lineno);
6203                         report("Chunk %d of %d", i + 1, stage_chunks);
6204                         return;
6205                 }
6206         }
6208         report("No next chunk found");
6211 static enum request
6212 stage_request(struct view *view, enum request request, struct line *line)
6214         switch (request) {
6215         case REQ_STATUS_UPDATE:
6216                 if (!stage_update(view, line))
6217                         return REQ_NONE;
6218                 break;
6220         case REQ_STATUS_REVERT:
6221                 if (!stage_revert(view, line))
6222                         return REQ_NONE;
6223                 break;
6225         case REQ_STAGE_NEXT:
6226                 if (stage_line_type == LINE_STAT_UNTRACKED) {
6227                         report("File is untracked; press %s to add",
6228                                get_key(KEYMAP_STAGE, REQ_STATUS_UPDATE));
6229                         return REQ_NONE;
6230                 }
6231                 stage_next(view, line);
6232                 return REQ_NONE;
6234         case REQ_EDIT:
6235                 if (!stage_status.new.name[0])
6236                         return request;
6237                 if (stage_status.status == 'D') {
6238                         report("File has been deleted.");
6239                         return REQ_NONE;
6240                 }
6242                 open_editor(stage_status.new.name);
6243                 break;
6245         case REQ_REFRESH:
6246                 /* Reload everything ... */
6247                 break;
6249         case REQ_VIEW_BLAME:
6250                 if (stage_status.new.name[0]) {
6251                         string_copy(opt_file, stage_status.new.name);
6252                         opt_ref[0] = 0;
6253                 }
6254                 return request;
6256         case REQ_ENTER:
6257                 return pager_request(view, request, line);
6259         default:
6260                 return request;
6261         }
6263         VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
6264         open_view(view, REQ_VIEW_STATUS, OPEN_REFRESH);
6266         /* Check whether the staged entry still exists, and close the
6267          * stage view if it doesn't. */
6268         if (!status_exists(&stage_status, stage_line_type)) {
6269                 status_restore(VIEW(REQ_VIEW_STATUS));
6270                 return REQ_VIEW_CLOSE;
6271         }
6273         if (stage_line_type == LINE_STAT_UNTRACKED) {
6274                 if (!suffixcmp(stage_status.new.name, -1, "/")) {
6275                         report("Cannot display a directory");
6276                         return REQ_NONE;
6277                 }
6279                 if (!prepare_update_file(view, stage_status.new.name)) {
6280                         report("Failed to open file: %s", strerror(errno));
6281                         return REQ_NONE;
6282                 }
6283         }
6284         open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
6286         return REQ_NONE;
6289 static struct view_ops stage_ops = {
6290         "line",
6291         NULL,
6292         NULL,
6293         pager_read,
6294         pager_draw,
6295         stage_request,
6296         pager_grep,
6297         pager_select,
6298 };
6301 /*
6302  * Revision graph
6303  */
6305 struct commit {
6306         char id[SIZEOF_REV];            /* SHA1 ID. */
6307         char title[128];                /* First line of the commit message. */
6308         const char *author;             /* Author of the commit. */
6309         struct time time;               /* Date from the author ident. */
6310         struct ref_list *refs;          /* Repository references. */
6311         chtype graph[SIZEOF_REVGRAPH];  /* Ancestry chain graphics. */
6312         size_t graph_size;              /* The width of the graph array. */
6313         bool has_parents;               /* Rewritten --parents seen. */
6314 };
6316 /* Size of rev graph with no  "padding" columns */
6317 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
6319 struct rev_graph {
6320         struct rev_graph *prev, *next, *parents;
6321         char rev[SIZEOF_REVITEMS][SIZEOF_REV];
6322         size_t size;
6323         struct commit *commit;
6324         size_t pos;
6325         unsigned int boundary:1;
6326 };
6328 /* Parents of the commit being visualized. */
6329 static struct rev_graph graph_parents[4];
6331 /* The current stack of revisions on the graph. */
6332 static struct rev_graph graph_stacks[4] = {
6333         { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
6334         { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
6335         { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
6336         { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
6337 };
6339 static inline bool
6340 graph_parent_is_merge(struct rev_graph *graph)
6342         return graph->parents->size > 1;
6345 static inline void
6346 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
6348         struct commit *commit = graph->commit;
6350         if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
6351                 commit->graph[commit->graph_size++] = symbol;
6354 static void
6355 clear_rev_graph(struct rev_graph *graph)
6357         graph->boundary = 0;
6358         graph->size = graph->pos = 0;
6359         graph->commit = NULL;
6360         memset(graph->parents, 0, sizeof(*graph->parents));
6363 static void
6364 done_rev_graph(struct rev_graph *graph)
6366         if (graph_parent_is_merge(graph) &&
6367             graph->pos < graph->size - 1 &&
6368             graph->next->size == graph->size + graph->parents->size - 1) {
6369                 size_t i = graph->pos + graph->parents->size - 1;
6371                 graph->commit->graph_size = i * 2;
6372                 while (i < graph->next->size - 1) {
6373                         append_to_rev_graph(graph, ' ');
6374                         append_to_rev_graph(graph, '\\');
6375                         i++;
6376                 }
6377         }
6379         clear_rev_graph(graph);
6382 static void
6383 push_rev_graph(struct rev_graph *graph, const char *parent)
6385         int i;
6387         /* "Collapse" duplicate parents lines.
6388          *
6389          * FIXME: This needs to also update update the drawn graph but
6390          * for now it just serves as a method for pruning graph lines. */
6391         for (i = 0; i < graph->size; i++)
6392                 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
6393                         return;
6395         if (graph->size < SIZEOF_REVITEMS) {
6396                 string_copy_rev(graph->rev[graph->size++], parent);
6397         }
6400 static chtype
6401 get_rev_graph_symbol(struct rev_graph *graph)
6403         chtype symbol;
6405         if (graph->boundary)
6406                 symbol = REVGRAPH_BOUND;
6407         else if (graph->parents->size == 0)
6408                 symbol = REVGRAPH_INIT;
6409         else if (graph_parent_is_merge(graph))
6410                 symbol = REVGRAPH_MERGE;
6411         else if (graph->pos >= graph->size)
6412                 symbol = REVGRAPH_BRANCH;
6413         else
6414                 symbol = REVGRAPH_COMMIT;
6416         return symbol;
6419 static void
6420 draw_rev_graph(struct rev_graph *graph)
6422         struct rev_filler {
6423                 chtype separator, line;
6424         };
6425         enum { DEFAULT, RSHARP, RDIAG, LDIAG };
6426         static struct rev_filler fillers[] = {
6427                 { ' ',  '|' },
6428                 { '`',  '.' },
6429                 { '\'', ' ' },
6430                 { '/',  ' ' },
6431         };
6432         chtype symbol = get_rev_graph_symbol(graph);
6433         struct rev_filler *filler;
6434         size_t i;
6436         fillers[DEFAULT].line = opt_line_graphics ? ACS_VLINE : '|';
6437         filler = &fillers[DEFAULT];
6439         for (i = 0; i < graph->pos; i++) {
6440                 append_to_rev_graph(graph, filler->line);
6441                 if (graph_parent_is_merge(graph->prev) &&
6442                     graph->prev->pos == i)
6443                         filler = &fillers[RSHARP];
6445                 append_to_rev_graph(graph, filler->separator);
6446         }
6448         /* Place the symbol for this revision. */
6449         append_to_rev_graph(graph, symbol);
6451         if (graph->prev->size > graph->size)
6452                 filler = &fillers[RDIAG];
6453         else
6454                 filler = &fillers[DEFAULT];
6456         i++;
6458         for (; i < graph->size; i++) {
6459                 append_to_rev_graph(graph, filler->separator);
6460                 append_to_rev_graph(graph, filler->line);
6461                 if (graph_parent_is_merge(graph->prev) &&
6462                     i < graph->prev->pos + graph->parents->size)
6463                         filler = &fillers[RSHARP];
6464                 if (graph->prev->size > graph->size)
6465                         filler = &fillers[LDIAG];
6466         }
6468         if (graph->prev->size > graph->size) {
6469                 append_to_rev_graph(graph, filler->separator);
6470                 if (filler->line != ' ')
6471                         append_to_rev_graph(graph, filler->line);
6472         }
6475 /* Prepare the next rev graph */
6476 static void
6477 prepare_rev_graph(struct rev_graph *graph)
6479         size_t i;
6481         /* First, traverse all lines of revisions up to the active one. */
6482         for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
6483                 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
6484                         break;
6486                 push_rev_graph(graph->next, graph->rev[graph->pos]);
6487         }
6489         /* Interleave the new revision parent(s). */
6490         for (i = 0; !graph->boundary && i < graph->parents->size; i++)
6491                 push_rev_graph(graph->next, graph->parents->rev[i]);
6493         /* Lastly, put any remaining revisions. */
6494         for (i = graph->pos + 1; i < graph->size; i++)
6495                 push_rev_graph(graph->next, graph->rev[i]);
6498 static void
6499 update_rev_graph(struct view *view, struct rev_graph *graph)
6501         /* If this is the finalizing update ... */
6502         if (graph->commit)
6503                 prepare_rev_graph(graph);
6505         /* Graph visualization needs a one rev look-ahead,
6506          * so the first update doesn't visualize anything. */
6507         if (!graph->prev->commit)
6508                 return;
6510         if (view->lines > 2)
6511                 view->line[view->lines - 3].dirty = 1;
6512         if (view->lines > 1)
6513                 view->line[view->lines - 2].dirty = 1;
6514         draw_rev_graph(graph->prev);
6515         done_rev_graph(graph->prev->prev);
6519 /*
6520  * Main view backend
6521  */
6523 static const char *main_argv[SIZEOF_ARG] = {
6524         "git", "log", "--no-color", "--pretty=raw", "--parents",
6525                       "--topo-order", "%(head)", NULL
6526 };
6528 static bool
6529 main_draw(struct view *view, struct line *line, unsigned int lineno)
6531         struct commit *commit = line->data;
6533         if (!commit->author)
6534                 return FALSE;
6536         if (opt_date && draw_date(view, &commit->time))
6537                 return TRUE;
6539         if (opt_author && draw_author(view, commit->author))
6540                 return TRUE;
6542         if (opt_rev_graph && commit->graph_size &&
6543             draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
6544                 return TRUE;
6546         if (opt_show_refs && commit->refs) {
6547                 size_t i;
6549                 for (i = 0; i < commit->refs->size; i++) {
6550                         struct ref *ref = commit->refs->refs[i];
6551                         enum line_type type;
6553                         if (ref->head)
6554                                 type = LINE_MAIN_HEAD;
6555                         else if (ref->ltag)
6556                                 type = LINE_MAIN_LOCAL_TAG;
6557                         else if (ref->tag)
6558                                 type = LINE_MAIN_TAG;
6559                         else if (ref->tracked)
6560                                 type = LINE_MAIN_TRACKED;
6561                         else if (ref->remote)
6562                                 type = LINE_MAIN_REMOTE;
6563                         else
6564                                 type = LINE_MAIN_REF;
6566                         if (draw_text(view, type, "[", TRUE) ||
6567                             draw_text(view, type, ref->name, TRUE) ||
6568                             draw_text(view, type, "]", TRUE))
6569                                 return TRUE;
6571                         if (draw_text(view, LINE_DEFAULT, " ", TRUE))
6572                                 return TRUE;
6573                 }
6574         }
6576         draw_text(view, LINE_DEFAULT, commit->title, TRUE);
6577         return TRUE;
6580 /* Reads git log --pretty=raw output and parses it into the commit struct. */
6581 static bool
6582 main_read(struct view *view, char *line)
6584         static struct rev_graph *graph = graph_stacks;
6585         enum line_type type;
6586         struct commit *commit;
6588         if (!line) {
6589                 int i;
6591                 if (!view->lines && !view->parent)
6592                         die("No revisions match the given arguments.");
6593                 if (view->lines > 0) {
6594                         commit = view->line[view->lines - 1].data;
6595                         view->line[view->lines - 1].dirty = 1;
6596                         if (!commit->author) {
6597                                 view->lines--;
6598                                 free(commit);
6599                                 graph->commit = NULL;
6600                         }
6601                 }
6602                 update_rev_graph(view, graph);
6604                 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
6605                         clear_rev_graph(&graph_stacks[i]);
6606                 return TRUE;
6607         }
6609         type = get_line_type(line);
6610         if (type == LINE_COMMIT) {
6611                 commit = calloc(1, sizeof(struct commit));
6612                 if (!commit)
6613                         return FALSE;
6615                 line += STRING_SIZE("commit ");
6616                 if (*line == '-') {
6617                         graph->boundary = 1;
6618                         line++;
6619                 }
6621                 string_copy_rev(commit->id, line);
6622                 commit->refs = get_ref_list(commit->id);
6623                 graph->commit = commit;
6624                 add_line_data(view, commit, LINE_MAIN_COMMIT);
6626                 while ((line = strchr(line, ' '))) {
6627                         line++;
6628                         push_rev_graph(graph->parents, line);
6629                         commit->has_parents = TRUE;
6630                 }
6631                 return TRUE;
6632         }
6634         if (!view->lines)
6635                 return TRUE;
6636         commit = view->line[view->lines - 1].data;
6638         switch (type) {
6639         case LINE_PARENT:
6640                 if (commit->has_parents)
6641                         break;
6642                 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
6643                 break;
6645         case LINE_AUTHOR:
6646                 parse_author_line(line + STRING_SIZE("author "),
6647                                   &commit->author, &commit->time);
6648                 update_rev_graph(view, graph);
6649                 graph = graph->next;
6650                 break;
6652         default:
6653                 /* Fill in the commit title if it has not already been set. */
6654                 if (commit->title[0])
6655                         break;
6657                 /* Require titles to start with a non-space character at the
6658                  * offset used by git log. */
6659                 if (strncmp(line, "    ", 4))
6660                         break;
6661                 line += 4;
6662                 /* Well, if the title starts with a whitespace character,
6663                  * try to be forgiving.  Otherwise we end up with no title. */
6664                 while (isspace(*line))
6665                         line++;
6666                 if (*line == '\0')
6667                         break;
6668                 /* FIXME: More graceful handling of titles; append "..." to
6669                  * shortened titles, etc. */
6671                 string_expand(commit->title, sizeof(commit->title), line, 1);
6672                 view->line[view->lines - 1].dirty = 1;
6673         }
6675         return TRUE;
6678 static enum request
6679 main_request(struct view *view, enum request request, struct line *line)
6681         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
6683         switch (request) {
6684         case REQ_ENTER:
6685                 open_view(view, REQ_VIEW_DIFF, flags);
6686                 break;
6687         case REQ_REFRESH:
6688                 load_refs();
6689                 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
6690                 break;
6691         default:
6692                 return request;
6693         }
6695         return REQ_NONE;
6698 static bool
6699 grep_refs(struct ref_list *list, regex_t *regex)
6701         regmatch_t pmatch;
6702         size_t i;
6704         if (!opt_show_refs || !list)
6705                 return FALSE;
6707         for (i = 0; i < list->size; i++) {
6708                 if (regexec(regex, list->refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
6709                         return TRUE;
6710         }
6712         return FALSE;
6715 static bool
6716 main_grep(struct view *view, struct line *line)
6718         struct commit *commit = line->data;
6719         const char *text[] = {
6720                 commit->title,
6721                 opt_author ? commit->author : "",
6722                 opt_date ? mkdate(&commit->time) : "",
6723                 NULL
6724         };
6726         return grep_text(view, text) || grep_refs(commit->refs, view->regex);
6729 static void
6730 main_select(struct view *view, struct line *line)
6732         struct commit *commit = line->data;
6734         string_copy_rev(view->ref, commit->id);
6735         string_copy_rev(ref_commit, view->ref);
6738 static struct view_ops main_ops = {
6739         "commit",
6740         main_argv,
6741         NULL,
6742         main_read,
6743         main_draw,
6744         main_request,
6745         main_grep,
6746         main_select,
6747 };
6750 /*
6751  * Unicode / UTF-8 handling
6752  *
6753  * NOTE: Much of the following code for dealing with Unicode is derived from
6754  * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
6755  * src/intl/charset.c from the UTF-8 branch commit elinks-0.11.0-g31f2c28.
6756  */
6758 static inline int
6759 unicode_width(unsigned long c)
6761         if (c >= 0x1100 &&
6762            (c <= 0x115f                         /* Hangul Jamo */
6763             || c == 0x2329
6764             || c == 0x232a
6765             || (c >= 0x2e80  && c <= 0xa4cf && c != 0x303f)
6766                                                 /* CJK ... Yi */
6767             || (c >= 0xac00  && c <= 0xd7a3)    /* Hangul Syllables */
6768             || (c >= 0xf900  && c <= 0xfaff)    /* CJK Compatibility Ideographs */
6769             || (c >= 0xfe30  && c <= 0xfe6f)    /* CJK Compatibility Forms */
6770             || (c >= 0xff00  && c <= 0xff60)    /* Fullwidth Forms */
6771             || (c >= 0xffe0  && c <= 0xffe6)
6772             || (c >= 0x20000 && c <= 0x2fffd)
6773             || (c >= 0x30000 && c <= 0x3fffd)))
6774                 return 2;
6776         if (c == '\t')
6777                 return opt_tab_size;
6779         return 1;
6782 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
6783  * Illegal bytes are set one. */
6784 static const unsigned char utf8_bytes[256] = {
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         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,
6791         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,
6792         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,
6793 };
6795 static inline unsigned char
6796 utf8_char_length(const char *string, const char *end)
6798         int c = *(unsigned char *) string;
6800         return utf8_bytes[c];
6803 /* Decode UTF-8 multi-byte representation into a Unicode character. */
6804 static inline unsigned long
6805 utf8_to_unicode(const char *string, size_t length)
6807         unsigned long unicode;
6809         switch (length) {
6810         case 1:
6811                 unicode  =   string[0];
6812                 break;
6813         case 2:
6814                 unicode  =  (string[0] & 0x1f) << 6;
6815                 unicode +=  (string[1] & 0x3f);
6816                 break;
6817         case 3:
6818                 unicode  =  (string[0] & 0x0f) << 12;
6819                 unicode += ((string[1] & 0x3f) << 6);
6820                 unicode +=  (string[2] & 0x3f);
6821                 break;
6822         case 4:
6823                 unicode  =  (string[0] & 0x0f) << 18;
6824                 unicode += ((string[1] & 0x3f) << 12);
6825                 unicode += ((string[2] & 0x3f) << 6);
6826                 unicode +=  (string[3] & 0x3f);
6827                 break;
6828         case 5:
6829                 unicode  =  (string[0] & 0x0f) << 24;
6830                 unicode += ((string[1] & 0x3f) << 18);
6831                 unicode += ((string[2] & 0x3f) << 12);
6832                 unicode += ((string[3] & 0x3f) << 6);
6833                 unicode +=  (string[4] & 0x3f);
6834                 break;
6835         case 6:
6836                 unicode  =  (string[0] & 0x01) << 30;
6837                 unicode += ((string[1] & 0x3f) << 24);
6838                 unicode += ((string[2] & 0x3f) << 18);
6839                 unicode += ((string[3] & 0x3f) << 12);
6840                 unicode += ((string[4] & 0x3f) << 6);
6841                 unicode +=  (string[5] & 0x3f);
6842                 break;
6843         default:
6844                 die("Invalid Unicode length");
6845         }
6847         /* Invalid characters could return the special 0xfffd value but NUL
6848          * should be just as good. */
6849         return unicode > 0xffff ? 0 : unicode;
6852 /* Calculates how much of string can be shown within the given maximum width
6853  * and sets trimmed parameter to non-zero value if all of string could not be
6854  * shown. If the reserve flag is TRUE, it will reserve at least one
6855  * trailing character, which can be useful when drawing a delimiter.
6856  *
6857  * Returns the number of bytes to output from string to satisfy max_width. */
6858 static size_t
6859 utf8_length(const char **start, size_t skip, int *width, size_t max_width, int *trimmed, bool reserve)
6861         const char *string = *start;
6862         const char *end = strchr(string, '\0');
6863         unsigned char last_bytes = 0;
6864         size_t last_ucwidth = 0;
6866         *width = 0;
6867         *trimmed = 0;
6869         while (string < end) {
6870                 unsigned char bytes = utf8_char_length(string, end);
6871                 size_t ucwidth;
6872                 unsigned long unicode;
6874                 if (string + bytes > end)
6875                         break;
6877                 /* Change representation to figure out whether
6878                  * it is a single- or double-width character. */
6880                 unicode = utf8_to_unicode(string, bytes);
6881                 /* FIXME: Graceful handling of invalid Unicode character. */
6882                 if (!unicode)
6883                         break;
6885                 ucwidth = unicode_width(unicode);
6886                 if (skip > 0) {
6887                         skip -= ucwidth <= skip ? ucwidth : skip;
6888                         *start += bytes;
6889                 }
6890                 *width  += ucwidth;
6891                 if (*width > max_width) {
6892                         *trimmed = 1;
6893                         *width -= ucwidth;
6894                         if (reserve && *width == max_width) {
6895                                 string -= last_bytes;
6896                                 *width -= last_ucwidth;
6897                         }
6898                         break;
6899                 }
6901                 string  += bytes;
6902                 last_bytes = ucwidth ? bytes : 0;
6903                 last_ucwidth = ucwidth;
6904         }
6906         return string - *start;
6910 /*
6911  * Status management
6912  */
6914 /* Whether or not the curses interface has been initialized. */
6915 static bool cursed = FALSE;
6917 /* Terminal hacks and workarounds. */
6918 static bool use_scroll_redrawwin;
6919 static bool use_scroll_status_wclear;
6921 /* The status window is used for polling keystrokes. */
6922 static WINDOW *status_win;
6924 /* Reading from the prompt? */
6925 static bool input_mode = FALSE;
6927 static bool status_empty = FALSE;
6929 /* Update status and title window. */
6930 static void
6931 report(const char *msg, ...)
6933         struct view *view = display[current_view];
6935         if (input_mode)
6936                 return;
6938         if (!view) {
6939                 char buf[SIZEOF_STR];
6940                 va_list args;
6942                 va_start(args, msg);
6943                 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
6944                         buf[sizeof(buf) - 1] = 0;
6945                         buf[sizeof(buf) - 2] = '.';
6946                         buf[sizeof(buf) - 3] = '.';
6947                         buf[sizeof(buf) - 4] = '.';
6948                 }
6949                 va_end(args);
6950                 die("%s", buf);
6951         }
6953         if (!status_empty || *msg) {
6954                 va_list args;
6956                 va_start(args, msg);
6958                 wmove(status_win, 0, 0);
6959                 if (view->has_scrolled && use_scroll_status_wclear)
6960                         wclear(status_win);
6961                 if (*msg) {
6962                         vwprintw(status_win, msg, args);
6963                         status_empty = FALSE;
6964                 } else {
6965                         status_empty = TRUE;
6966                 }
6967                 wclrtoeol(status_win);
6968                 wnoutrefresh(status_win);
6970                 va_end(args);
6971         }
6973         update_view_title(view);
6976 /* Controls when nodelay should be in effect when polling user input. */
6977 static void
6978 set_nonblocking_input(bool loading)
6980         static unsigned int loading_views;
6982         if ((loading == FALSE && loading_views-- == 1) ||
6983             (loading == TRUE  && loading_views++ == 0))
6984                 nodelay(status_win, loading);
6987 static void
6988 init_display(void)
6990         const char *term;
6991         int x, y;
6993         /* Initialize the curses library */
6994         if (isatty(STDIN_FILENO)) {
6995                 cursed = !!initscr();
6996                 opt_tty = stdin;
6997         } else {
6998                 /* Leave stdin and stdout alone when acting as a pager. */
6999                 opt_tty = fopen("/dev/tty", "r+");
7000                 if (!opt_tty)
7001                         die("Failed to open /dev/tty");
7002                 cursed = !!newterm(NULL, opt_tty, opt_tty);
7003         }
7005         if (!cursed)
7006                 die("Failed to initialize curses");
7008         nonl();         /* Disable conversion and detect newlines from input. */
7009         cbreak();       /* Take input chars one at a time, no wait for \n */
7010         noecho();       /* Don't echo input */
7011         leaveok(stdscr, FALSE);
7013         if (has_colors())
7014                 init_colors();
7016         getmaxyx(stdscr, y, x);
7017         status_win = newwin(1, 0, y - 1, 0);
7018         if (!status_win)
7019                 die("Failed to create status window");
7021         /* Enable keyboard mapping */
7022         keypad(status_win, TRUE);
7023         wbkgdset(status_win, get_line_attr(LINE_STATUS));
7025         TABSIZE = opt_tab_size;
7027         term = getenv("XTERM_VERSION") ? NULL : getenv("COLORTERM");
7028         if (term && !strcmp(term, "gnome-terminal")) {
7029                 /* In the gnome-terminal-emulator, the message from
7030                  * scrolling up one line when impossible followed by
7031                  * scrolling down one line causes corruption of the
7032                  * status line. This is fixed by calling wclear. */
7033                 use_scroll_status_wclear = TRUE;
7034                 use_scroll_redrawwin = FALSE;
7036         } else if (term && !strcmp(term, "xrvt-xpm")) {
7037                 /* No problems with full optimizations in xrvt-(unicode)
7038                  * and aterm. */
7039                 use_scroll_status_wclear = use_scroll_redrawwin = FALSE;
7041         } else {
7042                 /* When scrolling in (u)xterm the last line in the
7043                  * scrolling direction will update slowly. */
7044                 use_scroll_redrawwin = TRUE;
7045                 use_scroll_status_wclear = FALSE;
7046         }
7049 static int
7050 get_input(int prompt_position)
7052         struct view *view;
7053         int i, key, cursor_y, cursor_x;
7055         if (prompt_position)
7056                 input_mode = TRUE;
7058         while (TRUE) {
7059                 foreach_view (view, i) {
7060                         update_view(view);
7061                         if (view_is_displayed(view) && view->has_scrolled &&
7062                             use_scroll_redrawwin)
7063                                 redrawwin(view->win);
7064                         view->has_scrolled = FALSE;
7065                 }
7067                 /* Update the cursor position. */
7068                 if (prompt_position) {
7069                         getbegyx(status_win, cursor_y, cursor_x);
7070                         cursor_x = prompt_position;
7071                 } else {
7072                         view = display[current_view];
7073                         getbegyx(view->win, cursor_y, cursor_x);
7074                         cursor_x = view->width - 1;
7075                         cursor_y += view->lineno - view->offset;
7076                 }
7077                 setsyx(cursor_y, cursor_x);
7079                 /* Refresh, accept single keystroke of input */
7080                 doupdate();
7081                 key = wgetch(status_win);
7083                 /* wgetch() with nodelay() enabled returns ERR when
7084                  * there's no input. */
7085                 if (key == ERR) {
7087                 } else if (key == KEY_RESIZE) {
7088                         int height, width;
7090                         getmaxyx(stdscr, height, width);
7092                         wresize(status_win, 1, width);
7093                         mvwin(status_win, height - 1, 0);
7094                         wnoutrefresh(status_win);
7095                         resize_display();
7096                         redraw_display(TRUE);
7098                 } else {
7099                         input_mode = FALSE;
7100                         return key;
7101                 }
7102         }
7105 static char *
7106 prompt_input(const char *prompt, input_handler handler, void *data)
7108         enum input_status status = INPUT_OK;
7109         static char buf[SIZEOF_STR];
7110         size_t pos = 0;
7112         buf[pos] = 0;
7114         while (status == INPUT_OK || status == INPUT_SKIP) {
7115                 int key;
7117                 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
7118                 wclrtoeol(status_win);
7120                 key = get_input(pos + 1);
7121                 switch (key) {
7122                 case KEY_RETURN:
7123                 case KEY_ENTER:
7124                 case '\n':
7125                         status = pos ? INPUT_STOP : INPUT_CANCEL;
7126                         break;
7128                 case KEY_BACKSPACE:
7129                         if (pos > 0)
7130                                 buf[--pos] = 0;
7131                         else
7132                                 status = INPUT_CANCEL;
7133                         break;
7135                 case KEY_ESC:
7136                         status = INPUT_CANCEL;
7137                         break;
7139                 default:
7140                         if (pos >= sizeof(buf)) {
7141                                 report("Input string too long");
7142                                 return NULL;
7143                         }
7145                         status = handler(data, buf, key);
7146                         if (status == INPUT_OK)
7147                                 buf[pos++] = (char) key;
7148                 }
7149         }
7151         /* Clear the status window */
7152         status_empty = FALSE;
7153         report("");
7155         if (status == INPUT_CANCEL)
7156                 return NULL;
7158         buf[pos++] = 0;
7160         return buf;
7163 static enum input_status
7164 prompt_yesno_handler(void *data, char *buf, int c)
7166         if (c == 'y' || c == 'Y')
7167                 return INPUT_STOP;
7168         if (c == 'n' || c == 'N')
7169                 return INPUT_CANCEL;
7170         return INPUT_SKIP;
7173 static bool
7174 prompt_yesno(const char *prompt)
7176         char prompt2[SIZEOF_STR];
7178         if (!string_format(prompt2, "%s [Yy/Nn]", prompt))
7179                 return FALSE;
7181         return !!prompt_input(prompt2, prompt_yesno_handler, NULL);
7184 static enum input_status
7185 read_prompt_handler(void *data, char *buf, int c)
7187         return isprint(c) ? INPUT_OK : INPUT_SKIP;
7190 static char *
7191 read_prompt(const char *prompt)
7193         return prompt_input(prompt, read_prompt_handler, NULL);
7196 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected)
7198         enum input_status status = INPUT_OK;
7199         int size = 0;
7201         while (items[size].text)
7202                 size++;
7204         while (status == INPUT_OK) {
7205                 const struct menu_item *item = &items[*selected];
7206                 int key;
7207                 int i;
7209                 mvwprintw(status_win, 0, 0, "%s (%d of %d) ",
7210                           prompt, *selected + 1, size);
7211                 if (item->hotkey)
7212                         wprintw(status_win, "[%c] ", (char) item->hotkey);
7213                 wprintw(status_win, "%s", item->text);
7214                 wclrtoeol(status_win);
7216                 key = get_input(COLS - 1);
7217                 switch (key) {
7218                 case KEY_RETURN:
7219                 case KEY_ENTER:
7220                 case '\n':
7221                         status = INPUT_STOP;
7222                         break;
7224                 case KEY_LEFT:
7225                 case KEY_UP:
7226                         *selected = *selected - 1;
7227                         if (*selected < 0)
7228                                 *selected = size - 1;
7229                         break;
7231                 case KEY_RIGHT:
7232                 case KEY_DOWN:
7233                         *selected = (*selected + 1) % size;
7234                         break;
7236                 case KEY_ESC:
7237                         status = INPUT_CANCEL;
7238                         break;
7240                 default:
7241                         for (i = 0; items[i].text; i++)
7242                                 if (items[i].hotkey == key) {
7243                                         *selected = i;
7244                                         status = INPUT_STOP;
7245                                         break;
7246                                 }
7247                 }
7248         }
7250         /* Clear the status window */
7251         status_empty = FALSE;
7252         report("");
7254         return status != INPUT_CANCEL;
7257 /*
7258  * Repository properties
7259  */
7261 static struct ref **refs = NULL;
7262 static size_t refs_size = 0;
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_list *
7301 get_ref_list(const char *id)
7303         struct ref_list *list;
7304         size_t i;
7306         for (i = 0; i < ref_lists_size; i++)
7307                 if (!strcmp(id, ref_lists[i]->id))
7308                         return ref_lists[i];
7310         if (!realloc_ref_lists(&ref_lists, ref_lists_size, 1))
7311                 return NULL;
7312         list = calloc(1, sizeof(*list));
7313         if (!list)
7314                 return NULL;
7316         for (i = 0; i < refs_size; i++) {
7317                 if (!strcmp(id, refs[i]->id) &&
7318                     realloc_refs_list(&list->refs, list->size, 1))
7319                         list->refs[list->size++] = refs[i];
7320         }
7322         if (!list->refs) {
7323                 free(list);
7324                 return NULL;
7325         }
7327         qsort(list->refs, list->size, sizeof(*list->refs), compare_refs);
7328         ref_lists[ref_lists_size++] = list;
7329         return list;
7332 static int
7333 read_ref(char *id, size_t idlen, char *name, size_t namelen)
7335         struct ref *ref = NULL;
7336         bool tag = FALSE;
7337         bool ltag = FALSE;
7338         bool remote = FALSE;
7339         bool tracked = FALSE;
7340         bool head = FALSE;
7341         int from = 0, to = refs_size - 1;
7343         if (!prefixcmp(name, "refs/tags/")) {
7344                 if (!suffixcmp(name, namelen, "^{}")) {
7345                         namelen -= 3;
7346                         name[namelen] = 0;
7347                 } else {
7348                         ltag = TRUE;
7349                 }
7351                 tag = TRUE;
7352                 namelen -= STRING_SIZE("refs/tags/");
7353                 name    += STRING_SIZE("refs/tags/");
7355         } else if (!prefixcmp(name, "refs/remotes/")) {
7356                 remote = TRUE;
7357                 namelen -= STRING_SIZE("refs/remotes/");
7358                 name    += STRING_SIZE("refs/remotes/");
7359                 tracked  = !strcmp(opt_remote, name);
7361         } else if (!prefixcmp(name, "refs/heads/")) {
7362                 namelen -= STRING_SIZE("refs/heads/");
7363                 name    += STRING_SIZE("refs/heads/");
7364                 head     = !strncmp(opt_head, name, namelen);
7366         } else if (!strcmp(name, "HEAD")) {
7367                 string_ncopy(opt_head_rev, id, idlen);
7368                 return OK;
7369         }
7371         /* If we are reloading or it's an annotated tag, replace the
7372          * previous SHA1 with the resolved commit id; relies on the fact
7373          * git-ls-remote lists the commit id of an annotated tag right
7374          * before the commit id it points to. */
7375         while (from <= to) {
7376                 size_t pos = (to + from) / 2;
7377                 int cmp = strcmp(name, refs[pos]->name);
7379                 if (!cmp) {
7380                         ref = refs[pos];
7381                         break;
7382                 }
7384                 if (cmp < 0)
7385                         to = pos - 1;
7386                 else
7387                         from = pos + 1;
7388         }
7390         if (!ref) {
7391                 if (!realloc_refs(&refs, refs_size, 1))
7392                         return ERR;
7393                 ref = calloc(1, sizeof(*ref) + namelen);
7394                 if (!ref)
7395                         return ERR;
7396                 memmove(refs + from + 1, refs + from,
7397                         (refs_size - from) * sizeof(*refs));
7398                 refs[from] = ref;
7399                 strncpy(ref->name, name, namelen);
7400                 refs_size++;
7401         }
7403         ref->head = head;
7404         ref->tag = tag;
7405         ref->ltag = ltag;
7406         ref->remote = remote;
7407         ref->tracked = tracked;
7408         string_copy_rev(ref->id, id);
7410         return OK;
7413 static int
7414 load_refs(void)
7416         const char *head_argv[] = {
7417                 "git", "symbolic-ref", "HEAD", NULL
7418         };
7419         static const char *ls_remote_argv[SIZEOF_ARG] = {
7420                 "git", "ls-remote", opt_git_dir, NULL
7421         };
7422         static bool init = FALSE;
7423         size_t i;
7425         if (!init) {
7426                 argv_from_env(ls_remote_argv, "TIG_LS_REMOTE");
7427                 init = TRUE;
7428         }
7430         if (!*opt_git_dir)
7431                 return OK;
7433         if (run_io_buf(head_argv, opt_head, sizeof(opt_head)) &&
7434             !prefixcmp(opt_head, "refs/heads/")) {
7435                 char *offset = opt_head + STRING_SIZE("refs/heads/");
7437                 memmove(opt_head, offset, strlen(offset) + 1);
7438         }
7440         for (i = 0; i < refs_size; i++)
7441                 refs[i]->id[0] = 0;
7443         if (run_io_load(ls_remote_argv, "\t", read_ref) == ERR)
7444                 return ERR;
7446         /* Update the ref lists to reflect changes. */
7447         for (i = 0; i < ref_lists_size; i++) {
7448                 struct ref_list *list = ref_lists[i];
7449                 size_t old, new;
7451                 for (old = new = 0; old < list->size; old++)
7452                         if (!strcmp(list->id, list->refs[old]->id))
7453                                 list->refs[new++] = list->refs[old];
7454                 list->size = new;
7455         }
7457         return OK;
7460 static void
7461 set_remote_branch(const char *name, const char *value, size_t valuelen)
7463         if (!strcmp(name, ".remote")) {
7464                 string_ncopy(opt_remote, value, valuelen);
7466         } else if (*opt_remote && !strcmp(name, ".merge")) {
7467                 size_t from = strlen(opt_remote);
7469                 if (!prefixcmp(value, "refs/heads/"))
7470                         value += STRING_SIZE("refs/heads/");
7472                 if (!string_format_from(opt_remote, &from, "/%s", value))
7473                         opt_remote[0] = 0;
7474         }
7477 static void
7478 set_repo_config_option(char *name, char *value, int (*cmd)(int, const char **))
7480         const char *argv[SIZEOF_ARG] = { name, "=" };
7481         int argc = 1 + (cmd == option_set_command);
7482         int error = ERR;
7484         if (!argv_from_string(argv, &argc, value))
7485                 config_msg = "Too many option arguments";
7486         else
7487                 error = cmd(argc, argv);
7489         if (error == ERR)
7490                 warn("Option 'tig.%s': %s", name, config_msg);
7493 static bool
7494 set_environment_variable(const char *name, const char *value)
7496         size_t len = strlen(name) + 1 + strlen(value) + 1;
7497         char *env = malloc(len);
7499         if (env &&
7500             string_nformat(env, len, NULL, "%s=%s", name, value) &&
7501             putenv(env) == 0)
7502                 return TRUE;
7503         free(env);
7504         return FALSE;
7507 static void
7508 set_work_tree(const char *value)
7510         char cwd[SIZEOF_STR];
7512         if (!getcwd(cwd, sizeof(cwd)))
7513                 die("Failed to get cwd path: %s", strerror(errno));
7514         if (chdir(opt_git_dir) < 0)
7515                 die("Failed to chdir(%s): %s", strerror(errno));
7516         if (!getcwd(opt_git_dir, sizeof(opt_git_dir)))
7517                 die("Failed to get git path: %s", strerror(errno));
7518         if (chdir(cwd) < 0)
7519                 die("Failed to chdir(%s): %s", cwd, strerror(errno));
7520         if (chdir(value) < 0)
7521                 die("Failed to chdir(%s): %s", value, strerror(errno));
7522         if (!getcwd(cwd, sizeof(cwd)))
7523                 die("Failed to get cwd path: %s", strerror(errno));
7524         if (!set_environment_variable("GIT_WORK_TREE", cwd))
7525                 die("Failed to set GIT_WORK_TREE to '%s'", cwd);
7526         if (!set_environment_variable("GIT_DIR", opt_git_dir))
7527                 die("Failed to set GIT_DIR to '%s'", opt_git_dir);
7528         opt_is_inside_work_tree = TRUE;
7531 static int
7532 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
7534         if (!strcmp(name, "i18n.commitencoding"))
7535                 string_ncopy(opt_encoding, value, valuelen);
7537         else if (!strcmp(name, "core.editor"))
7538                 string_ncopy(opt_editor, value, valuelen);
7540         else if (!strcmp(name, "core.worktree"))
7541                 set_work_tree(value);
7543         else if (!prefixcmp(name, "tig.color."))
7544                 set_repo_config_option(name + 10, value, option_color_command);
7546         else if (!prefixcmp(name, "tig.bind."))
7547                 set_repo_config_option(name + 9, value, option_bind_command);
7549         else if (!prefixcmp(name, "tig."))
7550                 set_repo_config_option(name + 4, value, option_set_command);
7552         else if (*opt_head && !prefixcmp(name, "branch.") &&
7553                  !strncmp(name + 7, opt_head, strlen(opt_head)))
7554                 set_remote_branch(name + 7 + strlen(opt_head), value, valuelen);
7556         return OK;
7559 static int
7560 load_git_config(void)
7562         const char *config_list_argv[] = { "git", "config", "--list", NULL };
7564         return run_io_load(config_list_argv, "=", read_repo_config_option);
7567 static int
7568 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
7570         if (!opt_git_dir[0]) {
7571                 string_ncopy(opt_git_dir, name, namelen);
7573         } else if (opt_is_inside_work_tree == -1) {
7574                 /* This can be 3 different values depending on the
7575                  * version of git being used. If git-rev-parse does not
7576                  * understand --is-inside-work-tree it will simply echo
7577                  * the option else either "true" or "false" is printed.
7578                  * Default to true for the unknown case. */
7579                 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
7581         } else if (*name == '.') {
7582                 string_ncopy(opt_cdup, name, namelen);
7584         } else {
7585                 string_ncopy(opt_prefix, name, namelen);
7586         }
7588         return OK;
7591 static int
7592 load_repo_info(void)
7594         const char *rev_parse_argv[] = {
7595                 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
7596                         "--show-cdup", "--show-prefix", NULL
7597         };
7599         return run_io_load(rev_parse_argv, "=", read_repo_info);
7603 /*
7604  * Main
7605  */
7607 static const char usage[] =
7608 "tig " TIG_VERSION " (" __DATE__ ")\n"
7609 "\n"
7610 "Usage: tig        [options] [revs] [--] [paths]\n"
7611 "   or: tig show   [options] [revs] [--] [paths]\n"
7612 "   or: tig blame  [rev] path\n"
7613 "   or: tig status\n"
7614 "   or: tig <      [git command output]\n"
7615 "\n"
7616 "Options:\n"
7617 "  -v, --version   Show version and exit\n"
7618 "  -h, --help      Show help message and exit";
7620 static void __NORETURN
7621 quit(int sig)
7623         /* XXX: Restore tty modes and let the OS cleanup the rest! */
7624         if (cursed)
7625                 endwin();
7626         exit(0);
7629 static void __NORETURN
7630 die(const char *err, ...)
7632         va_list args;
7634         endwin();
7636         va_start(args, err);
7637         fputs("tig: ", stderr);
7638         vfprintf(stderr, err, args);
7639         fputs("\n", stderr);
7640         va_end(args);
7642         exit(1);
7645 static void
7646 warn(const char *msg, ...)
7648         va_list args;
7650         va_start(args, msg);
7651         fputs("tig warning: ", stderr);
7652         vfprintf(stderr, msg, args);
7653         fputs("\n", stderr);
7654         va_end(args);
7657 static enum request
7658 parse_options(int argc, const char *argv[])
7660         enum request request = REQ_VIEW_MAIN;
7661         const char *subcommand;
7662         bool seen_dashdash = FALSE;
7663         /* XXX: This is vulnerable to the user overriding options
7664          * required for the main view parser. */
7665         const char *custom_argv[SIZEOF_ARG] = {
7666                 "git", "log", "--no-color", "--pretty=raw", "--parents",
7667                         "--topo-order", NULL
7668         };
7669         int i, j = 6;
7671         if (!isatty(STDIN_FILENO)) {
7672                 io_open(&VIEW(REQ_VIEW_PAGER)->io, "");
7673                 return REQ_VIEW_PAGER;
7674         }
7676         if (argc <= 1)
7677                 return REQ_NONE;
7679         subcommand = argv[1];
7680         if (!strcmp(subcommand, "status")) {
7681                 if (argc > 2)
7682                         warn("ignoring arguments after `%s'", subcommand);
7683                 return REQ_VIEW_STATUS;
7685         } else if (!strcmp(subcommand, "blame")) {
7686                 if (argc <= 2 || argc > 4)
7687                         die("invalid number of options to blame\n\n%s", usage);
7689                 i = 2;
7690                 if (argc == 4) {
7691                         string_ncopy(opt_ref, argv[i], strlen(argv[i]));
7692                         i++;
7693                 }
7695                 string_ncopy(opt_file, argv[i], strlen(argv[i]));
7696                 return REQ_VIEW_BLAME;
7698         } else if (!strcmp(subcommand, "show")) {
7699                 request = REQ_VIEW_DIFF;
7701         } else {
7702                 subcommand = NULL;
7703         }
7705         if (subcommand) {
7706                 custom_argv[1] = subcommand;
7707                 j = 2;
7708         }
7710         for (i = 1 + !!subcommand; i < argc; i++) {
7711                 const char *opt = argv[i];
7713                 if (seen_dashdash || !strcmp(opt, "--")) {
7714                         seen_dashdash = TRUE;
7716                 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
7717                         printf("tig version %s\n", TIG_VERSION);
7718                         quit(0);
7720                 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
7721                         printf("%s\n", usage);
7722                         quit(0);
7723                 }
7725                 custom_argv[j++] = opt;
7726                 if (j >= ARRAY_SIZE(custom_argv))
7727                         die("command too long");
7728         }
7730         if (!prepare_update(VIEW(request), custom_argv, NULL, FORMAT_NONE))
7731                 die("Failed to format arguments");
7733         return request;
7736 int
7737 main(int argc, const char *argv[])
7739         enum request request = parse_options(argc, argv);
7740         struct view *view;
7741         size_t i;
7743         signal(SIGINT, quit);
7744         signal(SIGPIPE, SIG_IGN);
7746         if (setlocale(LC_ALL, "")) {
7747                 char *codeset = nl_langinfo(CODESET);
7749                 string_ncopy(opt_codeset, codeset, strlen(codeset));
7750         }
7752         if (load_repo_info() == ERR)
7753                 die("Failed to load repo info.");
7755         if (load_options() == ERR)
7756                 die("Failed to load user config.");
7758         if (load_git_config() == ERR)
7759                 die("Failed to load repo config.");
7761         /* Require a git repository unless when running in pager mode. */
7762         if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
7763                 die("Not a git repository");
7765         if (*opt_encoding && strcmp(opt_codeset, "UTF-8")) {
7766                 opt_iconv_in = iconv_open("UTF-8", opt_encoding);
7767                 if (opt_iconv_in == ICONV_NONE)
7768                         die("Failed to initialize character set conversion");
7769         }
7771         if (*opt_codeset && strcmp(opt_codeset, "UTF-8")) {
7772                 opt_iconv_out = iconv_open(opt_codeset, "UTF-8");
7773                 if (opt_iconv_out == ICONV_NONE)
7774                         die("Failed to initialize character set conversion");
7775         }
7777         if (load_refs() == ERR)
7778                 die("Failed to load refs.");
7780         foreach_view (view, i)
7781                 argv_from_env(view->ops->argv, view->cmd_env);
7783         init_display();
7785         if (request != REQ_NONE)
7786                 open_view(NULL, request, OPEN_PREPARED);
7787         request = request == REQ_NONE ? REQ_VIEW_MAIN : REQ_NONE;
7789         while (view_driver(display[current_view], request)) {
7790                 int key = get_input(0);
7792                 view = display[current_view];
7793                 request = get_keybinding(view->keymap, key);
7795                 /* Some low-level request handling. This keeps access to
7796                  * status_win restricted. */
7797                 switch (request) {
7798                 case REQ_PROMPT:
7799                 {
7800                         char *cmd = read_prompt(":");
7802                         if (cmd && isdigit(*cmd)) {
7803                                 int lineno = view->lineno + 1;
7805                                 if (parse_int(&lineno, cmd, 1, view->lines + 1) == OK) {
7806                                         select_view_line(view, lineno - 1);
7807                                         report("");
7808                                 } else {
7809                                         report("Unable to parse '%s' as a line number", cmd);
7810                                 }
7812                         } else if (cmd) {
7813                                 struct view *next = VIEW(REQ_VIEW_PAGER);
7814                                 const char *argv[SIZEOF_ARG] = { "git" };
7815                                 int argc = 1;
7817                                 /* When running random commands, initially show the
7818                                  * command in the title. However, it maybe later be
7819                                  * overwritten if a commit line is selected. */
7820                                 string_ncopy(next->ref, cmd, strlen(cmd));
7822                                 if (!argv_from_string(argv, &argc, cmd)) {
7823                                         report("Too many arguments");
7824                                 } else if (!prepare_update(next, argv, NULL, FORMAT_DASH)) {
7825                                         report("Failed to format command");
7826                                 } else {
7827                                         open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
7828                                 }
7829                         }
7831                         request = REQ_NONE;
7832                         break;
7833                 }
7834                 case REQ_SEARCH:
7835                 case REQ_SEARCH_BACK:
7836                 {
7837                         const char *prompt = request == REQ_SEARCH ? "/" : "?";
7838                         char *search = read_prompt(prompt);
7840                         if (search)
7841                                 string_ncopy(opt_search, search, strlen(search));
7842                         else if (*opt_search)
7843                                 request = request == REQ_SEARCH ?
7844                                         REQ_FIND_NEXT :
7845                                         REQ_FIND_PREV;
7846                         else
7847                                 request = REQ_NONE;
7848                         break;
7849                 }
7850                 default:
7851                         break;
7852                 }
7853         }
7855         quit(0);
7857         return 0;