Code

NEWS: Mention date-shorten feature
[tig.git] / tig.c
1 /* Copyright (c) 2006-2009 Jonas Fonseca <fonseca@diku.dk>
2  *
3  * This program is free software; you can redistribute it and/or
4  * modify it under the terms of the GNU General Public License as
5  * published by the Free Software Foundation; either version 2 of
6  * the License, or (at your option) any later version.
7  *
8  * This program is distributed in the hope that it will be useful,
9  * but WITHOUT ANY WARRANTY; without even the implied warranty of
10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11  * GNU General Public License for more details.
12  */
14 #ifdef HAVE_CONFIG_H
15 #include "config.h"
16 #endif
18 #ifndef TIG_VERSION
19 #define TIG_VERSION "unknown-version"
20 #endif
22 #ifndef DEBUG
23 #define NDEBUG
24 #endif
26 #include <assert.h>
27 #include <errno.h>
28 #include <ctype.h>
29 #include <signal.h>
30 #include <stdarg.h>
31 #include <stdio.h>
32 #include <stdlib.h>
33 #include <string.h>
34 #include <sys/types.h>
35 #include <sys/wait.h>
36 #include <sys/stat.h>
37 #include <sys/select.h>
38 #include <unistd.h>
39 #include <time.h>
40 #include <fcntl.h>
42 #include <regex.h>
44 #include <locale.h>
45 #include <langinfo.h>
46 #include <iconv.h>
48 /* ncurses(3): Must be defined to have extended wide-character functions. */
49 #define _XOPEN_SOURCE_EXTENDED
51 #ifdef HAVE_NCURSESW_NCURSES_H
52 #include <ncursesw/ncurses.h>
53 #else
54 #ifdef HAVE_NCURSES_NCURSES_H
55 #include <ncurses/ncurses.h>
56 #else
57 #include <ncurses.h>
58 #endif
59 #endif
61 #if __GNUC__ >= 3
62 #define __NORETURN __attribute__((__noreturn__))
63 #else
64 #define __NORETURN
65 #endif
67 static void __NORETURN die(const char *err, ...);
68 static void warn(const char *msg, ...);
69 static void report(const char *msg, ...);
70 static void set_nonblocking_input(bool loading);
71 static size_t utf8_length(const char **string, size_t col, int *width, size_t max_width, int *trimmed, bool reserve);
73 #define ABS(x)          ((x) >= 0  ? (x) : -(x))
74 #define MIN(x, y)       ((x) < (y) ? (x) :  (y))
75 #define MAX(x, y)       ((x) > (y) ? (x) :  (y))
77 #define ARRAY_SIZE(x)   (sizeof(x) / sizeof(x[0]))
78 #define STRING_SIZE(x)  (sizeof(x) - 1)
80 #define SIZEOF_STR      1024    /* Default string size. */
81 #define SIZEOF_REF      256     /* Size of symbolic or SHA1 ID. */
82 #define SIZEOF_REV      41      /* Holds a SHA-1 and an ending NUL. */
83 #define SIZEOF_ARG      32      /* Default argument array size. */
85 /* Revision graph */
87 #define REVGRAPH_INIT   'I'
88 #define REVGRAPH_MERGE  'M'
89 #define REVGRAPH_BRANCH '+'
90 #define REVGRAPH_COMMIT '*'
91 #define REVGRAPH_BOUND  '^'
93 #define SIZEOF_REVGRAPH 19      /* Size of revision ancestry graphics. */
95 /* This color name can be used to refer to the default term colors. */
96 #define COLOR_DEFAULT   (-1)
98 #define ICONV_NONE      ((iconv_t) -1)
99 #ifndef ICONV_CONST
100 #define ICONV_CONST     /* nothing */
101 #endif
103 /* The format and size of the date column in the main view. */
104 #define DATE_FORMAT     "%Y-%m-%d %H:%M"
105 #define DATE_COLS       STRING_SIZE("2006-04-29 14:21 ")
106 #define DATE_SHORT_COLS STRING_SIZE("2006-04-29 ")
108 #define ID_COLS         8
110 #define MIN_VIEW_HEIGHT 4
112 #define NULL_ID         "0000000000000000000000000000000000000000"
114 #define S_ISGITLINK(mode) (((mode) & S_IFMT) == 0160000)
116 #ifndef GIT_CONFIG
117 #define GIT_CONFIG "config"
118 #endif
120 /* Some ASCII-shorthands fitted into the ncurses namespace. */
121 #define KEY_TAB         '\t'
122 #define KEY_RETURN      '\r'
123 #define KEY_ESC         27
126 struct ref {
127         char id[SIZEOF_REV];    /* Commit SHA1 ID */
128         unsigned int head:1;    /* Is it the current HEAD? */
129         unsigned int tag:1;     /* Is it a tag? */
130         unsigned int ltag:1;    /* If so, is the tag local? */
131         unsigned int remote:1;  /* Is it a remote ref? */
132         unsigned int tracked:1; /* Is it the remote for the current HEAD? */
133         char name[1];           /* Ref name; tag or head names are shortened. */
134 };
136 struct ref_list {
137         char id[SIZEOF_REV];    /* Commit SHA1 ID */
138         size_t size;            /* Number of refs. */
139         struct ref **refs;      /* References for this ID. */
140 };
142 static struct ref_list *get_ref_list(const char *id);
143 static void foreach_ref(bool (*visitor)(void *data, struct ref *ref), void *data);
144 static int load_refs(void);
146 enum format_flags {
147         FORMAT_ALL,             /* Perform replacement in all arguments. */
148         FORMAT_DASH,            /* Perform replacement up until "--". */
149         FORMAT_NONE             /* No replacement should be performed. */
150 };
152 static bool format_argv(const char *dst[], const char *src[], enum format_flags flags);
154 enum input_status {
155         INPUT_OK,
156         INPUT_SKIP,
157         INPUT_STOP,
158         INPUT_CANCEL
159 };
161 typedef enum input_status (*input_handler)(void *data, char *buf, int c);
163 static char *prompt_input(const char *prompt, input_handler handler, void *data);
164 static bool prompt_yesno(const char *prompt);
166 struct menu_item {
167         int hotkey;
168         const char *text;
169         void *data;
170 };
172 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected);
174 /*
175  * Allocation helpers ... Entering macro hell to never be seen again.
176  */
178 #define DEFINE_ALLOCATOR(name, type, chunk_size)                                \
179 static type *                                                                   \
180 name(type **mem, size_t size, size_t increase)                                  \
181 {                                                                               \
182         size_t num_chunks = (size + chunk_size - 1) / chunk_size;               \
183         size_t num_chunks_new = (size + increase + chunk_size - 1) / chunk_size;\
184         type *tmp = *mem;                                                       \
185                                                                                 \
186         if (mem == NULL || num_chunks != num_chunks_new) {                      \
187                 tmp = realloc(tmp, num_chunks_new * chunk_size * sizeof(type)); \
188                 if (tmp)                                                        \
189                         *mem = tmp;                                             \
190         }                                                                       \
191                                                                                 \
192         return tmp;                                                             \
195 /*
196  * String helpers
197  */
199 static inline void
200 string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t srclen)
202         if (srclen > dstlen - 1)
203                 srclen = dstlen - 1;
205         strncpy(dst, src, srclen);
206         dst[srclen] = 0;
209 /* Shorthands for safely copying into a fixed buffer. */
211 #define string_copy(dst, src) \
212         string_ncopy_do(dst, sizeof(dst), src, sizeof(src))
214 #define string_ncopy(dst, src, srclen) \
215         string_ncopy_do(dst, sizeof(dst), src, srclen)
217 #define string_copy_rev(dst, src) \
218         string_ncopy_do(dst, SIZEOF_REV, src, SIZEOF_REV - 1)
220 #define string_add(dst, from, src) \
221         string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src))
223 static void
224 string_expand(char *dst, size_t dstlen, const char *src, int tabsize)
226         size_t size, pos;
228         for (size = pos = 0; size < dstlen - 1 && src[pos]; pos++) {
229                 if (src[pos] == '\t') {
230                         size_t expanded = tabsize - (size % tabsize);
232                         if (expanded + size >= dstlen - 1)
233                                 expanded = dstlen - size - 1;
234                         memcpy(dst + size, "        ", expanded);
235                         size += expanded;
236                 } else {
237                         dst[size++] = src[pos];
238                 }
239         }
241         dst[size] = 0;
244 static char *
245 chomp_string(char *name)
247         int namelen;
249         while (isspace(*name))
250                 name++;
252         namelen = strlen(name) - 1;
253         while (namelen > 0 && isspace(name[namelen]))
254                 name[namelen--] = 0;
256         return name;
259 static bool
260 string_nformat(char *buf, size_t bufsize, size_t *bufpos, const char *fmt, ...)
262         va_list args;
263         size_t pos = bufpos ? *bufpos : 0;
265         va_start(args, fmt);
266         pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
267         va_end(args);
269         if (bufpos)
270                 *bufpos = pos;
272         return pos >= bufsize ? FALSE : TRUE;
275 #define string_format(buf, fmt, args...) \
276         string_nformat(buf, sizeof(buf), NULL, fmt, args)
278 #define string_format_from(buf, from, fmt, args...) \
279         string_nformat(buf, sizeof(buf), from, fmt, args)
281 static int
282 string_enum_compare(const char *str1, const char *str2, int len)
284         size_t i;
286 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
288         /* Diff-Header == DIFF_HEADER */
289         for (i = 0; i < len; i++) {
290                 if (toupper(str1[i]) == toupper(str2[i]))
291                         continue;
293                 if (string_enum_sep(str1[i]) &&
294                     string_enum_sep(str2[i]))
295                         continue;
297                 return str1[i] - str2[i];
298         }
300         return 0;
303 struct enum_map {
304         const char *name;
305         int namelen;
306         int value;
307 };
309 #define ENUM_MAP(name, value) { name, STRING_SIZE(name), value }
311 static bool
312 map_enum_do(const struct enum_map *map, size_t map_size, int *value, const char *name)
314         size_t namelen = strlen(name);
315         int i;
317         for (i = 0; i < map_size; i++)
318                 if (namelen == map[i].namelen &&
319                     !string_enum_compare(name, map[i].name, namelen)) {
320                         *value = map[i].value;
321                         return TRUE;
322                 }
324         return FALSE;
327 #define map_enum(attr, map, name) \
328         map_enum_do(map, ARRAY_SIZE(map), attr, name)
330 #define prefixcmp(str1, str2) \
331         strncmp(str1, str2, STRING_SIZE(str2))
333 static inline int
334 suffixcmp(const char *str, int slen, const char *suffix)
336         size_t len = slen >= 0 ? slen : strlen(str);
337         size_t suffixlen = strlen(suffix);
339         return suffixlen < len ? strcmp(str + len - suffixlen, suffix) : -1;
343 static const char *
344 mkdate(const time_t *time)
346         static char buf[DATE_COLS + 1];
347         struct tm tm;
349         gmtime_r(time, &tm);
350         return strftime(buf, sizeof(buf), DATE_FORMAT, &tm) ? buf : NULL;
354 static bool
355 argv_from_string(const char *argv[SIZEOF_ARG], int *argc, char *cmd)
357         int valuelen;
359         while (*cmd && *argc < SIZEOF_ARG && (valuelen = strcspn(cmd, " \t"))) {
360                 bool advance = cmd[valuelen] != 0;
362                 cmd[valuelen] = 0;
363                 argv[(*argc)++] = chomp_string(cmd);
364                 cmd = chomp_string(cmd + valuelen + advance);
365         }
367         if (*argc < SIZEOF_ARG)
368                 argv[*argc] = NULL;
369         return *argc < SIZEOF_ARG;
372 static void
373 argv_from_env(const char **argv, const char *name)
375         char *env = argv ? getenv(name) : NULL;
376         int argc = 0;
378         if (env && *env)
379                 env = strdup(env);
380         if (env && !argv_from_string(argv, &argc, env))
381                 die("Too many arguments in the `%s` environment variable", name);
385 /*
386  * Executing external commands.
387  */
389 enum io_type {
390         IO_FD,                  /* File descriptor based IO. */
391         IO_BG,                  /* Execute command in the background. */
392         IO_FG,                  /* Execute command with same std{in,out,err}. */
393         IO_RD,                  /* Read only fork+exec IO. */
394         IO_WR,                  /* Write only fork+exec IO. */
395         IO_AP,                  /* Append fork+exec output to file. */
396 };
398 struct io {
399         enum io_type type;      /* The requested type of pipe. */
400         const char *dir;        /* Directory from which to execute. */
401         pid_t pid;              /* Pipe for reading or writing. */
402         int pipe;               /* Pipe end for reading or writing. */
403         int error;              /* Error status. */
404         const char *argv[SIZEOF_ARG];   /* Shell command arguments. */
405         char *buf;              /* Read buffer. */
406         size_t bufalloc;        /* Allocated buffer size. */
407         size_t bufsize;         /* Buffer content size. */
408         char *bufpos;           /* Current buffer position. */
409         unsigned int eof:1;     /* Has end of file been reached. */
410 };
412 static void
413 reset_io(struct io *io)
415         io->pipe = -1;
416         io->pid = 0;
417         io->buf = io->bufpos = NULL;
418         io->bufalloc = io->bufsize = 0;
419         io->error = 0;
420         io->eof = 0;
423 static void
424 init_io(struct io *io, const char *dir, enum io_type type)
426         reset_io(io);
427         io->type = type;
428         io->dir = dir;
431 static bool
432 init_io_rd(struct io *io, const char *argv[], const char *dir,
433                 enum format_flags flags)
435         init_io(io, dir, IO_RD);
436         return format_argv(io->argv, argv, flags);
439 static bool
440 io_open(struct io *io, const char *name)
442         init_io(io, NULL, IO_FD);
443         io->pipe = *name ? open(name, O_RDONLY) : STDIN_FILENO;
444         if (io->pipe == -1)
445                 io->error = errno;
446         return io->pipe != -1;
449 static bool
450 kill_io(struct io *io)
452         return io->pid == 0 || kill(io->pid, SIGKILL) != -1;
455 static bool
456 done_io(struct io *io)
458         pid_t pid = io->pid;
460         if (io->pipe != -1)
461                 close(io->pipe);
462         free(io->buf);
463         reset_io(io);
465         while (pid > 0) {
466                 int status;
467                 pid_t waiting = waitpid(pid, &status, 0);
469                 if (waiting < 0) {
470                         if (errno == EINTR)
471                                 continue;
472                         report("waitpid failed (%s)", strerror(errno));
473                         return FALSE;
474                 }
476                 return waiting == pid &&
477                        !WIFSIGNALED(status) &&
478                        WIFEXITED(status) &&
479                        !WEXITSTATUS(status);
480         }
482         return TRUE;
485 static bool
486 start_io(struct io *io)
488         int pipefds[2] = { -1, -1 };
490         if (io->type == IO_FD)
491                 return TRUE;
493         if ((io->type == IO_RD || io->type == IO_WR) &&
494             pipe(pipefds) < 0)
495                 return FALSE;
496         else if (io->type == IO_AP)
497                 pipefds[1] = io->pipe;
499         if ((io->pid = fork())) {
500                 if (pipefds[!(io->type == IO_WR)] != -1)
501                         close(pipefds[!(io->type == IO_WR)]);
502                 if (io->pid != -1) {
503                         io->pipe = pipefds[!!(io->type == IO_WR)];
504                         return TRUE;
505                 }
507         } else {
508                 if (io->type != IO_FG) {
509                         int devnull = open("/dev/null", O_RDWR);
510                         int readfd  = io->type == IO_WR ? pipefds[0] : devnull;
511                         int writefd = (io->type == IO_RD || io->type == IO_AP)
512                                                         ? pipefds[1] : devnull;
514                         dup2(readfd,  STDIN_FILENO);
515                         dup2(writefd, STDOUT_FILENO);
516                         dup2(devnull, STDERR_FILENO);
518                         close(devnull);
519                         if (pipefds[0] != -1)
520                                 close(pipefds[0]);
521                         if (pipefds[1] != -1)
522                                 close(pipefds[1]);
523                 }
525                 if (io->dir && *io->dir && chdir(io->dir) == -1)
526                         die("Failed to change directory: %s", strerror(errno));
528                 execvp(io->argv[0], (char *const*) io->argv);
529                 die("Failed to execute program: %s", strerror(errno));
530         }
532         if (pipefds[!!(io->type == IO_WR)] != -1)
533                 close(pipefds[!!(io->type == IO_WR)]);
534         return FALSE;
537 static bool
538 run_io(struct io *io, const char **argv, const char *dir, enum io_type type)
540         init_io(io, dir, type);
541         if (!format_argv(io->argv, argv, FORMAT_NONE))
542                 return FALSE;
543         return start_io(io);
546 static int
547 run_io_do(struct io *io)
549         return start_io(io) && done_io(io);
552 static int
553 run_io_bg(const char **argv)
555         struct io io = {};
557         init_io(&io, NULL, IO_BG);
558         if (!format_argv(io.argv, argv, FORMAT_NONE))
559                 return FALSE;
560         return run_io_do(&io);
563 static bool
564 run_io_fg(const char **argv, const char *dir)
566         struct io io = {};
568         init_io(&io, dir, IO_FG);
569         if (!format_argv(io.argv, argv, FORMAT_NONE))
570                 return FALSE;
571         return run_io_do(&io);
574 static bool
575 run_io_append(const char **argv, enum format_flags flags, int fd)
577         struct io io = {};
579         init_io(&io, NULL, IO_AP);
580         io.pipe = fd;
581         if (format_argv(io.argv, argv, flags))
582                 return run_io_do(&io);
583         close(fd);
584         return FALSE;
587 static bool
588 run_io_rd(struct io *io, const char **argv, enum format_flags flags)
590         return init_io_rd(io, argv, NULL, flags) && start_io(io);
593 static bool
594 io_eof(struct io *io)
596         return io->eof;
599 static int
600 io_error(struct io *io)
602         return io->error;
605 static char *
606 io_strerror(struct io *io)
608         return strerror(io->error);
611 static bool
612 io_can_read(struct io *io)
614         struct timeval tv = { 0, 500 };
615         fd_set fds;
617         FD_ZERO(&fds);
618         FD_SET(io->pipe, &fds);
620         return select(io->pipe + 1, &fds, NULL, NULL, &tv) > 0;
623 static ssize_t
624 io_read(struct io *io, void *buf, size_t bufsize)
626         do {
627                 ssize_t readsize = read(io->pipe, buf, bufsize);
629                 if (readsize < 0 && (errno == EAGAIN || errno == EINTR))
630                         continue;
631                 else if (readsize == -1)
632                         io->error = errno;
633                 else if (readsize == 0)
634                         io->eof = 1;
635                 return readsize;
636         } while (1);
639 DEFINE_ALLOCATOR(realloc_io_buf, char, BUFSIZ)
641 static char *
642 io_get(struct io *io, int c, bool can_read)
644         char *eol;
645         ssize_t readsize;
647         while (TRUE) {
648                 if (io->bufsize > 0) {
649                         eol = memchr(io->bufpos, c, io->bufsize);
650                         if (eol) {
651                                 char *line = io->bufpos;
653                                 *eol = 0;
654                                 io->bufpos = eol + 1;
655                                 io->bufsize -= io->bufpos - line;
656                                 return line;
657                         }
658                 }
660                 if (io_eof(io)) {
661                         if (io->bufsize) {
662                                 io->bufpos[io->bufsize] = 0;
663                                 io->bufsize = 0;
664                                 return io->bufpos;
665                         }
666                         return NULL;
667                 }
669                 if (!can_read)
670                         return NULL;
672                 if (io->bufsize > 0 && io->bufpos > io->buf)
673                         memmove(io->buf, io->bufpos, io->bufsize);
675                 if (io->bufalloc == io->bufsize) {
676                         if (!realloc_io_buf(&io->buf, io->bufalloc, BUFSIZ))
677                                 return NULL;
678                         io->bufalloc += BUFSIZ;
679                 }
681                 io->bufpos = io->buf;
682                 readsize = io_read(io, io->buf + io->bufsize, io->bufalloc - io->bufsize);
683                 if (io_error(io))
684                         return NULL;
685                 io->bufsize += readsize;
686         }
689 static bool
690 io_write(struct io *io, const void *buf, size_t bufsize)
692         size_t written = 0;
694         while (!io_error(io) && written < bufsize) {
695                 ssize_t size;
697                 size = write(io->pipe, buf + written, bufsize - written);
698                 if (size < 0 && (errno == EAGAIN || errno == EINTR))
699                         continue;
700                 else if (size == -1)
701                         io->error = errno;
702                 else
703                         written += size;
704         }
706         return written == bufsize;
709 static bool
710 io_read_buf(struct io *io, char buf[], size_t bufsize)
712         char *result = io_get(io, '\n', TRUE);
714         if (result) {
715                 result = chomp_string(result);
716                 string_ncopy_do(buf, bufsize, result, strlen(result));
717         }
719         return done_io(io) && result;
722 static bool
723 run_io_buf(const char **argv, char buf[], size_t bufsize)
725         struct io io = {};
727         return run_io_rd(&io, argv, FORMAT_NONE) && io_read_buf(&io, buf, bufsize);
730 static int
731 io_load(struct io *io, const char *separators,
732         int (*read_property)(char *, size_t, char *, size_t))
734         char *name;
735         int state = OK;
737         if (!start_io(io))
738                 return ERR;
740         while (state == OK && (name = io_get(io, '\n', TRUE))) {
741                 char *value;
742                 size_t namelen;
743                 size_t valuelen;
745                 name = chomp_string(name);
746                 namelen = strcspn(name, separators);
748                 if (name[namelen]) {
749                         name[namelen] = 0;
750                         value = chomp_string(name + namelen + 1);
751                         valuelen = strlen(value);
753                 } else {
754                         value = "";
755                         valuelen = 0;
756                 }
758                 state = read_property(name, namelen, value, valuelen);
759         }
761         if (state != ERR && io_error(io))
762                 state = ERR;
763         done_io(io);
765         return state;
768 static int
769 run_io_load(const char **argv, const char *separators,
770             int (*read_property)(char *, size_t, char *, size_t))
772         struct io io = {};
774         return init_io_rd(&io, argv, NULL, FORMAT_NONE)
775                 ? io_load(&io, separators, read_property) : ERR;
779 /*
780  * User requests
781  */
783 #define REQ_INFO \
784         /* XXX: Keep the view request first and in sync with views[]. */ \
785         REQ_GROUP("View switching") \
786         REQ_(VIEW_MAIN,         "Show main view"), \
787         REQ_(VIEW_DIFF,         "Show diff view"), \
788         REQ_(VIEW_LOG,          "Show log view"), \
789         REQ_(VIEW_TREE,         "Show tree view"), \
790         REQ_(VIEW_BLOB,         "Show blob view"), \
791         REQ_(VIEW_BLAME,        "Show blame view"), \
792         REQ_(VIEW_BRANCH,       "Show branch view"), \
793         REQ_(VIEW_HELP,         "Show help page"), \
794         REQ_(VIEW_PAGER,        "Show pager view"), \
795         REQ_(VIEW_STATUS,       "Show status view"), \
796         REQ_(VIEW_STAGE,        "Show stage view"), \
797         \
798         REQ_GROUP("View manipulation") \
799         REQ_(ENTER,             "Enter current line and scroll"), \
800         REQ_(NEXT,              "Move to next"), \
801         REQ_(PREVIOUS,          "Move to previous"), \
802         REQ_(PARENT,            "Move to parent"), \
803         REQ_(VIEW_NEXT,         "Move focus to next view"), \
804         REQ_(REFRESH,           "Reload and refresh"), \
805         REQ_(MAXIMIZE,          "Maximize the current view"), \
806         REQ_(VIEW_CLOSE,        "Close the current view"), \
807         REQ_(QUIT,              "Close all views and quit"), \
808         \
809         REQ_GROUP("View specific requests") \
810         REQ_(STATUS_UPDATE,     "Update file status"), \
811         REQ_(STATUS_REVERT,     "Revert file changes"), \
812         REQ_(STATUS_MERGE,      "Merge file using external tool"), \
813         REQ_(STAGE_NEXT,        "Find next chunk to stage"), \
814         \
815         REQ_GROUP("Cursor navigation") \
816         REQ_(MOVE_UP,           "Move cursor one line up"), \
817         REQ_(MOVE_DOWN,         "Move cursor one line down"), \
818         REQ_(MOVE_PAGE_DOWN,    "Move cursor one page down"), \
819         REQ_(MOVE_PAGE_UP,      "Move cursor one page up"), \
820         REQ_(MOVE_FIRST_LINE,   "Move cursor to first line"), \
821         REQ_(MOVE_LAST_LINE,    "Move cursor to last line"), \
822         \
823         REQ_GROUP("Scrolling") \
824         REQ_(SCROLL_LEFT,       "Scroll two columns left"), \
825         REQ_(SCROLL_RIGHT,      "Scroll two columns right"), \
826         REQ_(SCROLL_LINE_UP,    "Scroll one line up"), \
827         REQ_(SCROLL_LINE_DOWN,  "Scroll one line down"), \
828         REQ_(SCROLL_PAGE_UP,    "Scroll one page up"), \
829         REQ_(SCROLL_PAGE_DOWN,  "Scroll one page down"), \
830         \
831         REQ_GROUP("Searching") \
832         REQ_(SEARCH,            "Search the view"), \
833         REQ_(SEARCH_BACK,       "Search backwards in the view"), \
834         REQ_(FIND_NEXT,         "Find next search match"), \
835         REQ_(FIND_PREV,         "Find previous search match"), \
836         \
837         REQ_GROUP("Option manipulation") \
838         REQ_(OPTIONS,           "Open option menu"), \
839         REQ_(TOGGLE_LINENO,     "Toggle line numbers"), \
840         REQ_(TOGGLE_DATE,       "Toggle date display"), \
841         REQ_(TOGGLE_DATE_SHORT, "Toggle short (date-only) dates"), \
842         REQ_(TOGGLE_AUTHOR,     "Toggle author display"), \
843         REQ_(TOGGLE_REV_GRAPH,  "Toggle revision graph visualization"), \
844         REQ_(TOGGLE_REFS,       "Toggle reference display (tags/branches)"), \
845         REQ_(TOGGLE_SORT_ORDER, "Toggle ascending/descending sort order"), \
846         REQ_(TOGGLE_SORT_FIELD, "Toggle field to sort by"), \
847         \
848         REQ_GROUP("Misc") \
849         REQ_(PROMPT,            "Bring up the prompt"), \
850         REQ_(SCREEN_REDRAW,     "Redraw the screen"), \
851         REQ_(SHOW_VERSION,      "Show version information"), \
852         REQ_(STOP_LOADING,      "Stop all loading views"), \
853         REQ_(EDIT,              "Open in editor"), \
854         REQ_(NONE,              "Do nothing")
857 /* User action requests. */
858 enum request {
859 #define REQ_GROUP(help)
860 #define REQ_(req, help) REQ_##req
862         /* Offset all requests to avoid conflicts with ncurses getch values. */
863         REQ_OFFSET = KEY_MAX + 1,
864         REQ_INFO
866 #undef  REQ_GROUP
867 #undef  REQ_
868 };
870 struct request_info {
871         enum request request;
872         const char *name;
873         int namelen;
874         const char *help;
875 };
877 static const struct request_info req_info[] = {
878 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
879 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
880         REQ_INFO
881 #undef  REQ_GROUP
882 #undef  REQ_
883 };
885 static enum request
886 get_request(const char *name)
888         int namelen = strlen(name);
889         int i;
891         for (i = 0; i < ARRAY_SIZE(req_info); i++)
892                 if (req_info[i].namelen == namelen &&
893                     !string_enum_compare(req_info[i].name, name, namelen))
894                         return req_info[i].request;
896         return REQ_NONE;
900 /*
901  * Options
902  */
904 /* Option and state variables. */
905 static bool opt_date                    = TRUE;
906 static bool opt_date_short              = FALSE;
907 static bool opt_author                  = TRUE;
908 static bool opt_line_number             = FALSE;
909 static bool opt_line_graphics           = TRUE;
910 static bool opt_rev_graph               = FALSE;
911 static bool opt_show_refs               = TRUE;
912 static int opt_num_interval             = 5;
913 static double opt_hscroll               = 0.50;
914 static double opt_scale_split_view      = 2.0 / 3.0;
915 static int opt_tab_size                 = 8;
916 static int opt_author_cols              = 19;
917 static char opt_path[SIZEOF_STR]        = "";
918 static char opt_file[SIZEOF_STR]        = "";
919 static char opt_ref[SIZEOF_REF]         = "";
920 static char opt_head[SIZEOF_REF]        = "";
921 static char opt_head_rev[SIZEOF_REV]    = "";
922 static char opt_remote[SIZEOF_REF]      = "";
923 static char opt_encoding[20]            = "UTF-8";
924 static bool opt_utf8                    = TRUE;
925 static char opt_codeset[20]             = "UTF-8";
926 static iconv_t opt_iconv                = ICONV_NONE;
927 static char opt_search[SIZEOF_STR]      = "";
928 static char opt_cdup[SIZEOF_STR]        = "";
929 static char opt_prefix[SIZEOF_STR]      = "";
930 static char opt_git_dir[SIZEOF_STR]     = "";
931 static signed char opt_is_inside_work_tree      = -1; /* set to TRUE or FALSE */
932 static char opt_editor[SIZEOF_STR]      = "";
933 static FILE *opt_tty                    = NULL;
935 #define is_initial_commit()     (!*opt_head_rev)
936 #define is_head_commit(rev)     (!strcmp((rev), "HEAD") || !strcmp(opt_head_rev, (rev)))
939 /*
940  * Line-oriented content detection.
941  */
943 #define LINE_INFO \
944 LINE(DIFF_HEADER,  "diff --git ",       COLOR_YELLOW,   COLOR_DEFAULT,  0), \
945 LINE(DIFF_CHUNK,   "@@",                COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
946 LINE(DIFF_ADD,     "+",                 COLOR_GREEN,    COLOR_DEFAULT,  0), \
947 LINE(DIFF_DEL,     "-",                 COLOR_RED,      COLOR_DEFAULT,  0), \
948 LINE(DIFF_INDEX,        "index ",         COLOR_BLUE,   COLOR_DEFAULT,  0), \
949 LINE(DIFF_OLDMODE,      "old file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
950 LINE(DIFF_NEWMODE,      "new file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
951 LINE(DIFF_COPY_FROM,    "copy from",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
952 LINE(DIFF_COPY_TO,      "copy to",        COLOR_YELLOW, COLOR_DEFAULT,  0), \
953 LINE(DIFF_RENAME_FROM,  "rename from",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
954 LINE(DIFF_RENAME_TO,    "rename to",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
955 LINE(DIFF_SIMILARITY,   "similarity ",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
956 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
957 LINE(DIFF_TREE,         "diff-tree ",     COLOR_BLUE,   COLOR_DEFAULT,  0), \
958 LINE(PP_AUTHOR,    "Author: ",          COLOR_CYAN,     COLOR_DEFAULT,  0), \
959 LINE(PP_COMMIT,    "Commit: ",          COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
960 LINE(PP_MERGE,     "Merge: ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
961 LINE(PP_DATE,      "Date:   ",          COLOR_YELLOW,   COLOR_DEFAULT,  0), \
962 LINE(PP_ADATE,     "AuthorDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
963 LINE(PP_CDATE,     "CommitDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
964 LINE(PP_REFS,      "Refs: ",            COLOR_RED,      COLOR_DEFAULT,  0), \
965 LINE(COMMIT,       "commit ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
966 LINE(PARENT,       "parent ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
967 LINE(TREE,         "tree ",             COLOR_BLUE,     COLOR_DEFAULT,  0), \
968 LINE(AUTHOR,       "author ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
969 LINE(COMMITTER,    "committer ",        COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
970 LINE(SIGNOFF,      "    Signed-off-by", COLOR_YELLOW,   COLOR_DEFAULT,  0), \
971 LINE(ACKED,        "    Acked-by",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
972 LINE(DEFAULT,      "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
973 LINE(CURSOR,       "",                  COLOR_WHITE,    COLOR_GREEN,    A_BOLD), \
974 LINE(STATUS,       "",                  COLOR_GREEN,    COLOR_DEFAULT,  0), \
975 LINE(DELIMITER,    "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
976 LINE(DATE,         "",                  COLOR_BLUE,     COLOR_DEFAULT,  0), \
977 LINE(MODE,         "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
978 LINE(LINE_NUMBER,  "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
979 LINE(TITLE_BLUR,   "",                  COLOR_WHITE,    COLOR_BLUE,     0), \
980 LINE(TITLE_FOCUS,  "",                  COLOR_WHITE,    COLOR_BLUE,     A_BOLD), \
981 LINE(MAIN_COMMIT,  "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
982 LINE(MAIN_TAG,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  A_BOLD), \
983 LINE(MAIN_LOCAL_TAG,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
984 LINE(MAIN_REMOTE,  "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
985 LINE(MAIN_TRACKED, "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_BOLD), \
986 LINE(MAIN_REF,     "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
987 LINE(MAIN_HEAD,    "",                  COLOR_CYAN,     COLOR_DEFAULT,  A_BOLD), \
988 LINE(MAIN_REVGRAPH,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
989 LINE(TREE_HEAD,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_BOLD), \
990 LINE(TREE_DIR,     "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_NORMAL), \
991 LINE(TREE_FILE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
992 LINE(STAT_HEAD,    "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
993 LINE(STAT_SECTION, "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
994 LINE(STAT_NONE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
995 LINE(STAT_STAGED,  "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
996 LINE(STAT_UNSTAGED,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
997 LINE(STAT_UNTRACKED,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
998 LINE(HELP_KEYMAP,  "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
999 LINE(HELP_GROUP,   "",                  COLOR_BLUE,     COLOR_DEFAULT,  0), \
1000 LINE(BLAME_ID,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0)
1002 enum line_type {
1003 #define LINE(type, line, fg, bg, attr) \
1004         LINE_##type
1005         LINE_INFO,
1006         LINE_NONE
1007 #undef  LINE
1008 };
1010 struct line_info {
1011         const char *name;       /* Option name. */
1012         int namelen;            /* Size of option name. */
1013         const char *line;       /* The start of line to match. */
1014         int linelen;            /* Size of string to match. */
1015         int fg, bg, attr;       /* Color and text attributes for the lines. */
1016 };
1018 static struct line_info line_info[] = {
1019 #define LINE(type, line, fg, bg, attr) \
1020         { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
1021         LINE_INFO
1022 #undef  LINE
1023 };
1025 static enum line_type
1026 get_line_type(const char *line)
1028         int linelen = strlen(line);
1029         enum line_type type;
1031         for (type = 0; type < ARRAY_SIZE(line_info); type++)
1032                 /* Case insensitive search matches Signed-off-by lines better. */
1033                 if (linelen >= line_info[type].linelen &&
1034                     !strncasecmp(line_info[type].line, line, line_info[type].linelen))
1035                         return type;
1037         return LINE_DEFAULT;
1040 static inline int
1041 get_line_attr(enum line_type type)
1043         assert(type < ARRAY_SIZE(line_info));
1044         return COLOR_PAIR(type) | line_info[type].attr;
1047 static struct line_info *
1048 get_line_info(const char *name)
1050         size_t namelen = strlen(name);
1051         enum line_type type;
1053         for (type = 0; type < ARRAY_SIZE(line_info); type++)
1054                 if (namelen == line_info[type].namelen &&
1055                     !string_enum_compare(line_info[type].name, name, namelen))
1056                         return &line_info[type];
1058         return NULL;
1061 static void
1062 init_colors(void)
1064         int default_bg = line_info[LINE_DEFAULT].bg;
1065         int default_fg = line_info[LINE_DEFAULT].fg;
1066         enum line_type type;
1068         start_color();
1070         if (assume_default_colors(default_fg, default_bg) == ERR) {
1071                 default_bg = COLOR_BLACK;
1072                 default_fg = COLOR_WHITE;
1073         }
1075         for (type = 0; type < ARRAY_SIZE(line_info); type++) {
1076                 struct line_info *info = &line_info[type];
1077                 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
1078                 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
1080                 init_pair(type, fg, bg);
1081         }
1084 struct line {
1085         enum line_type type;
1087         /* State flags */
1088         unsigned int selected:1;
1089         unsigned int dirty:1;
1090         unsigned int cleareol:1;
1091         unsigned int other:16;
1093         void *data;             /* User data */
1094 };
1097 /*
1098  * Keys
1099  */
1101 struct keybinding {
1102         int alias;
1103         enum request request;
1104 };
1106 static const struct keybinding default_keybindings[] = {
1107         /* View switching */
1108         { 'm',          REQ_VIEW_MAIN },
1109         { 'd',          REQ_VIEW_DIFF },
1110         { 'l',          REQ_VIEW_LOG },
1111         { 't',          REQ_VIEW_TREE },
1112         { 'f',          REQ_VIEW_BLOB },
1113         { 'B',          REQ_VIEW_BLAME },
1114         { 'H',          REQ_VIEW_BRANCH },
1115         { 'p',          REQ_VIEW_PAGER },
1116         { 'h',          REQ_VIEW_HELP },
1117         { 'S',          REQ_VIEW_STATUS },
1118         { 'c',          REQ_VIEW_STAGE },
1120         /* View manipulation */
1121         { 'q',          REQ_VIEW_CLOSE },
1122         { KEY_TAB,      REQ_VIEW_NEXT },
1123         { KEY_RETURN,   REQ_ENTER },
1124         { KEY_UP,       REQ_PREVIOUS },
1125         { KEY_DOWN,     REQ_NEXT },
1126         { 'R',          REQ_REFRESH },
1127         { KEY_F(5),     REQ_REFRESH },
1128         { 'O',          REQ_MAXIMIZE },
1130         /* Cursor navigation */
1131         { 'k',          REQ_MOVE_UP },
1132         { 'j',          REQ_MOVE_DOWN },
1133         { KEY_HOME,     REQ_MOVE_FIRST_LINE },
1134         { KEY_END,      REQ_MOVE_LAST_LINE },
1135         { KEY_NPAGE,    REQ_MOVE_PAGE_DOWN },
1136         { ' ',          REQ_MOVE_PAGE_DOWN },
1137         { KEY_PPAGE,    REQ_MOVE_PAGE_UP },
1138         { 'b',          REQ_MOVE_PAGE_UP },
1139         { '-',          REQ_MOVE_PAGE_UP },
1141         /* Scrolling */
1142         { KEY_LEFT,     REQ_SCROLL_LEFT },
1143         { KEY_RIGHT,    REQ_SCROLL_RIGHT },
1144         { KEY_IC,       REQ_SCROLL_LINE_UP },
1145         { KEY_DC,       REQ_SCROLL_LINE_DOWN },
1146         { 'w',          REQ_SCROLL_PAGE_UP },
1147         { 's',          REQ_SCROLL_PAGE_DOWN },
1149         /* Searching */
1150         { '/',          REQ_SEARCH },
1151         { '?',          REQ_SEARCH_BACK },
1152         { 'n',          REQ_FIND_NEXT },
1153         { 'N',          REQ_FIND_PREV },
1155         /* Misc */
1156         { 'Q',          REQ_QUIT },
1157         { 'z',          REQ_STOP_LOADING },
1158         { 'v',          REQ_SHOW_VERSION },
1159         { 'r',          REQ_SCREEN_REDRAW },
1160         { 'o',          REQ_OPTIONS },
1161         { '.',          REQ_TOGGLE_LINENO },
1162         { 'D',          REQ_TOGGLE_DATE },
1163         { 'T',          REQ_TOGGLE_DATE_SHORT },
1164         { 'A',          REQ_TOGGLE_AUTHOR },
1165         { 'g',          REQ_TOGGLE_REV_GRAPH },
1166         { 'F',          REQ_TOGGLE_REFS },
1167         { 'I',          REQ_TOGGLE_SORT_ORDER },
1168         { 'i',          REQ_TOGGLE_SORT_FIELD },
1169         { ':',          REQ_PROMPT },
1170         { 'u',          REQ_STATUS_UPDATE },
1171         { '!',          REQ_STATUS_REVERT },
1172         { 'M',          REQ_STATUS_MERGE },
1173         { '@',          REQ_STAGE_NEXT },
1174         { ',',          REQ_PARENT },
1175         { 'e',          REQ_EDIT },
1176 };
1178 #define KEYMAP_INFO \
1179         KEYMAP_(GENERIC), \
1180         KEYMAP_(MAIN), \
1181         KEYMAP_(DIFF), \
1182         KEYMAP_(LOG), \
1183         KEYMAP_(TREE), \
1184         KEYMAP_(BLOB), \
1185         KEYMAP_(BLAME), \
1186         KEYMAP_(BRANCH), \
1187         KEYMAP_(PAGER), \
1188         KEYMAP_(HELP), \
1189         KEYMAP_(STATUS), \
1190         KEYMAP_(STAGE)
1192 enum keymap {
1193 #define KEYMAP_(name) KEYMAP_##name
1194         KEYMAP_INFO
1195 #undef  KEYMAP_
1196 };
1198 static const struct enum_map keymap_table[] = {
1199 #define KEYMAP_(name) ENUM_MAP(#name, KEYMAP_##name)
1200         KEYMAP_INFO
1201 #undef  KEYMAP_
1202 };
1204 #define set_keymap(map, name) map_enum(map, keymap_table, name)
1206 struct keybinding_table {
1207         struct keybinding *data;
1208         size_t size;
1209 };
1211 static struct keybinding_table keybindings[ARRAY_SIZE(keymap_table)];
1213 static void
1214 add_keybinding(enum keymap keymap, enum request request, int key)
1216         struct keybinding_table *table = &keybindings[keymap];
1218         table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data));
1219         if (!table->data)
1220                 die("Failed to allocate keybinding");
1221         table->data[table->size].alias = key;
1222         table->data[table->size++].request = request;
1225 /* Looks for a key binding first in the given map, then in the generic map, and
1226  * lastly in the default keybindings. */
1227 static enum request
1228 get_keybinding(enum keymap keymap, int key)
1230         size_t i;
1232         for (i = 0; i < keybindings[keymap].size; i++)
1233                 if (keybindings[keymap].data[i].alias == key)
1234                         return keybindings[keymap].data[i].request;
1236         for (i = 0; i < keybindings[KEYMAP_GENERIC].size; i++)
1237                 if (keybindings[KEYMAP_GENERIC].data[i].alias == key)
1238                         return keybindings[KEYMAP_GENERIC].data[i].request;
1240         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1241                 if (default_keybindings[i].alias == key)
1242                         return default_keybindings[i].request;
1244         return (enum request) key;
1248 struct key {
1249         const char *name;
1250         int value;
1251 };
1253 static const struct key key_table[] = {
1254         { "Enter",      KEY_RETURN },
1255         { "Space",      ' ' },
1256         { "Backspace",  KEY_BACKSPACE },
1257         { "Tab",        KEY_TAB },
1258         { "Escape",     KEY_ESC },
1259         { "Left",       KEY_LEFT },
1260         { "Right",      KEY_RIGHT },
1261         { "Up",         KEY_UP },
1262         { "Down",       KEY_DOWN },
1263         { "Insert",     KEY_IC },
1264         { "Delete",     KEY_DC },
1265         { "Hash",       '#' },
1266         { "Home",       KEY_HOME },
1267         { "End",        KEY_END },
1268         { "PageUp",     KEY_PPAGE },
1269         { "PageDown",   KEY_NPAGE },
1270         { "F1",         KEY_F(1) },
1271         { "F2",         KEY_F(2) },
1272         { "F3",         KEY_F(3) },
1273         { "F4",         KEY_F(4) },
1274         { "F5",         KEY_F(5) },
1275         { "F6",         KEY_F(6) },
1276         { "F7",         KEY_F(7) },
1277         { "F8",         KEY_F(8) },
1278         { "F9",         KEY_F(9) },
1279         { "F10",        KEY_F(10) },
1280         { "F11",        KEY_F(11) },
1281         { "F12",        KEY_F(12) },
1282 };
1284 static int
1285 get_key_value(const char *name)
1287         int i;
1289         for (i = 0; i < ARRAY_SIZE(key_table); i++)
1290                 if (!strcasecmp(key_table[i].name, name))
1291                         return key_table[i].value;
1293         if (strlen(name) == 1 && isprint(*name))
1294                 return (int) *name;
1296         return ERR;
1299 static const char *
1300 get_key_name(int key_value)
1302         static char key_char[] = "'X'";
1303         const char *seq = NULL;
1304         int key;
1306         for (key = 0; key < ARRAY_SIZE(key_table); key++)
1307                 if (key_table[key].value == key_value)
1308                         seq = key_table[key].name;
1310         if (seq == NULL &&
1311             key_value < 127 &&
1312             isprint(key_value)) {
1313                 key_char[1] = (char) key_value;
1314                 seq = key_char;
1315         }
1317         return seq ? seq : "(no key)";
1320 static bool
1321 append_key(char *buf, size_t *pos, const struct keybinding *keybinding)
1323         const char *sep = *pos > 0 ? ", " : "";
1324         const char *keyname = get_key_name(keybinding->alias);
1326         return string_nformat(buf, BUFSIZ, pos, "%s%s", sep, keyname);
1329 static bool
1330 append_keymap_request_keys(char *buf, size_t *pos, enum request request,
1331                            enum keymap keymap, bool all)
1333         int i;
1335         for (i = 0; i < keybindings[keymap].size; i++) {
1336                 if (keybindings[keymap].data[i].request == request) {
1337                         if (!append_key(buf, pos, &keybindings[keymap].data[i]))
1338                                 return FALSE;
1339                         if (!all)
1340                                 break;
1341                 }
1342         }
1344         return TRUE;
1347 #define get_key(keymap, request) get_keys(keymap, request, FALSE)
1349 static const char *
1350 get_keys(enum keymap keymap, enum request request, bool all)
1352         static char buf[BUFSIZ];
1353         size_t pos = 0;
1354         int i;
1356         buf[pos] = 0;
1358         if (!append_keymap_request_keys(buf, &pos, request, keymap, all))
1359                 return "Too many keybindings!";
1360         if (pos > 0 && !all)
1361                 return buf;
1363         if (keymap != KEYMAP_GENERIC) {
1364                 /* Only the generic keymap includes the default keybindings when
1365                  * listing all keys. */
1366                 if (all)
1367                         return buf;
1369                 if (!append_keymap_request_keys(buf, &pos, request, KEYMAP_GENERIC, all))
1370                         return "Too many keybindings!";
1371                 if (pos)
1372                         return buf;
1373         }
1375         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1376                 if (default_keybindings[i].request == request) {
1377                         if (!append_key(buf, &pos, &default_keybindings[i]))
1378                                 return "Too many keybindings!";
1379                         if (!all)
1380                                 return buf;
1381                 }
1382         }
1384         return buf;
1387 struct run_request {
1388         enum keymap keymap;
1389         int key;
1390         const char *argv[SIZEOF_ARG];
1391 };
1393 static struct run_request *run_request;
1394 static size_t run_requests;
1396 DEFINE_ALLOCATOR(realloc_run_requests, struct run_request, 8)
1398 static enum request
1399 add_run_request(enum keymap keymap, int key, int argc, const char **argv)
1401         struct run_request *req;
1403         if (argc >= ARRAY_SIZE(req->argv) - 1)
1404                 return REQ_NONE;
1406         if (!realloc_run_requests(&run_request, run_requests, 1))
1407                 return REQ_NONE;
1409         req = &run_request[run_requests];
1410         req->keymap = keymap;
1411         req->key = key;
1412         req->argv[0] = NULL;
1414         if (!format_argv(req->argv, argv, FORMAT_NONE))
1415                 return REQ_NONE;
1417         return REQ_NONE + ++run_requests;
1420 static struct run_request *
1421 get_run_request(enum request request)
1423         if (request <= REQ_NONE)
1424                 return NULL;
1425         return &run_request[request - REQ_NONE - 1];
1428 static void
1429 add_builtin_run_requests(void)
1431         const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1432         const char *commit[] = { "git", "commit", NULL };
1433         const char *gc[] = { "git", "gc", NULL };
1434         struct {
1435                 enum keymap keymap;
1436                 int key;
1437                 int argc;
1438                 const char **argv;
1439         } reqs[] = {
1440                 { KEYMAP_MAIN,    'C', ARRAY_SIZE(cherry_pick) - 1, cherry_pick },
1441                 { KEYMAP_STATUS,  'C', ARRAY_SIZE(commit) - 1, commit },
1442                 { KEYMAP_GENERIC, 'G', ARRAY_SIZE(gc) - 1, gc },
1443         };
1444         int i;
1446         for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1447                 enum request req;
1449                 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argc, reqs[i].argv);
1450                 if (req != REQ_NONE)
1451                         add_keybinding(reqs[i].keymap, req, reqs[i].key);
1452         }
1455 /*
1456  * User config file handling.
1457  */
1459 static int   config_lineno;
1460 static bool  config_errors;
1461 static const char *config_msg;
1463 static const struct enum_map color_map[] = {
1464 #define COLOR_MAP(name) ENUM_MAP(#name, COLOR_##name)
1465         COLOR_MAP(DEFAULT),
1466         COLOR_MAP(BLACK),
1467         COLOR_MAP(BLUE),
1468         COLOR_MAP(CYAN),
1469         COLOR_MAP(GREEN),
1470         COLOR_MAP(MAGENTA),
1471         COLOR_MAP(RED),
1472         COLOR_MAP(WHITE),
1473         COLOR_MAP(YELLOW),
1474 };
1476 static const struct enum_map attr_map[] = {
1477 #define ATTR_MAP(name) ENUM_MAP(#name, A_##name)
1478         ATTR_MAP(NORMAL),
1479         ATTR_MAP(BLINK),
1480         ATTR_MAP(BOLD),
1481         ATTR_MAP(DIM),
1482         ATTR_MAP(REVERSE),
1483         ATTR_MAP(STANDOUT),
1484         ATTR_MAP(UNDERLINE),
1485 };
1487 #define set_attribute(attr, name)       map_enum(attr, attr_map, name)
1489 static int parse_step(double *opt, const char *arg)
1491         *opt = atoi(arg);
1492         if (!strchr(arg, '%'))
1493                 return OK;
1495         /* "Shift down" so 100% and 1 does not conflict. */
1496         *opt = (*opt - 1) / 100;
1497         if (*opt >= 1.0) {
1498                 *opt = 0.99;
1499                 config_msg = "Step value larger than 100%";
1500                 return ERR;
1501         }
1502         if (*opt < 0.0) {
1503                 *opt = 1;
1504                 config_msg = "Invalid step value";
1505                 return ERR;
1506         }
1507         return OK;
1510 static int
1511 parse_int(int *opt, const char *arg, int min, int max)
1513         int value = atoi(arg);
1515         if (min <= value && value <= max) {
1516                 *opt = value;
1517                 return OK;
1518         }
1520         config_msg = "Integer value out of bound";
1521         return ERR;
1524 static bool
1525 set_color(int *color, const char *name)
1527         if (map_enum(color, color_map, name))
1528                 return TRUE;
1529         if (!prefixcmp(name, "color"))
1530                 return parse_int(color, name + 5, 0, 255) == OK;
1531         return FALSE;
1534 /* Wants: object fgcolor bgcolor [attribute] */
1535 static int
1536 option_color_command(int argc, const char *argv[])
1538         struct line_info *info;
1540         if (argc < 3) {
1541                 config_msg = "Wrong number of arguments given to color command";
1542                 return ERR;
1543         }
1545         info = get_line_info(argv[0]);
1546         if (!info) {
1547                 static const struct enum_map obsolete[] = {
1548                         ENUM_MAP("main-delim",  LINE_DELIMITER),
1549                         ENUM_MAP("main-date",   LINE_DATE),
1550                         ENUM_MAP("main-author", LINE_AUTHOR),
1551                 };
1552                 int index;
1554                 if (!map_enum(&index, obsolete, argv[0])) {
1555                         config_msg = "Unknown color name";
1556                         return ERR;
1557                 }
1558                 info = &line_info[index];
1559         }
1561         if (!set_color(&info->fg, argv[1]) ||
1562             !set_color(&info->bg, argv[2])) {
1563                 config_msg = "Unknown color";
1564                 return ERR;
1565         }
1567         info->attr = 0;
1568         while (argc-- > 3) {
1569                 int attr;
1571                 if (!set_attribute(&attr, argv[argc])) {
1572                         config_msg = "Unknown attribute";
1573                         return ERR;
1574                 }
1575                 info->attr |= attr;
1576         }
1578         return OK;
1581 static int parse_bool(bool *opt, const char *arg)
1583         *opt = (!strcmp(arg, "1") || !strcmp(arg, "true") || !strcmp(arg, "yes"))
1584                 ? TRUE : FALSE;
1585         return OK;
1588 static int
1589 parse_string(char *opt, const char *arg, size_t optsize)
1591         int arglen = strlen(arg);
1593         switch (arg[0]) {
1594         case '\"':
1595         case '\'':
1596                 if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1597                         config_msg = "Unmatched quotation";
1598                         return ERR;
1599                 }
1600                 arg += 1; arglen -= 2;
1601         default:
1602                 string_ncopy_do(opt, optsize, arg, arglen);
1603                 return OK;
1604         }
1607 /* Wants: name = value */
1608 static int
1609 option_set_command(int argc, const char *argv[])
1611         if (argc != 3) {
1612                 config_msg = "Wrong number of arguments given to set command";
1613                 return ERR;
1614         }
1616         if (strcmp(argv[1], "=")) {
1617                 config_msg = "No value assigned";
1618                 return ERR;
1619         }
1621         if (!strcmp(argv[0], "show-author"))
1622                 return parse_bool(&opt_author, argv[2]);
1624         if (!strcmp(argv[0], "show-date"))
1625                 return parse_bool(&opt_date, argv[2]);
1627         if (!strcmp(argv[0], "date-short"))
1628                 return parse_bool(&opt_date_short, argv[2]);
1630         if (!strcmp(argv[0], "show-rev-graph"))
1631                 return parse_bool(&opt_rev_graph, argv[2]);
1633         if (!strcmp(argv[0], "show-refs"))
1634                 return parse_bool(&opt_show_refs, argv[2]);
1636         if (!strcmp(argv[0], "show-line-numbers"))
1637                 return parse_bool(&opt_line_number, argv[2]);
1639         if (!strcmp(argv[0], "line-graphics"))
1640                 return parse_bool(&opt_line_graphics, argv[2]);
1642         if (!strcmp(argv[0], "line-number-interval"))
1643                 return parse_int(&opt_num_interval, argv[2], 1, 1024);
1645         if (!strcmp(argv[0], "author-width"))
1646                 return parse_int(&opt_author_cols, argv[2], 0, 1024);
1648         if (!strcmp(argv[0], "horizontal-scroll"))
1649                 return parse_step(&opt_hscroll, argv[2]);
1651         if (!strcmp(argv[0], "split-view-height"))
1652                 return parse_step(&opt_scale_split_view, argv[2]);
1654         if (!strcmp(argv[0], "tab-size"))
1655                 return parse_int(&opt_tab_size, argv[2], 1, 1024);
1657         if (!strcmp(argv[0], "commit-encoding"))
1658                 return parse_string(opt_encoding, argv[2], sizeof(opt_encoding));
1660         config_msg = "Unknown variable name";
1661         return ERR;
1664 /* Wants: mode request key */
1665 static int
1666 option_bind_command(int argc, const char *argv[])
1668         enum request request;
1669         int keymap = -1;
1670         int key;
1672         if (argc < 3) {
1673                 config_msg = "Wrong number of arguments given to bind command";
1674                 return ERR;
1675         }
1677         if (set_keymap(&keymap, argv[0]) == ERR) {
1678                 config_msg = "Unknown key map";
1679                 return ERR;
1680         }
1682         key = get_key_value(argv[1]);
1683         if (key == ERR) {
1684                 config_msg = "Unknown key";
1685                 return ERR;
1686         }
1688         request = get_request(argv[2]);
1689         if (request == REQ_NONE) {
1690                 static const struct enum_map obsolete[] = {
1691                         ENUM_MAP("cherry-pick",         REQ_NONE),
1692                         ENUM_MAP("screen-resize",       REQ_NONE),
1693                         ENUM_MAP("tree-parent",         REQ_PARENT),
1694                 };
1695                 int alias;
1697                 if (map_enum(&alias, obsolete, argv[2])) {
1698                         if (alias != REQ_NONE)
1699                                 add_keybinding(keymap, alias, key);
1700                         config_msg = "Obsolete request name";
1701                         return ERR;
1702                 }
1703         }
1704         if (request == REQ_NONE && *argv[2]++ == '!')
1705                 request = add_run_request(keymap, key, argc - 2, argv + 2);
1706         if (request == REQ_NONE) {
1707                 config_msg = "Unknown request name";
1708                 return ERR;
1709         }
1711         add_keybinding(keymap, request, key);
1713         return OK;
1716 static int
1717 set_option(const char *opt, char *value)
1719         const char *argv[SIZEOF_ARG];
1720         int argc = 0;
1722         if (!argv_from_string(argv, &argc, value)) {
1723                 config_msg = "Too many option arguments";
1724                 return ERR;
1725         }
1727         if (!strcmp(opt, "color"))
1728                 return option_color_command(argc, argv);
1730         if (!strcmp(opt, "set"))
1731                 return option_set_command(argc, argv);
1733         if (!strcmp(opt, "bind"))
1734                 return option_bind_command(argc, argv);
1736         config_msg = "Unknown option command";
1737         return ERR;
1740 static int
1741 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
1743         int status = OK;
1745         config_lineno++;
1746         config_msg = "Internal error";
1748         /* Check for comment markers, since read_properties() will
1749          * only ensure opt and value are split at first " \t". */
1750         optlen = strcspn(opt, "#");
1751         if (optlen == 0)
1752                 return OK;
1754         if (opt[optlen] != 0) {
1755                 config_msg = "No option value";
1756                 status = ERR;
1758         }  else {
1759                 /* Look for comment endings in the value. */
1760                 size_t len = strcspn(value, "#");
1762                 if (len < valuelen) {
1763                         valuelen = len;
1764                         value[valuelen] = 0;
1765                 }
1767                 status = set_option(opt, value);
1768         }
1770         if (status == ERR) {
1771                 warn("Error on line %d, near '%.*s': %s",
1772                      config_lineno, (int) optlen, opt, config_msg);
1773                 config_errors = TRUE;
1774         }
1776         /* Always keep going if errors are encountered. */
1777         return OK;
1780 static void
1781 load_option_file(const char *path)
1783         struct io io = {};
1785         /* It's OK that the file doesn't exist. */
1786         if (!io_open(&io, path))
1787                 return;
1789         config_lineno = 0;
1790         config_errors = FALSE;
1792         if (io_load(&io, " \t", read_option) == ERR ||
1793             config_errors == TRUE)
1794                 warn("Errors while loading %s.", path);
1797 static int
1798 load_options(void)
1800         const char *home = getenv("HOME");
1801         const char *tigrc_user = getenv("TIGRC_USER");
1802         const char *tigrc_system = getenv("TIGRC_SYSTEM");
1803         char buf[SIZEOF_STR];
1805         add_builtin_run_requests();
1807         if (!tigrc_system)
1808                 tigrc_system = SYSCONFDIR "/tigrc";
1809         load_option_file(tigrc_system);
1811         if (!tigrc_user) {
1812                 if (!home || !string_format(buf, "%s/.tigrc", home))
1813                         return ERR;
1814                 tigrc_user = buf;
1815         }
1816         load_option_file(tigrc_user);
1818         return OK;
1822 /*
1823  * The viewer
1824  */
1826 struct view;
1827 struct view_ops;
1829 /* The display array of active views and the index of the current view. */
1830 static struct view *display[2];
1831 static unsigned int current_view;
1833 #define foreach_displayed_view(view, i) \
1834         for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1836 #define displayed_views()       (display[1] != NULL ? 2 : 1)
1838 /* Current head and commit ID */
1839 static char ref_blob[SIZEOF_REF]        = "";
1840 static char ref_commit[SIZEOF_REF]      = "HEAD";
1841 static char ref_head[SIZEOF_REF]        = "HEAD";
1843 struct view {
1844         const char *name;       /* View name */
1845         const char *cmd_env;    /* Command line set via environment */
1846         const char *id;         /* Points to either of ref_{head,commit,blob} */
1848         struct view_ops *ops;   /* View operations */
1850         enum keymap keymap;     /* What keymap does this view have */
1851         bool git_dir;           /* Whether the view requires a git directory. */
1853         char ref[SIZEOF_REF];   /* Hovered commit reference */
1854         char vid[SIZEOF_REF];   /* View ID. Set to id member when updating. */
1856         int height, width;      /* The width and height of the main window */
1857         WINDOW *win;            /* The main window */
1858         WINDOW *title;          /* The title window living below the main window */
1860         /* Navigation */
1861         unsigned long offset;   /* Offset of the window top */
1862         unsigned long yoffset;  /* Offset from the window side. */
1863         unsigned long lineno;   /* Current line number */
1864         unsigned long p_offset; /* Previous offset of the window top */
1865         unsigned long p_yoffset;/* Previous offset from the window side */
1866         unsigned long p_lineno; /* Previous current line number */
1867         bool p_restore;         /* Should the previous position be restored. */
1869         /* Searching */
1870         char grep[SIZEOF_STR];  /* Search string */
1871         regex_t *regex;         /* Pre-compiled regexp */
1873         /* If non-NULL, points to the view that opened this view. If this view
1874          * is closed tig will switch back to the parent view. */
1875         struct view *parent;
1877         /* Buffering */
1878         size_t lines;           /* Total number of lines */
1879         struct line *line;      /* Line index */
1880         unsigned int digits;    /* Number of digits in the lines member. */
1882         /* Drawing */
1883         struct line *curline;   /* Line currently being drawn. */
1884         enum line_type curtype; /* Attribute currently used for drawing. */
1885         unsigned long col;      /* Column when drawing. */
1886         bool has_scrolled;      /* View was scrolled. */
1888         /* Loading */
1889         struct io io;
1890         struct io *pipe;
1891         time_t start_time;
1892         time_t update_secs;
1893 };
1895 struct view_ops {
1896         /* What type of content being displayed. Used in the title bar. */
1897         const char *type;
1898         /* Default command arguments. */
1899         const char **argv;
1900         /* Open and reads in all view content. */
1901         bool (*open)(struct view *view);
1902         /* Read one line; updates view->line. */
1903         bool (*read)(struct view *view, char *data);
1904         /* Draw one line; @lineno must be < view->height. */
1905         bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
1906         /* Depending on view handle a special requests. */
1907         enum request (*request)(struct view *view, enum request request, struct line *line);
1908         /* Search for regexp in a line. */
1909         bool (*grep)(struct view *view, struct line *line);
1910         /* Select line */
1911         void (*select)(struct view *view, struct line *line);
1912 };
1914 static struct view_ops blame_ops;
1915 static struct view_ops blob_ops;
1916 static struct view_ops diff_ops;
1917 static struct view_ops help_ops;
1918 static struct view_ops log_ops;
1919 static struct view_ops main_ops;
1920 static struct view_ops pager_ops;
1921 static struct view_ops stage_ops;
1922 static struct view_ops status_ops;
1923 static struct view_ops tree_ops;
1924 static struct view_ops branch_ops;
1926 #define VIEW_STR(name, env, ref, ops, map, git) \
1927         { name, #env, ref, ops, map, git }
1929 #define VIEW_(id, name, ops, git, ref) \
1930         VIEW_STR(name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
1933 static struct view views[] = {
1934         VIEW_(MAIN,   "main",   &main_ops,   TRUE,  ref_head),
1935         VIEW_(DIFF,   "diff",   &diff_ops,   TRUE,  ref_commit),
1936         VIEW_(LOG,    "log",    &log_ops,    TRUE,  ref_head),
1937         VIEW_(TREE,   "tree",   &tree_ops,   TRUE,  ref_commit),
1938         VIEW_(BLOB,   "blob",   &blob_ops,   TRUE,  ref_blob),
1939         VIEW_(BLAME,  "blame",  &blame_ops,  TRUE,  ref_commit),
1940         VIEW_(BRANCH, "branch", &branch_ops, TRUE,  ref_head),
1941         VIEW_(HELP,   "help",   &help_ops,   FALSE, ""),
1942         VIEW_(PAGER,  "pager",  &pager_ops,  FALSE, "stdin"),
1943         VIEW_(STATUS, "status", &status_ops, TRUE,  ""),
1944         VIEW_(STAGE,  "stage",  &stage_ops,  TRUE,  ""),
1945 };
1947 #define VIEW(req)       (&views[(req) - REQ_OFFSET - 1])
1948 #define VIEW_REQ(view)  ((view) - views + REQ_OFFSET + 1)
1950 #define foreach_view(view, i) \
1951         for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
1953 #define view_is_displayed(view) \
1954         (view == display[0] || view == display[1])
1957 enum line_graphic {
1958         LINE_GRAPHIC_VLINE
1959 };
1961 static chtype line_graphics[] = {
1962         /* LINE_GRAPHIC_VLINE: */ '|'
1963 };
1965 static inline void
1966 set_view_attr(struct view *view, enum line_type type)
1968         if (!view->curline->selected && view->curtype != type) {
1969                 wattrset(view->win, get_line_attr(type));
1970                 wchgat(view->win, -1, 0, type, NULL);
1971                 view->curtype = type;
1972         }
1975 static int
1976 draw_chars(struct view *view, enum line_type type, const char *string,
1977            int max_len, bool use_tilde)
1979         int len = 0;
1980         int col = 0;
1981         int trimmed = FALSE;
1982         size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
1984         if (max_len <= 0)
1985                 return 0;
1987         if (opt_utf8) {
1988                 len = utf8_length(&string, skip, &col, max_len, &trimmed, use_tilde);
1989         } else {
1990                 col = len = strlen(string);
1991                 if (len > max_len) {
1992                         if (use_tilde) {
1993                                 max_len -= 1;
1994                         }
1995                         col = len = max_len;
1996                         trimmed = TRUE;
1997                 }
1998         }
2000         set_view_attr(view, type);
2001         if (len > 0)
2002                 waddnstr(view->win, string, len);
2003         if (trimmed && use_tilde) {
2004                 set_view_attr(view, LINE_DELIMITER);
2005                 waddch(view->win, '~');
2006                 col++;
2007         }
2009         return col;
2012 static int
2013 draw_space(struct view *view, enum line_type type, int max, int spaces)
2015         static char space[] = "                    ";
2016         int col = 0;
2018         spaces = MIN(max, spaces);
2020         while (spaces > 0) {
2021                 int len = MIN(spaces, sizeof(space) - 1);
2023                 col += draw_chars(view, type, space, len, FALSE);
2024                 spaces -= len;
2025         }
2027         return col;
2030 static bool
2031 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
2033         view->col += draw_chars(view, type, string, view->width + view->yoffset - view->col, trim);
2034         return view->width + view->yoffset <= view->col;
2037 static bool
2038 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
2040         size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2041         int max = view->width + view->yoffset - view->col;
2042         int i;
2044         if (max < size)
2045                 size = max;
2047         set_view_attr(view, type);
2048         /* Using waddch() instead of waddnstr() ensures that
2049          * they'll be rendered correctly for the cursor line. */
2050         for (i = skip; i < size; i++)
2051                 waddch(view->win, graphic[i]);
2053         view->col += size;
2054         if (size < max && skip <= size)
2055                 waddch(view->win, ' ');
2056         view->col++;
2058         return view->width + view->yoffset <= view->col;
2061 static bool
2062 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
2064         int max = MIN(view->width + view->yoffset - view->col, len);
2065         int col;
2067         if (text)
2068                 col = draw_chars(view, type, text, max - 1, trim);
2069         else
2070                 col = draw_space(view, type, max - 1, max - 1);
2072         view->col += col;
2073         view->col += draw_space(view, LINE_DEFAULT, max - col, max - col);
2074         return view->width + view->yoffset <= view->col;
2077 static bool
2078 draw_date(struct view *view, time_t *time)
2080         const char *date = mkdate(time);
2081         int cols = opt_date_short ? DATE_SHORT_COLS : DATE_COLS;
2083         return draw_field(view, LINE_DATE, date, cols, FALSE);
2086 static bool
2087 draw_author(struct view *view, const char *author)
2089         bool trim = opt_author_cols == 0 || opt_author_cols > 5 || !author;
2091         if (!trim) {
2092                 static char initials[10];
2093                 size_t pos;
2095 #define is_initial_sep(c) (isspace(c) || ispunct(c) || (c) == '@')
2097                 memset(initials, 0, sizeof(initials));
2098                 for (pos = 0; *author && pos < opt_author_cols - 1; author++, pos++) {
2099                         while (is_initial_sep(*author))
2100                                 author++;
2101                         strncpy(&initials[pos], author, sizeof(initials) - 1 - pos);
2102                         while (*author && !is_initial_sep(author[1]))
2103                                 author++;
2104                 }
2106                 author = initials;
2107         }
2109         return draw_field(view, LINE_AUTHOR, author, opt_author_cols, trim);
2112 static bool
2113 draw_mode(struct view *view, mode_t mode)
2115         const char *str;
2117         if (S_ISDIR(mode))
2118                 str = "drwxr-xr-x";
2119         else if (S_ISLNK(mode))
2120                 str = "lrwxrwxrwx";
2121         else if (S_ISGITLINK(mode))
2122                 str = "m---------";
2123         else if (S_ISREG(mode) && mode & S_IXUSR)
2124                 str = "-rwxr-xr-x";
2125         else if (S_ISREG(mode))
2126                 str = "-rw-r--r--";
2127         else
2128                 str = "----------";
2130         return draw_field(view, LINE_MODE, str, STRING_SIZE("-rw-r--r-- "), FALSE);
2133 static bool
2134 draw_lineno(struct view *view, unsigned int lineno)
2136         char number[10];
2137         int digits3 = view->digits < 3 ? 3 : view->digits;
2138         int max = MIN(view->width + view->yoffset - view->col, digits3);
2139         char *text = NULL;
2141         lineno += view->offset + 1;
2142         if (lineno == 1 || (lineno % opt_num_interval) == 0) {
2143                 static char fmt[] = "%1ld";
2145                 fmt[1] = '0' + (view->digits <= 9 ? digits3 : 1);
2146                 if (string_format(number, fmt, lineno))
2147                         text = number;
2148         }
2149         if (text)
2150                 view->col += draw_chars(view, LINE_LINE_NUMBER, text, max, TRUE);
2151         else
2152                 view->col += draw_space(view, LINE_LINE_NUMBER, max, digits3);
2153         return draw_graphic(view, LINE_DEFAULT, &line_graphics[LINE_GRAPHIC_VLINE], 1);
2156 static bool
2157 draw_view_line(struct view *view, unsigned int lineno)
2159         struct line *line;
2160         bool selected = (view->offset + lineno == view->lineno);
2162         assert(view_is_displayed(view));
2164         if (view->offset + lineno >= view->lines)
2165                 return FALSE;
2167         line = &view->line[view->offset + lineno];
2169         wmove(view->win, lineno, 0);
2170         if (line->cleareol)
2171                 wclrtoeol(view->win);
2172         view->col = 0;
2173         view->curline = line;
2174         view->curtype = LINE_NONE;
2175         line->selected = FALSE;
2176         line->dirty = line->cleareol = 0;
2178         if (selected) {
2179                 set_view_attr(view, LINE_CURSOR);
2180                 line->selected = TRUE;
2181                 view->ops->select(view, line);
2182         }
2184         return view->ops->draw(view, line, lineno);
2187 static void
2188 redraw_view_dirty(struct view *view)
2190         bool dirty = FALSE;
2191         int lineno;
2193         for (lineno = 0; lineno < view->height; lineno++) {
2194                 if (view->offset + lineno >= view->lines)
2195                         break;
2196                 if (!view->line[view->offset + lineno].dirty)
2197                         continue;
2198                 dirty = TRUE;
2199                 if (!draw_view_line(view, lineno))
2200                         break;
2201         }
2203         if (!dirty)
2204                 return;
2205         wnoutrefresh(view->win);
2208 static void
2209 redraw_view_from(struct view *view, int lineno)
2211         assert(0 <= lineno && lineno < view->height);
2213         for (; lineno < view->height; lineno++) {
2214                 if (!draw_view_line(view, lineno))
2215                         break;
2216         }
2218         wnoutrefresh(view->win);
2221 static void
2222 redraw_view(struct view *view)
2224         werase(view->win);
2225         redraw_view_from(view, 0);
2229 static void
2230 update_view_title(struct view *view)
2232         char buf[SIZEOF_STR];
2233         char state[SIZEOF_STR];
2234         size_t bufpos = 0, statelen = 0;
2236         assert(view_is_displayed(view));
2238         if (view != VIEW(REQ_VIEW_STATUS) && view->lines) {
2239                 unsigned int view_lines = view->offset + view->height;
2240                 unsigned int lines = view->lines
2241                                    ? MIN(view_lines, view->lines) * 100 / view->lines
2242                                    : 0;
2244                 string_format_from(state, &statelen, " - %s %d of %d (%d%%)",
2245                                    view->ops->type,
2246                                    view->lineno + 1,
2247                                    view->lines,
2248                                    lines);
2250         }
2252         if (view->pipe) {
2253                 time_t secs = time(NULL) - view->start_time;
2255                 /* Three git seconds are a long time ... */
2256                 if (secs > 2)
2257                         string_format_from(state, &statelen, " loading %lds", secs);
2258         }
2260         string_format_from(buf, &bufpos, "[%s]", view->name);
2261         if (*view->ref && bufpos < view->width) {
2262                 size_t refsize = strlen(view->ref);
2263                 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2265                 if (minsize < view->width)
2266                         refsize = view->width - minsize + 7;
2267                 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2268         }
2270         if (statelen && bufpos < view->width) {
2271                 string_format_from(buf, &bufpos, "%s", state);
2272         }
2274         if (view == display[current_view])
2275                 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2276         else
2277                 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2279         mvwaddnstr(view->title, 0, 0, buf, bufpos);
2280         wclrtoeol(view->title);
2281         wnoutrefresh(view->title);
2284 static int
2285 apply_step(double step, int value)
2287         if (step >= 1)
2288                 return (int) step;
2289         value *= step + 0.01;
2290         return value ? value : 1;
2293 static void
2294 resize_display(void)
2296         int offset, i;
2297         struct view *base = display[0];
2298         struct view *view = display[1] ? display[1] : display[0];
2300         /* Setup window dimensions */
2302         getmaxyx(stdscr, base->height, base->width);
2304         /* Make room for the status window. */
2305         base->height -= 1;
2307         if (view != base) {
2308                 /* Horizontal split. */
2309                 view->width   = base->width;
2310                 view->height  = apply_step(opt_scale_split_view, base->height);
2311                 view->height  = MAX(view->height, MIN_VIEW_HEIGHT);
2312                 view->height  = MIN(view->height, base->height - MIN_VIEW_HEIGHT);
2313                 base->height -= view->height;
2315                 /* Make room for the title bar. */
2316                 view->height -= 1;
2317         }
2319         /* Make room for the title bar. */
2320         base->height -= 1;
2322         offset = 0;
2324         foreach_displayed_view (view, i) {
2325                 if (!view->win) {
2326                         view->win = newwin(view->height, 0, offset, 0);
2327                         if (!view->win)
2328                                 die("Failed to create %s view", view->name);
2330                         scrollok(view->win, FALSE);
2332                         view->title = newwin(1, 0, offset + view->height, 0);
2333                         if (!view->title)
2334                                 die("Failed to create title window");
2336                 } else {
2337                         wresize(view->win, view->height, view->width);
2338                         mvwin(view->win,   offset, 0);
2339                         mvwin(view->title, offset + view->height, 0);
2340                 }
2342                 offset += view->height + 1;
2343         }
2346 static void
2347 redraw_display(bool clear)
2349         struct view *view;
2350         int i;
2352         foreach_displayed_view (view, i) {
2353                 if (clear)
2354                         wclear(view->win);
2355                 redraw_view(view);
2356                 update_view_title(view);
2357         }
2360 static void
2361 toggle_view_option(bool *option, const char *help)
2363         *option = !*option;
2364         redraw_display(FALSE);
2365         report("%sabling %s", *option ? "En" : "Dis", help);
2368 static void
2369 open_option_menu(void)
2371         const struct menu_item menu[] = {
2372                 { '.', "line numbers", &opt_line_number },
2373                 { 'D', "date display", &opt_date },
2374                 { 'A', "author display", &opt_author },
2375                 { 'g', "revision graph display", &opt_rev_graph },
2376                 { 'F', "reference display", &opt_show_refs },
2377                 { 0 }
2378         };
2379         int selected = 0;
2381         if (prompt_menu("Toggle option", menu, &selected))
2382                 toggle_view_option(menu[selected].data, menu[selected].text);
2385 static void
2386 maximize_view(struct view *view)
2388         memset(display, 0, sizeof(display));
2389         current_view = 0;
2390         display[current_view] = view;
2391         resize_display();
2392         redraw_display(FALSE);
2393         report("");
2397 /*
2398  * Navigation
2399  */
2401 static bool
2402 goto_view_line(struct view *view, unsigned long offset, unsigned long lineno)
2404         if (lineno >= view->lines)
2405                 lineno = view->lines > 0 ? view->lines - 1 : 0;
2407         if (offset > lineno || offset + view->height <= lineno) {
2408                 unsigned long half = view->height / 2;
2410                 if (lineno > half)
2411                         offset = lineno - half;
2412                 else
2413                         offset = 0;
2414         }
2416         if (offset != view->offset || lineno != view->lineno) {
2417                 view->offset = offset;
2418                 view->lineno = lineno;
2419                 return TRUE;
2420         }
2422         return FALSE;
2425 /* Scrolling backend */
2426 static void
2427 do_scroll_view(struct view *view, int lines)
2429         bool redraw_current_line = FALSE;
2431         /* The rendering expects the new offset. */
2432         view->offset += lines;
2434         assert(0 <= view->offset && view->offset < view->lines);
2435         assert(lines);
2437         /* Move current line into the view. */
2438         if (view->lineno < view->offset) {
2439                 view->lineno = view->offset;
2440                 redraw_current_line = TRUE;
2441         } else if (view->lineno >= view->offset + view->height) {
2442                 view->lineno = view->offset + view->height - 1;
2443                 redraw_current_line = TRUE;
2444         }
2446         assert(view->offset <= view->lineno && view->lineno < view->lines);
2448         /* Redraw the whole screen if scrolling is pointless. */
2449         if (view->height < ABS(lines)) {
2450                 redraw_view(view);
2452         } else {
2453                 int line = lines > 0 ? view->height - lines : 0;
2454                 int end = line + ABS(lines);
2456                 scrollok(view->win, TRUE);
2457                 wscrl(view->win, lines);
2458                 scrollok(view->win, FALSE);
2460                 while (line < end && draw_view_line(view, line))
2461                         line++;
2463                 if (redraw_current_line)
2464                         draw_view_line(view, view->lineno - view->offset);
2465                 wnoutrefresh(view->win);
2466         }
2468         view->has_scrolled = TRUE;
2469         report("");
2472 /* Scroll frontend */
2473 static void
2474 scroll_view(struct view *view, enum request request)
2476         int lines = 1;
2478         assert(view_is_displayed(view));
2480         switch (request) {
2481         case REQ_SCROLL_LEFT:
2482                 if (view->yoffset == 0) {
2483                         report("Cannot scroll beyond the first column");
2484                         return;
2485                 }
2486                 if (view->yoffset <= apply_step(opt_hscroll, view->width))
2487                         view->yoffset = 0;
2488                 else
2489                         view->yoffset -= apply_step(opt_hscroll, view->width);
2490                 redraw_view_from(view, 0);
2491                 report("");
2492                 return;
2493         case REQ_SCROLL_RIGHT:
2494                 view->yoffset += apply_step(opt_hscroll, view->width);
2495                 redraw_view(view);
2496                 report("");
2497                 return;
2498         case REQ_SCROLL_PAGE_DOWN:
2499                 lines = view->height;
2500         case REQ_SCROLL_LINE_DOWN:
2501                 if (view->offset + lines > view->lines)
2502                         lines = view->lines - view->offset;
2504                 if (lines == 0 || view->offset + view->height >= view->lines) {
2505                         report("Cannot scroll beyond the last line");
2506                         return;
2507                 }
2508                 break;
2510         case REQ_SCROLL_PAGE_UP:
2511                 lines = view->height;
2512         case REQ_SCROLL_LINE_UP:
2513                 if (lines > view->offset)
2514                         lines = view->offset;
2516                 if (lines == 0) {
2517                         report("Cannot scroll beyond the first line");
2518                         return;
2519                 }
2521                 lines = -lines;
2522                 break;
2524         default:
2525                 die("request %d not handled in switch", request);
2526         }
2528         do_scroll_view(view, lines);
2531 /* Cursor moving */
2532 static void
2533 move_view(struct view *view, enum request request)
2535         int scroll_steps = 0;
2536         int steps;
2538         switch (request) {
2539         case REQ_MOVE_FIRST_LINE:
2540                 steps = -view->lineno;
2541                 break;
2543         case REQ_MOVE_LAST_LINE:
2544                 steps = view->lines - view->lineno - 1;
2545                 break;
2547         case REQ_MOVE_PAGE_UP:
2548                 steps = view->height > view->lineno
2549                       ? -view->lineno : -view->height;
2550                 break;
2552         case REQ_MOVE_PAGE_DOWN:
2553                 steps = view->lineno + view->height >= view->lines
2554                       ? view->lines - view->lineno - 1 : view->height;
2555                 break;
2557         case REQ_MOVE_UP:
2558                 steps = -1;
2559                 break;
2561         case REQ_MOVE_DOWN:
2562                 steps = 1;
2563                 break;
2565         default:
2566                 die("request %d not handled in switch", request);
2567         }
2569         if (steps <= 0 && view->lineno == 0) {
2570                 report("Cannot move beyond the first line");
2571                 return;
2573         } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2574                 report("Cannot move beyond the last line");
2575                 return;
2576         }
2578         /* Move the current line */
2579         view->lineno += steps;
2580         assert(0 <= view->lineno && view->lineno < view->lines);
2582         /* Check whether the view needs to be scrolled */
2583         if (view->lineno < view->offset ||
2584             view->lineno >= view->offset + view->height) {
2585                 scroll_steps = steps;
2586                 if (steps < 0 && -steps > view->offset) {
2587                         scroll_steps = -view->offset;
2589                 } else if (steps > 0) {
2590                         if (view->lineno == view->lines - 1 &&
2591                             view->lines > view->height) {
2592                                 scroll_steps = view->lines - view->offset - 1;
2593                                 if (scroll_steps >= view->height)
2594                                         scroll_steps -= view->height - 1;
2595                         }
2596                 }
2597         }
2599         if (!view_is_displayed(view)) {
2600                 view->offset += scroll_steps;
2601                 assert(0 <= view->offset && view->offset < view->lines);
2602                 view->ops->select(view, &view->line[view->lineno]);
2603                 return;
2604         }
2606         /* Repaint the old "current" line if we be scrolling */
2607         if (ABS(steps) < view->height)
2608                 draw_view_line(view, view->lineno - steps - view->offset);
2610         if (scroll_steps) {
2611                 do_scroll_view(view, scroll_steps);
2612                 return;
2613         }
2615         /* Draw the current line */
2616         draw_view_line(view, view->lineno - view->offset);
2618         wnoutrefresh(view->win);
2619         report("");
2623 /*
2624  * Searching
2625  */
2627 static void search_view(struct view *view, enum request request);
2629 static bool
2630 grep_text(struct view *view, const char *text[])
2632         regmatch_t pmatch;
2633         size_t i;
2635         for (i = 0; text[i]; i++)
2636                 if (*text[i] &&
2637                     regexec(view->regex, text[i], 1, &pmatch, 0) != REG_NOMATCH)
2638                         return TRUE;
2639         return FALSE;
2642 static void
2643 select_view_line(struct view *view, unsigned long lineno)
2645         unsigned long old_lineno = view->lineno;
2646         unsigned long old_offset = view->offset;
2648         if (goto_view_line(view, view->offset, lineno)) {
2649                 if (view_is_displayed(view)) {
2650                         if (old_offset != view->offset) {
2651                                 redraw_view(view);
2652                         } else {
2653                                 draw_view_line(view, old_lineno - view->offset);
2654                                 draw_view_line(view, view->lineno - view->offset);
2655                                 wnoutrefresh(view->win);
2656                         }
2657                 } else {
2658                         view->ops->select(view, &view->line[view->lineno]);
2659                 }
2660         }
2663 static void
2664 find_next(struct view *view, enum request request)
2666         unsigned long lineno = view->lineno;
2667         int direction;
2669         if (!*view->grep) {
2670                 if (!*opt_search)
2671                         report("No previous search");
2672                 else
2673                         search_view(view, request);
2674                 return;
2675         }
2677         switch (request) {
2678         case REQ_SEARCH:
2679         case REQ_FIND_NEXT:
2680                 direction = 1;
2681                 break;
2683         case REQ_SEARCH_BACK:
2684         case REQ_FIND_PREV:
2685                 direction = -1;
2686                 break;
2688         default:
2689                 return;
2690         }
2692         if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
2693                 lineno += direction;
2695         /* Note, lineno is unsigned long so will wrap around in which case it
2696          * will become bigger than view->lines. */
2697         for (; lineno < view->lines; lineno += direction) {
2698                 if (view->ops->grep(view, &view->line[lineno])) {
2699                         select_view_line(view, lineno);
2700                         report("Line %ld matches '%s'", lineno + 1, view->grep);
2701                         return;
2702                 }
2703         }
2705         report("No match found for '%s'", view->grep);
2708 static void
2709 search_view(struct view *view, enum request request)
2711         int regex_err;
2713         if (view->regex) {
2714                 regfree(view->regex);
2715                 *view->grep = 0;
2716         } else {
2717                 view->regex = calloc(1, sizeof(*view->regex));
2718                 if (!view->regex)
2719                         return;
2720         }
2722         regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
2723         if (regex_err != 0) {
2724                 char buf[SIZEOF_STR] = "unknown error";
2726                 regerror(regex_err, view->regex, buf, sizeof(buf));
2727                 report("Search failed: %s", buf);
2728                 return;
2729         }
2731         string_copy(view->grep, opt_search);
2733         find_next(view, request);
2736 /*
2737  * Incremental updating
2738  */
2740 static void
2741 reset_view(struct view *view)
2743         int i;
2745         for (i = 0; i < view->lines; i++)
2746                 free(view->line[i].data);
2747         free(view->line);
2749         view->p_offset = view->offset;
2750         view->p_yoffset = view->yoffset;
2751         view->p_lineno = view->lineno;
2753         view->line = NULL;
2754         view->offset = 0;
2755         view->yoffset = 0;
2756         view->lines  = 0;
2757         view->lineno = 0;
2758         view->vid[0] = 0;
2759         view->update_secs = 0;
2762 static void
2763 free_argv(const char *argv[])
2765         int argc;
2767         for (argc = 0; argv[argc]; argc++)
2768                 free((void *) argv[argc]);
2771 static bool
2772 format_argv(const char *dst_argv[], const char *src_argv[], enum format_flags flags)
2774         char buf[SIZEOF_STR];
2775         int argc;
2776         bool noreplace = flags == FORMAT_NONE;
2778         free_argv(dst_argv);
2780         for (argc = 0; src_argv[argc]; argc++) {
2781                 const char *arg = src_argv[argc];
2782                 size_t bufpos = 0;
2784                 while (arg) {
2785                         char *next = strstr(arg, "%(");
2786                         int len = next - arg;
2787                         const char *value;
2789                         if (!next || noreplace) {
2790                                 if (flags == FORMAT_DASH && !strcmp(arg, "--"))
2791                                         noreplace = TRUE;
2792                                 len = strlen(arg);
2793                                 value = "";
2795                         } else if (!prefixcmp(next, "%(directory)")) {
2796                                 value = opt_path;
2798                         } else if (!prefixcmp(next, "%(file)")) {
2799                                 value = opt_file;
2801                         } else if (!prefixcmp(next, "%(ref)")) {
2802                                 value = *opt_ref ? opt_ref : "HEAD";
2804                         } else if (!prefixcmp(next, "%(head)")) {
2805                                 value = ref_head;
2807                         } else if (!prefixcmp(next, "%(commit)")) {
2808                                 value = ref_commit;
2810                         } else if (!prefixcmp(next, "%(blob)")) {
2811                                 value = ref_blob;
2813                         } else {
2814                                 report("Unknown replacement: `%s`", next);
2815                                 return FALSE;
2816                         }
2818                         if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
2819                                 return FALSE;
2821                         arg = next && !noreplace ? strchr(next, ')') + 1 : NULL;
2822                 }
2824                 dst_argv[argc] = strdup(buf);
2825                 if (!dst_argv[argc])
2826                         break;
2827         }
2829         dst_argv[argc] = NULL;
2831         return src_argv[argc] == NULL;
2834 static bool
2835 restore_view_position(struct view *view)
2837         if (!view->p_restore || (view->pipe && view->lines <= view->p_lineno))
2838                 return FALSE;
2840         /* Changing the view position cancels the restoring. */
2841         /* FIXME: Changing back to the first line is not detected. */
2842         if (view->offset != 0 || view->lineno != 0) {
2843                 view->p_restore = FALSE;
2844                 return FALSE;
2845         }
2847         if (goto_view_line(view, view->p_offset, view->p_lineno) &&
2848             view_is_displayed(view))
2849                 werase(view->win);
2851         view->yoffset = view->p_yoffset;
2852         view->p_restore = FALSE;
2854         return TRUE;
2857 static void
2858 end_update(struct view *view, bool force)
2860         if (!view->pipe)
2861                 return;
2862         while (!view->ops->read(view, NULL))
2863                 if (!force)
2864                         return;
2865         set_nonblocking_input(FALSE);
2866         if (force)
2867                 kill_io(view->pipe);
2868         done_io(view->pipe);
2869         view->pipe = NULL;
2872 static void
2873 setup_update(struct view *view, const char *vid)
2875         set_nonblocking_input(TRUE);
2876         reset_view(view);
2877         string_copy_rev(view->vid, vid);
2878         view->pipe = &view->io;
2879         view->start_time = time(NULL);
2882 static bool
2883 prepare_update(struct view *view, const char *argv[], const char *dir,
2884                enum format_flags flags)
2886         if (view->pipe)
2887                 end_update(view, TRUE);
2888         return init_io_rd(&view->io, argv, dir, flags);
2891 static bool
2892 prepare_update_file(struct view *view, const char *name)
2894         if (view->pipe)
2895                 end_update(view, TRUE);
2896         return io_open(&view->io, name);
2899 static bool
2900 begin_update(struct view *view, bool refresh)
2902         if (view->pipe)
2903                 end_update(view, TRUE);
2905         if (refresh) {
2906                 if (!start_io(&view->io))
2907                         return FALSE;
2909         } else {
2910                 if (view == VIEW(REQ_VIEW_TREE) && strcmp(view->vid, view->id))
2911                         opt_path[0] = 0;
2913                 if (!run_io_rd(&view->io, view->ops->argv, FORMAT_ALL))
2914                         return FALSE;
2916                 /* Put the current ref_* value to the view title ref
2917                  * member. This is needed by the blob view. Most other
2918                  * views sets it automatically after loading because the
2919                  * first line is a commit line. */
2920                 string_copy_rev(view->ref, view->id);
2921         }
2923         setup_update(view, view->id);
2925         return TRUE;
2928 static bool
2929 update_view(struct view *view)
2931         char out_buffer[BUFSIZ * 2];
2932         char *line;
2933         /* Clear the view and redraw everything since the tree sorting
2934          * might have rearranged things. */
2935         bool redraw = view->lines == 0;
2936         bool can_read = TRUE;
2938         if (!view->pipe)
2939                 return TRUE;
2941         if (!io_can_read(view->pipe)) {
2942                 if (view->lines == 0 && view_is_displayed(view)) {
2943                         time_t secs = time(NULL) - view->start_time;
2945                         if (secs > 1 && secs > view->update_secs) {
2946                                 if (view->update_secs == 0)
2947                                         redraw_view(view);
2948                                 update_view_title(view);
2949                                 view->update_secs = secs;
2950                         }
2951                 }
2952                 return TRUE;
2953         }
2955         for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
2956                 if (opt_iconv != ICONV_NONE) {
2957                         ICONV_CONST char *inbuf = line;
2958                         size_t inlen = strlen(line) + 1;
2960                         char *outbuf = out_buffer;
2961                         size_t outlen = sizeof(out_buffer);
2963                         size_t ret;
2965                         ret = iconv(opt_iconv, &inbuf, &inlen, &outbuf, &outlen);
2966                         if (ret != (size_t) -1)
2967                                 line = out_buffer;
2968                 }
2970                 if (!view->ops->read(view, line)) {
2971                         report("Allocation failure");
2972                         end_update(view, TRUE);
2973                         return FALSE;
2974                 }
2975         }
2977         {
2978                 unsigned long lines = view->lines;
2979                 int digits;
2981                 for (digits = 0; lines; digits++)
2982                         lines /= 10;
2984                 /* Keep the displayed view in sync with line number scaling. */
2985                 if (digits != view->digits) {
2986                         view->digits = digits;
2987                         if (opt_line_number || view == VIEW(REQ_VIEW_BLAME))
2988                                 redraw = TRUE;
2989                 }
2990         }
2992         if (io_error(view->pipe)) {
2993                 report("Failed to read: %s", io_strerror(view->pipe));
2994                 end_update(view, TRUE);
2996         } else if (io_eof(view->pipe)) {
2997                 report("");
2998                 end_update(view, FALSE);
2999         }
3001         if (restore_view_position(view))
3002                 redraw = TRUE;
3004         if (!view_is_displayed(view))
3005                 return TRUE;
3007         if (redraw)
3008                 redraw_view_from(view, 0);
3009         else
3010                 redraw_view_dirty(view);
3012         /* Update the title _after_ the redraw so that if the redraw picks up a
3013          * commit reference in view->ref it'll be available here. */
3014         update_view_title(view);
3015         return TRUE;
3018 DEFINE_ALLOCATOR(realloc_lines, struct line, 256)
3020 static struct line *
3021 add_line_data(struct view *view, void *data, enum line_type type)
3023         struct line *line;
3025         if (!realloc_lines(&view->line, view->lines, 1))
3026                 return NULL;
3028         line = &view->line[view->lines++];
3029         memset(line, 0, sizeof(*line));
3030         line->type = type;
3031         line->data = data;
3032         line->dirty = 1;
3034         return line;
3037 static struct line *
3038 add_line_text(struct view *view, const char *text, enum line_type type)
3040         char *data = text ? strdup(text) : NULL;
3042         return data ? add_line_data(view, data, type) : NULL;
3045 static struct line *
3046 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
3048         char buf[SIZEOF_STR];
3049         va_list args;
3051         va_start(args, fmt);
3052         if (vsnprintf(buf, sizeof(buf), fmt, args) >= sizeof(buf))
3053                 buf[0] = 0;
3054         va_end(args);
3056         return buf[0] ? add_line_text(view, buf, type) : NULL;
3059 /*
3060  * View opening
3061  */
3063 enum open_flags {
3064         OPEN_DEFAULT = 0,       /* Use default view switching. */
3065         OPEN_SPLIT = 1,         /* Split current view. */
3066         OPEN_RELOAD = 4,        /* Reload view even if it is the current. */
3067         OPEN_REFRESH = 16,      /* Refresh view using previous command. */
3068         OPEN_PREPARED = 32,     /* Open already prepared command. */
3069 };
3071 static void
3072 open_view(struct view *prev, enum request request, enum open_flags flags)
3074         bool split = !!(flags & OPEN_SPLIT);
3075         bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
3076         bool nomaximize = !!(flags & OPEN_REFRESH);
3077         struct view *view = VIEW(request);
3078         int nviews = displayed_views();
3079         struct view *base_view = display[0];
3081         if (view == prev && nviews == 1 && !reload) {
3082                 report("Already in %s view", view->name);
3083                 return;
3084         }
3086         if (view->git_dir && !opt_git_dir[0]) {
3087                 report("The %s view is disabled in pager view", view->name);
3088                 return;
3089         }
3091         if (split) {
3092                 display[1] = view;
3093                 current_view = 1;
3094         } else if (!nomaximize) {
3095                 /* Maximize the current view. */
3096                 memset(display, 0, sizeof(display));
3097                 current_view = 0;
3098                 display[current_view] = view;
3099         }
3101         /* Resize the view when switching between split- and full-screen,
3102          * or when switching between two different full-screen views. */
3103         if (nviews != displayed_views() ||
3104             (nviews == 1 && base_view != display[0]))
3105                 resize_display();
3107         if (view->ops->open) {
3108                 if (view->pipe)
3109                         end_update(view, TRUE);
3110                 if (!view->ops->open(view)) {
3111                         report("Failed to load %s view", view->name);
3112                         return;
3113                 }
3114                 restore_view_position(view);
3116         } else if ((reload || strcmp(view->vid, view->id)) &&
3117                    !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
3118                 report("Failed to load %s view", view->name);
3119                 return;
3120         }
3122         if (split && prev->lineno - prev->offset >= prev->height) {
3123                 /* Take the title line into account. */
3124                 int lines = prev->lineno - prev->offset - prev->height + 1;
3126                 /* Scroll the view that was split if the current line is
3127                  * outside the new limited view. */
3128                 do_scroll_view(prev, lines);
3129         }
3131         if (prev && view != prev) {
3132                 if (split) {
3133                         /* "Blur" the previous view. */
3134                         update_view_title(prev);
3135                 }
3137                 view->parent = prev;
3138         }
3140         if (view->pipe && view->lines == 0) {
3141                 /* Clear the old view and let the incremental updating refill
3142                  * the screen. */
3143                 werase(view->win);
3144                 view->p_restore = flags & (OPEN_RELOAD | OPEN_REFRESH);
3145                 report("");
3146         } else if (view_is_displayed(view)) {
3147                 redraw_view(view);
3148                 report("");
3149         }
3152 static void
3153 open_external_viewer(const char *argv[], const char *dir)
3155         def_prog_mode();           /* save current tty modes */
3156         endwin();                  /* restore original tty modes */
3157         run_io_fg(argv, dir);
3158         fprintf(stderr, "Press Enter to continue");
3159         getc(opt_tty);
3160         reset_prog_mode();
3161         redraw_display(TRUE);
3164 static void
3165 open_mergetool(const char *file)
3167         const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
3169         open_external_viewer(mergetool_argv, opt_cdup);
3172 static void
3173 open_editor(bool from_root, const char *file)
3175         const char *editor_argv[] = { "vi", file, NULL };
3176         const char *editor;
3178         editor = getenv("GIT_EDITOR");
3179         if (!editor && *opt_editor)
3180                 editor = opt_editor;
3181         if (!editor)
3182                 editor = getenv("VISUAL");
3183         if (!editor)
3184                 editor = getenv("EDITOR");
3185         if (!editor)
3186                 editor = "vi";
3188         editor_argv[0] = editor;
3189         open_external_viewer(editor_argv, from_root ? opt_cdup : NULL);
3192 static void
3193 open_run_request(enum request request)
3195         struct run_request *req = get_run_request(request);
3196         const char *argv[ARRAY_SIZE(req->argv)] = { NULL };
3198         if (!req) {
3199                 report("Unknown run request");
3200                 return;
3201         }
3203         if (format_argv(argv, req->argv, FORMAT_ALL))
3204                 open_external_viewer(argv, NULL);
3205         free_argv(argv);
3208 /*
3209  * User request switch noodle
3210  */
3212 static int
3213 view_driver(struct view *view, enum request request)
3215         int i;
3217         if (request == REQ_NONE)
3218                 return TRUE;
3220         if (request > REQ_NONE) {
3221                 open_run_request(request);
3222                 /* FIXME: When all views can refresh always do this. */
3223                 if (view == VIEW(REQ_VIEW_STATUS) ||
3224                     view == VIEW(REQ_VIEW_MAIN) ||
3225                     view == VIEW(REQ_VIEW_LOG) ||
3226                     view == VIEW(REQ_VIEW_BRANCH) ||
3227                     view == VIEW(REQ_VIEW_STAGE))
3228                         request = REQ_REFRESH;
3229                 else
3230                         return TRUE;
3231         }
3233         if (view && view->lines) {
3234                 request = view->ops->request(view, request, &view->line[view->lineno]);
3235                 if (request == REQ_NONE)
3236                         return TRUE;
3237         }
3239         switch (request) {
3240         case REQ_MOVE_UP:
3241         case REQ_MOVE_DOWN:
3242         case REQ_MOVE_PAGE_UP:
3243         case REQ_MOVE_PAGE_DOWN:
3244         case REQ_MOVE_FIRST_LINE:
3245         case REQ_MOVE_LAST_LINE:
3246                 move_view(view, request);
3247                 break;
3249         case REQ_SCROLL_LEFT:
3250         case REQ_SCROLL_RIGHT:
3251         case REQ_SCROLL_LINE_DOWN:
3252         case REQ_SCROLL_LINE_UP:
3253         case REQ_SCROLL_PAGE_DOWN:
3254         case REQ_SCROLL_PAGE_UP:
3255                 scroll_view(view, request);
3256                 break;
3258         case REQ_VIEW_BLAME:
3259                 if (!opt_file[0]) {
3260                         report("No file chosen, press %s to open tree view",
3261                                get_key(view->keymap, REQ_VIEW_TREE));
3262                         break;
3263                 }
3264                 open_view(view, request, OPEN_DEFAULT);
3265                 break;
3267         case REQ_VIEW_BLOB:
3268                 if (!ref_blob[0]) {
3269                         report("No file chosen, press %s to open tree view",
3270                                get_key(view->keymap, REQ_VIEW_TREE));
3271                         break;
3272                 }
3273                 open_view(view, request, OPEN_DEFAULT);
3274                 break;
3276         case REQ_VIEW_PAGER:
3277                 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3278                         report("No pager content, press %s to run command from prompt",
3279                                get_key(view->keymap, REQ_PROMPT));
3280                         break;
3281                 }
3282                 open_view(view, request, OPEN_DEFAULT);
3283                 break;
3285         case REQ_VIEW_STAGE:
3286                 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3287                         report("No stage content, press %s to open the status view and choose file",
3288                                get_key(view->keymap, REQ_VIEW_STATUS));
3289                         break;
3290                 }
3291                 open_view(view, request, OPEN_DEFAULT);
3292                 break;
3294         case REQ_VIEW_STATUS:
3295                 if (opt_is_inside_work_tree == FALSE) {
3296                         report("The status view requires a working tree");
3297                         break;
3298                 }
3299                 open_view(view, request, OPEN_DEFAULT);
3300                 break;
3302         case REQ_VIEW_MAIN:
3303         case REQ_VIEW_DIFF:
3304         case REQ_VIEW_LOG:
3305         case REQ_VIEW_TREE:
3306         case REQ_VIEW_HELP:
3307         case REQ_VIEW_BRANCH:
3308                 open_view(view, request, OPEN_DEFAULT);
3309                 break;
3311         case REQ_NEXT:
3312         case REQ_PREVIOUS:
3313                 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3315                 if ((view == VIEW(REQ_VIEW_DIFF) &&
3316                      view->parent == VIEW(REQ_VIEW_MAIN)) ||
3317                    (view == VIEW(REQ_VIEW_DIFF) &&
3318                      view->parent == VIEW(REQ_VIEW_BLAME)) ||
3319                    (view == VIEW(REQ_VIEW_STAGE) &&
3320                      view->parent == VIEW(REQ_VIEW_STATUS)) ||
3321                    (view == VIEW(REQ_VIEW_BLOB) &&
3322                      view->parent == VIEW(REQ_VIEW_TREE)) ||
3323                    (view == VIEW(REQ_VIEW_MAIN) &&
3324                      view->parent == VIEW(REQ_VIEW_BRANCH))) {
3325                         int line;
3327                         view = view->parent;
3328                         line = view->lineno;
3329                         move_view(view, request);
3330                         if (view_is_displayed(view))
3331                                 update_view_title(view);
3332                         if (line != view->lineno)
3333                                 view->ops->request(view, REQ_ENTER,
3334                                                    &view->line[view->lineno]);
3336                 } else {
3337                         move_view(view, request);
3338                 }
3339                 break;
3341         case REQ_VIEW_NEXT:
3342         {
3343                 int nviews = displayed_views();
3344                 int next_view = (current_view + 1) % nviews;
3346                 if (next_view == current_view) {
3347                         report("Only one view is displayed");
3348                         break;
3349                 }
3351                 current_view = next_view;
3352                 /* Blur out the title of the previous view. */
3353                 update_view_title(view);
3354                 report("");
3355                 break;
3356         }
3357         case REQ_REFRESH:
3358                 report("Refreshing is not yet supported for the %s view", view->name);
3359                 break;
3361         case REQ_MAXIMIZE:
3362                 if (displayed_views() == 2)
3363                         maximize_view(view);
3364                 break;
3366         case REQ_OPTIONS:
3367                 open_option_menu();
3368                 break;
3370         case REQ_TOGGLE_LINENO:
3371                 toggle_view_option(&opt_line_number, "line numbers");
3372                 break;
3374         case REQ_TOGGLE_DATE:
3375                 toggle_view_option(&opt_date, "date display");
3376                 break;
3378         case REQ_TOGGLE_DATE_SHORT:
3379                 toggle_view_option(&opt_date_short, "date shortening");
3380                 break;
3382         case REQ_TOGGLE_AUTHOR:
3383                 toggle_view_option(&opt_author, "author display");
3384                 break;
3386         case REQ_TOGGLE_REV_GRAPH:
3387                 toggle_view_option(&opt_rev_graph, "revision graph display");
3388                 break;
3390         case REQ_TOGGLE_REFS:
3391                 toggle_view_option(&opt_show_refs, "reference display");
3392                 break;
3394         case REQ_TOGGLE_SORT_FIELD:
3395         case REQ_TOGGLE_SORT_ORDER:
3396                 report("Sorting is not yet supported for the %s view", view->name);
3397                 break;
3399         case REQ_SEARCH:
3400         case REQ_SEARCH_BACK:
3401                 search_view(view, request);
3402                 break;
3404         case REQ_FIND_NEXT:
3405         case REQ_FIND_PREV:
3406                 find_next(view, request);
3407                 break;
3409         case REQ_STOP_LOADING:
3410                 for (i = 0; i < ARRAY_SIZE(views); i++) {
3411                         view = &views[i];
3412                         if (view->pipe)
3413                                 report("Stopped loading the %s view", view->name),
3414                         end_update(view, TRUE);
3415                 }
3416                 break;
3418         case REQ_SHOW_VERSION:
3419                 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3420                 return TRUE;
3422         case REQ_SCREEN_REDRAW:
3423                 redraw_display(TRUE);
3424                 break;
3426         case REQ_EDIT:
3427                 report("Nothing to edit");
3428                 break;
3430         case REQ_ENTER:
3431                 report("Nothing to enter");
3432                 break;
3434         case REQ_VIEW_CLOSE:
3435                 /* XXX: Mark closed views by letting view->parent point to the
3436                  * view itself. Parents to closed view should never be
3437                  * followed. */
3438                 if (view->parent &&
3439                     view->parent->parent != view->parent) {
3440                         maximize_view(view->parent);
3441                         view->parent = view;
3442                         break;
3443                 }
3444                 /* Fall-through */
3445         case REQ_QUIT:
3446                 return FALSE;
3448         default:
3449                 report("Unknown key, press %s for help",
3450                        get_key(view->keymap, REQ_VIEW_HELP));
3451                 return TRUE;
3452         }
3454         return TRUE;
3458 /*
3459  * View backend utilities
3460  */
3462 enum sort_field {
3463         ORDERBY_NAME,
3464         ORDERBY_DATE,
3465         ORDERBY_AUTHOR,
3466 };
3468 struct sort_state {
3469         const enum sort_field *fields;
3470         size_t size, current;
3471         bool reverse;
3472 };
3474 #define SORT_STATE(fields) { fields, ARRAY_SIZE(fields), 0 }
3475 #define get_sort_field(state) ((state).fields[(state).current])
3476 #define sort_order(state, result) ((state).reverse ? -(result) : (result))
3478 static void
3479 sort_view(struct view *view, enum request request, struct sort_state *state,
3480           int (*compare)(const void *, const void *))
3482         switch (request) {
3483         case REQ_TOGGLE_SORT_FIELD:
3484                 state->current = (state->current + 1) % state->size;
3485                 break;
3487         case REQ_TOGGLE_SORT_ORDER:
3488                 state->reverse = !state->reverse;
3489                 break;
3490         default:
3491                 die("Not a sort request");
3492         }
3494         qsort(view->line, view->lines, sizeof(*view->line), compare);
3495         redraw_view(view);
3498 DEFINE_ALLOCATOR(realloc_authors, const char *, 256)
3500 /* Small author cache to reduce memory consumption. It uses binary
3501  * search to lookup or find place to position new entries. No entries
3502  * are ever freed. */
3503 static const char *
3504 get_author(const char *name)
3506         static const char **authors;
3507         static size_t authors_size;
3508         int from = 0, to = authors_size - 1;
3510         while (from <= to) {
3511                 size_t pos = (to + from) / 2;
3512                 int cmp = strcmp(name, authors[pos]);
3514                 if (!cmp)
3515                         return authors[pos];
3517                 if (cmp < 0)
3518                         to = pos - 1;
3519                 else
3520                         from = pos + 1;
3521         }
3523         if (!realloc_authors(&authors, authors_size, 1))
3524                 return NULL;
3525         name = strdup(name);
3526         if (!name)
3527                 return NULL;
3529         memmove(authors + from + 1, authors + from, (authors_size - from) * sizeof(*authors));
3530         authors[from] = name;
3531         authors_size++;
3533         return name;
3536 static void
3537 parse_timezone(time_t *time, const char *zone)
3539         long tz;
3541         tz  = ('0' - zone[1]) * 60 * 60 * 10;
3542         tz += ('0' - zone[2]) * 60 * 60;
3543         tz += ('0' - zone[3]) * 60;
3544         tz += ('0' - zone[4]);
3546         if (zone[0] == '-')
3547                 tz = -tz;
3549         *time -= tz;
3552 /* Parse author lines where the name may be empty:
3553  *      author  <email@address.tld> 1138474660 +0100
3554  */
3555 static void
3556 parse_author_line(char *ident, const char **author, time_t *time)
3558         char *nameend = strchr(ident, '<');
3559         char *emailend = strchr(ident, '>');
3561         if (nameend && emailend)
3562                 *nameend = *emailend = 0;
3563         ident = chomp_string(ident);
3564         if (!*ident) {
3565                 if (nameend)
3566                         ident = chomp_string(nameend + 1);
3567                 if (!*ident)
3568                         ident = "Unknown";
3569         }
3571         *author = get_author(ident);
3573         /* Parse epoch and timezone */
3574         if (emailend && emailend[1] == ' ') {
3575                 char *secs = emailend + 2;
3576                 char *zone = strchr(secs, ' ');
3578                 *time = (time_t) atol(secs);
3580                 if (zone && strlen(zone) == STRING_SIZE(" +0700"))
3581                         parse_timezone(time, zone + 1);
3582         }
3585 static bool
3586 open_commit_parent_menu(char buf[SIZEOF_STR], int *parents)
3588         char rev[SIZEOF_REV];
3589         const char *revlist_argv[] = {
3590                 "git", "log", "--no-color", "-1", "--pretty=format:%s", rev, NULL
3591         };
3592         struct menu_item *items;
3593         char text[SIZEOF_STR];
3594         bool ok = TRUE;
3595         int i;
3597         items = calloc(*parents + 1, sizeof(*items));
3598         if (!items)
3599                 return FALSE;
3601         for (i = 0; i < *parents; i++) {
3602                 string_copy_rev(rev, &buf[SIZEOF_REV * i]);
3603                 if (!run_io_buf(revlist_argv, text, sizeof(text)) ||
3604                     !(items[i].text = strdup(text))) {
3605                         ok = FALSE;
3606                         break;
3607                 }
3608         }
3610         if (ok) {
3611                 *parents = 0;
3612                 ok = prompt_menu("Select parent", items, parents);
3613         }
3614         for (i = 0; items[i].text; i++)
3615                 free((char *) items[i].text);
3616         free(items);
3617         return ok;
3620 static bool
3621 select_commit_parent(const char *id, char rev[SIZEOF_REV], const char *path)
3623         char buf[SIZEOF_STR * 4];
3624         const char *revlist_argv[] = {
3625                 "git", "log", "--no-color", "-1",
3626                         "--pretty=format:%P", id, "--", path, NULL
3627         };
3628         int parents;
3630         if (!run_io_buf(revlist_argv, buf, sizeof(buf)) ||
3631             (parents = strlen(buf) / 40) < 0) {
3632                 report("Failed to get parent information");
3633                 return FALSE;
3635         } else if (parents == 0) {
3636                 if (path)
3637                         report("Path '%s' does not exist in the parent", path);
3638                 else
3639                         report("The selected commit has no parents");
3640                 return FALSE;
3641         }
3643         if (parents > 1 && !open_commit_parent_menu(buf, &parents))
3644                 return FALSE;
3646         string_copy_rev(rev, &buf[41 * parents]);
3647         return TRUE;
3650 /*
3651  * Pager backend
3652  */
3654 static bool
3655 pager_draw(struct view *view, struct line *line, unsigned int lineno)
3657         char text[SIZEOF_STR];
3659         if (opt_line_number && draw_lineno(view, lineno))
3660                 return TRUE;
3662         string_expand(text, sizeof(text), line->data, opt_tab_size);
3663         draw_text(view, line->type, text, TRUE);
3664         return TRUE;
3667 static bool
3668 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
3670         const char *describe_argv[] = { "git", "describe", commit_id, NULL };
3671         char ref[SIZEOF_STR];
3673         if (!run_io_buf(describe_argv, ref, sizeof(ref)) || !*ref)
3674                 return TRUE;
3676         /* This is the only fatal call, since it can "corrupt" the buffer. */
3677         if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
3678                 return FALSE;
3680         return TRUE;
3683 static void
3684 add_pager_refs(struct view *view, struct line *line)
3686         char buf[SIZEOF_STR];
3687         char *commit_id = (char *)line->data + STRING_SIZE("commit ");
3688         struct ref_list *list;
3689         size_t bufpos = 0, i;
3690         const char *sep = "Refs: ";
3691         bool is_tag = FALSE;
3693         assert(line->type == LINE_COMMIT);
3695         list = get_ref_list(commit_id);
3696         if (!list) {
3697                 if (view == VIEW(REQ_VIEW_DIFF))
3698                         goto try_add_describe_ref;
3699                 return;
3700         }
3702         for (i = 0; i < list->size; i++) {
3703                 struct ref *ref = list->refs[i];
3704                 const char *fmt = ref->tag    ? "%s[%s]" :
3705                                   ref->remote ? "%s<%s>" : "%s%s";
3707                 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
3708                         return;
3709                 sep = ", ";
3710                 if (ref->tag)
3711                         is_tag = TRUE;
3712         }
3714         if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
3715 try_add_describe_ref:
3716                 /* Add <tag>-g<commit_id> "fake" reference. */
3717                 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
3718                         return;
3719         }
3721         if (bufpos == 0)
3722                 return;
3724         add_line_text(view, buf, LINE_PP_REFS);
3727 static bool
3728 pager_read(struct view *view, char *data)
3730         struct line *line;
3732         if (!data)
3733                 return TRUE;
3735         line = add_line_text(view, data, get_line_type(data));
3736         if (!line)
3737                 return FALSE;
3739         if (line->type == LINE_COMMIT &&
3740             (view == VIEW(REQ_VIEW_DIFF) ||
3741              view == VIEW(REQ_VIEW_LOG)))
3742                 add_pager_refs(view, line);
3744         return TRUE;
3747 static enum request
3748 pager_request(struct view *view, enum request request, struct line *line)
3750         int split = 0;
3752         if (request != REQ_ENTER)
3753                 return request;
3755         if (line->type == LINE_COMMIT &&
3756            (view == VIEW(REQ_VIEW_LOG) ||
3757             view == VIEW(REQ_VIEW_PAGER))) {
3758                 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
3759                 split = 1;
3760         }
3762         /* Always scroll the view even if it was split. That way
3763          * you can use Enter to scroll through the log view and
3764          * split open each commit diff. */
3765         scroll_view(view, REQ_SCROLL_LINE_DOWN);
3767         /* FIXME: A minor workaround. Scrolling the view will call report("")
3768          * but if we are scrolling a non-current view this won't properly
3769          * update the view title. */
3770         if (split)
3771                 update_view_title(view);
3773         return REQ_NONE;
3776 static bool
3777 pager_grep(struct view *view, struct line *line)
3779         const char *text[] = { line->data, NULL };
3781         return grep_text(view, text);
3784 static void
3785 pager_select(struct view *view, struct line *line)
3787         if (line->type == LINE_COMMIT) {
3788                 char *text = (char *)line->data + STRING_SIZE("commit ");
3790                 if (view != VIEW(REQ_VIEW_PAGER))
3791                         string_copy_rev(view->ref, text);
3792                 string_copy_rev(ref_commit, text);
3793         }
3796 static struct view_ops pager_ops = {
3797         "line",
3798         NULL,
3799         NULL,
3800         pager_read,
3801         pager_draw,
3802         pager_request,
3803         pager_grep,
3804         pager_select,
3805 };
3807 static const char *log_argv[SIZEOF_ARG] = {
3808         "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
3809 };
3811 static enum request
3812 log_request(struct view *view, enum request request, struct line *line)
3814         switch (request) {
3815         case REQ_REFRESH:
3816                 load_refs();
3817                 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
3818                 return REQ_NONE;
3819         default:
3820                 return pager_request(view, request, line);
3821         }
3824 static struct view_ops log_ops = {
3825         "line",
3826         log_argv,
3827         NULL,
3828         pager_read,
3829         pager_draw,
3830         log_request,
3831         pager_grep,
3832         pager_select,
3833 };
3835 static const char *diff_argv[SIZEOF_ARG] = {
3836         "git", "show", "--pretty=fuller", "--no-color", "--root",
3837                 "--patch-with-stat", "--find-copies-harder", "-C", "%(commit)", NULL
3838 };
3840 static struct view_ops diff_ops = {
3841         "line",
3842         diff_argv,
3843         NULL,
3844         pager_read,
3845         pager_draw,
3846         pager_request,
3847         pager_grep,
3848         pager_select,
3849 };
3851 /*
3852  * Help backend
3853  */
3855 static bool help_keymap_hidden[ARRAY_SIZE(keymap_table)];
3857 static char *
3858 help_name(char buf[SIZEOF_STR], const char *name, size_t namelen)
3860         int bufpos;
3862         for (bufpos = 0; bufpos <= namelen; bufpos++) {
3863                 buf[bufpos] = tolower(name[bufpos]);
3864                 if (buf[bufpos] == '_')
3865                         buf[bufpos] = '-';
3866         }
3868         buf[bufpos] = 0;
3869         return buf;
3872 #define help_keymap_name(buf, keymap) \
3873         help_name(buf, keymap_table[keymap].name, keymap_table[keymap].namelen)
3875 static bool
3876 help_open_keymap_title(struct view *view, enum keymap keymap)
3878         char buf[SIZEOF_STR];
3879         struct line *line;
3881         line = add_line_format(view, LINE_HELP_KEYMAP, "[%c] %s bindings",
3882                                help_keymap_hidden[keymap] ? '+' : '-',
3883                                help_keymap_name(buf, keymap));
3884         if (line)
3885                 line->other = keymap;
3887         return help_keymap_hidden[keymap];
3890 static void
3891 help_open_keymap(struct view *view, enum keymap keymap)
3893         const char *group = NULL;
3894         char buf[SIZEOF_STR];
3895         size_t bufpos;
3896         bool add_title = TRUE;
3897         int i;
3899         for (i = 0; i < ARRAY_SIZE(req_info); i++) {
3900                 const char *key = NULL;
3902                 if (req_info[i].request == REQ_NONE)
3903                         continue;
3905                 if (!req_info[i].request) {
3906                         group = req_info[i].help;
3907                         continue;
3908                 }
3910                 key = get_keys(keymap, req_info[i].request, TRUE);
3911                 if (!key || !*key)
3912                         continue;
3914                 if (add_title && help_open_keymap_title(view, keymap))
3915                         return;
3916                 add_title = false;
3918                 if (group) {
3919                         add_line_text(view, group, LINE_HELP_GROUP);
3920                         group = NULL;
3921                 }
3923                 add_line_format(view, LINE_DEFAULT, "    %-25s %-20s %s", key,
3924                                 help_name(buf, req_info[i].name, req_info[i].namelen),
3925                                 req_info[i].help);
3926         }
3928         group = "External commands:";
3930         for (i = 0; i < run_requests; i++) {
3931                 struct run_request *req = get_run_request(REQ_NONE + i + 1);
3932                 const char *key;
3933                 int argc;
3935                 if (!req || req->keymap != keymap)
3936                         continue;
3938                 key = get_key_name(req->key);
3939                 if (!*key)
3940                         key = "(no key defined)";
3942                 if (add_title && help_open_keymap_title(view, keymap))
3943                         return;
3944                 if (group) {
3945                         add_line_text(view, group, LINE_HELP_GROUP);
3946                         group = NULL;
3947                 }
3949                 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
3950                         if (!string_format_from(buf, &bufpos, "%s%s",
3951                                                 argc ? " " : "", req->argv[argc]))
3952                                 return;
3954                 add_line_format(view, LINE_DEFAULT, "    %-25s `%s`", key, buf);
3955         }
3958 static bool
3959 help_open(struct view *view)
3961         enum keymap keymap;
3963         reset_view(view);
3964         add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
3965         add_line_text(view, "", LINE_DEFAULT);
3967         for (keymap = 0; keymap < ARRAY_SIZE(keymap_table); keymap++)
3968                 help_open_keymap(view, keymap);
3970         return TRUE;
3973 static enum request
3974 help_request(struct view *view, enum request request, struct line *line)
3976         switch (request) {
3977         case REQ_ENTER:
3978                 if (line->type == LINE_HELP_KEYMAP) {
3979                         help_keymap_hidden[line->other] =
3980                                 !help_keymap_hidden[line->other];
3981                         view->p_restore = TRUE;
3982                         open_view(view, REQ_VIEW_HELP, OPEN_REFRESH);
3983                 }
3985                 return REQ_NONE;
3986         default:
3987                 return pager_request(view, request, line);
3988         }
3991 static struct view_ops help_ops = {
3992         "line",
3993         NULL,
3994         help_open,
3995         NULL,
3996         pager_draw,
3997         help_request,
3998         pager_grep,
3999         pager_select,
4000 };
4003 /*
4004  * Tree backend
4005  */
4007 struct tree_stack_entry {
4008         struct tree_stack_entry *prev;  /* Entry below this in the stack */
4009         unsigned long lineno;           /* Line number to restore */
4010         char *name;                     /* Position of name in opt_path */
4011 };
4013 /* The top of the path stack. */
4014 static struct tree_stack_entry *tree_stack = NULL;
4015 unsigned long tree_lineno = 0;
4017 static void
4018 pop_tree_stack_entry(void)
4020         struct tree_stack_entry *entry = tree_stack;
4022         tree_lineno = entry->lineno;
4023         entry->name[0] = 0;
4024         tree_stack = entry->prev;
4025         free(entry);
4028 static void
4029 push_tree_stack_entry(const char *name, unsigned long lineno)
4031         struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
4032         size_t pathlen = strlen(opt_path);
4034         if (!entry)
4035                 return;
4037         entry->prev = tree_stack;
4038         entry->name = opt_path + pathlen;
4039         tree_stack = entry;
4041         if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
4042                 pop_tree_stack_entry();
4043                 return;
4044         }
4046         /* Move the current line to the first tree entry. */
4047         tree_lineno = 1;
4048         entry->lineno = lineno;
4051 /* Parse output from git-ls-tree(1):
4052  *
4053  * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
4054  */
4056 #define SIZEOF_TREE_ATTR \
4057         STRING_SIZE("100644 blob f931e1d229c3e185caad4449bf5b66ed72462657\t")
4059 #define SIZEOF_TREE_MODE \
4060         STRING_SIZE("100644 ")
4062 #define TREE_ID_OFFSET \
4063         STRING_SIZE("100644 blob ")
4065 struct tree_entry {
4066         char id[SIZEOF_REV];
4067         mode_t mode;
4068         time_t time;                    /* Date from the author ident. */
4069         const char *author;             /* Author of the commit. */
4070         char name[1];
4071 };
4073 static const char *
4074 tree_path(const struct line *line)
4076         return ((struct tree_entry *) line->data)->name;
4079 static int
4080 tree_compare_entry(const struct line *line1, const struct line *line2)
4082         if (line1->type != line2->type)
4083                 return line1->type == LINE_TREE_DIR ? -1 : 1;
4084         return strcmp(tree_path(line1), tree_path(line2));
4087 static const enum sort_field tree_sort_fields[] = {
4088         ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
4089 };
4090 static struct sort_state tree_sort_state = SORT_STATE(tree_sort_fields);
4092 static int
4093 tree_compare(const void *l1, const void *l2)
4095         const struct line *line1 = (const struct line *) l1;
4096         const struct line *line2 = (const struct line *) l2;
4097         const struct tree_entry *entry1 = ((const struct line *) l1)->data;
4098         const struct tree_entry *entry2 = ((const struct line *) l2)->data;
4100         if (line1->type == LINE_TREE_HEAD)
4101                 return -1;
4102         if (line2->type == LINE_TREE_HEAD)
4103                 return 1;
4105         switch (get_sort_field(tree_sort_state)) {
4106         case ORDERBY_DATE:
4107                 return sort_order(tree_sort_state, entry1->time - entry2->time);
4109         case ORDERBY_AUTHOR:
4110                 return sort_order(tree_sort_state, strcmp(entry1->author, entry2->author));
4112         case ORDERBY_NAME:
4113         default:
4114                 return sort_order(tree_sort_state, tree_compare_entry(line1, line2));
4115         }
4119 static struct line *
4120 tree_entry(struct view *view, enum line_type type, const char *path,
4121            const char *mode, const char *id)
4123         struct tree_entry *entry = calloc(1, sizeof(*entry) + strlen(path));
4124         struct line *line = entry ? add_line_data(view, entry, type) : NULL;
4126         if (!entry || !line) {
4127                 free(entry);
4128                 return NULL;
4129         }
4131         strncpy(entry->name, path, strlen(path));
4132         if (mode)
4133                 entry->mode = strtoul(mode, NULL, 8);
4134         if (id)
4135                 string_copy_rev(entry->id, id);
4137         return line;
4140 static bool
4141 tree_read_date(struct view *view, char *text, bool *read_date)
4143         static const char *author_name;
4144         static time_t author_time;
4146         if (!text && *read_date) {
4147                 *read_date = FALSE;
4148                 return TRUE;
4150         } else if (!text) {
4151                 char *path = *opt_path ? opt_path : ".";
4152                 /* Find next entry to process */
4153                 const char *log_file[] = {
4154                         "git", "log", "--no-color", "--pretty=raw",
4155                                 "--cc", "--raw", view->id, "--", path, NULL
4156                 };
4157                 struct io io = {};
4159                 if (!view->lines) {
4160                         tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL);
4161                         report("Tree is empty");
4162                         return TRUE;
4163                 }
4165                 if (!run_io_rd(&io, log_file, FORMAT_NONE)) {
4166                         report("Failed to load tree data");
4167                         return TRUE;
4168                 }
4170                 done_io(view->pipe);
4171                 view->io = io;
4172                 *read_date = TRUE;
4173                 return FALSE;
4175         } else if (*text == 'a' && get_line_type(text) == LINE_AUTHOR) {
4176                 parse_author_line(text + STRING_SIZE("author "),
4177                                   &author_name, &author_time);
4179         } else if (*text == ':') {
4180                 char *pos;
4181                 size_t annotated = 1;
4182                 size_t i;
4184                 pos = strchr(text, '\t');
4185                 if (!pos)
4186                         return TRUE;
4187                 text = pos + 1;
4188                 if (*opt_prefix && !strncmp(text, opt_prefix, strlen(opt_prefix)))
4189                         text += strlen(opt_prefix);
4190                 if (*opt_path && !strncmp(text, opt_path, strlen(opt_path)))
4191                         text += strlen(opt_path);
4192                 pos = strchr(text, '/');
4193                 if (pos)
4194                         *pos = 0;
4196                 for (i = 1; i < view->lines; i++) {
4197                         struct line *line = &view->line[i];
4198                         struct tree_entry *entry = line->data;
4200                         annotated += !!entry->author;
4201                         if (entry->author || strcmp(entry->name, text))
4202                                 continue;
4204                         entry->author = author_name;
4205                         entry->time = author_time;
4206                         line->dirty = 1;
4207                         break;
4208                 }
4210                 if (annotated == view->lines)
4211                         kill_io(view->pipe);
4212         }
4213         return TRUE;
4216 static bool
4217 tree_read(struct view *view, char *text)
4219         static bool read_date = FALSE;
4220         struct tree_entry *data;
4221         struct line *entry, *line;
4222         enum line_type type;
4223         size_t textlen = text ? strlen(text) : 0;
4224         char *path = text + SIZEOF_TREE_ATTR;
4226         if (read_date || !text)
4227                 return tree_read_date(view, text, &read_date);
4229         if (textlen <= SIZEOF_TREE_ATTR)
4230                 return FALSE;
4231         if (view->lines == 0 &&
4232             !tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL))
4233                 return FALSE;
4235         /* Strip the path part ... */
4236         if (*opt_path) {
4237                 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
4238                 size_t striplen = strlen(opt_path);
4240                 if (pathlen > striplen)
4241                         memmove(path, path + striplen,
4242                                 pathlen - striplen + 1);
4244                 /* Insert "link" to parent directory. */
4245                 if (view->lines == 1 &&
4246                     !tree_entry(view, LINE_TREE_DIR, "..", "040000", view->ref))
4247                         return FALSE;
4248         }
4250         type = text[SIZEOF_TREE_MODE] == 't' ? LINE_TREE_DIR : LINE_TREE_FILE;
4251         entry = tree_entry(view, type, path, text, text + TREE_ID_OFFSET);
4252         if (!entry)
4253                 return FALSE;
4254         data = entry->data;
4256         /* Skip "Directory ..." and ".." line. */
4257         for (line = &view->line[1 + !!*opt_path]; line < entry; line++) {
4258                 if (tree_compare_entry(line, entry) <= 0)
4259                         continue;
4261                 memmove(line + 1, line, (entry - line) * sizeof(*entry));
4263                 line->data = data;
4264                 line->type = type;
4265                 for (; line <= entry; line++)
4266                         line->dirty = line->cleareol = 1;
4267                 return TRUE;
4268         }
4270         if (tree_lineno > view->lineno) {
4271                 view->lineno = tree_lineno;
4272                 tree_lineno = 0;
4273         }
4275         return TRUE;
4278 static bool
4279 tree_draw(struct view *view, struct line *line, unsigned int lineno)
4281         struct tree_entry *entry = line->data;
4283         if (line->type == LINE_TREE_HEAD) {
4284                 if (draw_text(view, line->type, "Directory path /", TRUE))
4285                         return TRUE;
4286         } else {
4287                 if (draw_mode(view, entry->mode))
4288                         return TRUE;
4290                 if (opt_author && draw_author(view, entry->author))
4291                         return TRUE;
4293                 if (opt_date && draw_date(view, entry->author ? &entry->time : NULL))
4294                         return TRUE;
4295         }
4296         if (draw_text(view, line->type, entry->name, TRUE))
4297                 return TRUE;
4298         return TRUE;
4301 static void
4302 open_blob_editor()
4304         char file[SIZEOF_STR] = "/tmp/tigblob.XXXXXX";
4305         int fd = mkstemp(file);
4307         if (fd == -1)
4308                 report("Failed to create temporary file");
4309         else if (!run_io_append(blob_ops.argv, FORMAT_ALL, fd))
4310                 report("Failed to save blob data to file");
4311         else
4312                 open_editor(FALSE, file);
4313         if (fd != -1)
4314                 unlink(file);
4317 static enum request
4318 tree_request(struct view *view, enum request request, struct line *line)
4320         enum open_flags flags;
4322         switch (request) {
4323         case REQ_VIEW_BLAME:
4324                 if (line->type != LINE_TREE_FILE) {
4325                         report("Blame only supported for files");
4326                         return REQ_NONE;
4327                 }
4329                 string_copy(opt_ref, view->vid);
4330                 return request;
4332         case REQ_EDIT:
4333                 if (line->type != LINE_TREE_FILE) {
4334                         report("Edit only supported for files");
4335                 } else if (!is_head_commit(view->vid)) {
4336                         open_blob_editor();
4337                 } else {
4338                         open_editor(TRUE, opt_file);
4339                 }
4340                 return REQ_NONE;
4342         case REQ_TOGGLE_SORT_FIELD:
4343         case REQ_TOGGLE_SORT_ORDER:
4344                 sort_view(view, request, &tree_sort_state, tree_compare);
4345                 return REQ_NONE;
4347         case REQ_PARENT:
4348                 if (!*opt_path) {
4349                         /* quit view if at top of tree */
4350                         return REQ_VIEW_CLOSE;
4351                 }
4352                 /* fake 'cd  ..' */
4353                 line = &view->line[1];
4354                 break;
4356         case REQ_ENTER:
4357                 break;
4359         default:
4360                 return request;
4361         }
4363         /* Cleanup the stack if the tree view is at a different tree. */
4364         while (!*opt_path && tree_stack)
4365                 pop_tree_stack_entry();
4367         switch (line->type) {
4368         case LINE_TREE_DIR:
4369                 /* Depending on whether it is a subdirectory or parent link
4370                  * mangle the path buffer. */
4371                 if (line == &view->line[1] && *opt_path) {
4372                         pop_tree_stack_entry();
4374                 } else {
4375                         const char *basename = tree_path(line);
4377                         push_tree_stack_entry(basename, view->lineno);
4378                 }
4380                 /* Trees and subtrees share the same ID, so they are not not
4381                  * unique like blobs. */
4382                 flags = OPEN_RELOAD;
4383                 request = REQ_VIEW_TREE;
4384                 break;
4386         case LINE_TREE_FILE:
4387                 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4388                 request = REQ_VIEW_BLOB;
4389                 break;
4391         default:
4392                 return REQ_NONE;
4393         }
4395         open_view(view, request, flags);
4396         if (request == REQ_VIEW_TREE)
4397                 view->lineno = tree_lineno;
4399         return REQ_NONE;
4402 static bool
4403 tree_grep(struct view *view, struct line *line)
4405         struct tree_entry *entry = line->data;
4406         const char *text[] = {
4407                 entry->name,
4408                 opt_author ? entry->author : "",
4409                 opt_date ? mkdate(&entry->time) : "",
4410                 NULL
4411         };
4413         return grep_text(view, text);
4416 static void
4417 tree_select(struct view *view, struct line *line)
4419         struct tree_entry *entry = line->data;
4421         if (line->type == LINE_TREE_FILE) {
4422                 string_copy_rev(ref_blob, entry->id);
4423                 string_format(opt_file, "%s%s", opt_path, tree_path(line));
4425         } else if (line->type != LINE_TREE_DIR) {
4426                 return;
4427         }
4429         string_copy_rev(view->ref, entry->id);
4432 static const char *tree_argv[SIZEOF_ARG] = {
4433         "git", "ls-tree", "%(commit)", "%(directory)", NULL
4434 };
4436 static struct view_ops tree_ops = {
4437         "file",
4438         tree_argv,
4439         NULL,
4440         tree_read,
4441         tree_draw,
4442         tree_request,
4443         tree_grep,
4444         tree_select,
4445 };
4447 static bool
4448 blob_read(struct view *view, char *line)
4450         if (!line)
4451                 return TRUE;
4452         return add_line_text(view, line, LINE_DEFAULT) != NULL;
4455 static enum request
4456 blob_request(struct view *view, enum request request, struct line *line)
4458         switch (request) {
4459         case REQ_EDIT:
4460                 open_blob_editor();
4461                 return REQ_NONE;
4462         default:
4463                 return pager_request(view, request, line);
4464         }
4467 static const char *blob_argv[SIZEOF_ARG] = {
4468         "git", "cat-file", "blob", "%(blob)", NULL
4469 };
4471 static struct view_ops blob_ops = {
4472         "line",
4473         blob_argv,
4474         NULL,
4475         blob_read,
4476         pager_draw,
4477         blob_request,
4478         pager_grep,
4479         pager_select,
4480 };
4482 /*
4483  * Blame backend
4484  *
4485  * Loading the blame view is a two phase job:
4486  *
4487  *  1. File content is read either using opt_file from the
4488  *     filesystem or using git-cat-file.
4489  *  2. Then blame information is incrementally added by
4490  *     reading output from git-blame.
4491  */
4493 static const char *blame_head_argv[] = {
4494         "git", "blame", "--incremental", "--", "%(file)", NULL
4495 };
4497 static const char *blame_ref_argv[] = {
4498         "git", "blame", "--incremental", "%(ref)", "--", "%(file)", NULL
4499 };
4501 static const char *blame_cat_file_argv[] = {
4502         "git", "cat-file", "blob", "%(ref):%(file)", NULL
4503 };
4505 struct blame_commit {
4506         char id[SIZEOF_REV];            /* SHA1 ID. */
4507         char title[128];                /* First line of the commit message. */
4508         const char *author;             /* Author of the commit. */
4509         time_t time;                    /* Date from the author ident. */
4510         char filename[128];             /* Name of file. */
4511         bool has_previous;              /* Was a "previous" line detected. */
4512 };
4514 struct blame {
4515         struct blame_commit *commit;
4516         unsigned long lineno;
4517         char text[1];
4518 };
4520 static bool
4521 blame_open(struct view *view)
4523         if (*opt_ref || !io_open(&view->io, opt_file)) {
4524                 if (!run_io_rd(&view->io, blame_cat_file_argv, FORMAT_ALL))
4525                         return FALSE;
4526         }
4528         setup_update(view, opt_file);
4529         string_format(view->ref, "%s ...", opt_file);
4531         return TRUE;
4534 static struct blame_commit *
4535 get_blame_commit(struct view *view, const char *id)
4537         size_t i;
4539         for (i = 0; i < view->lines; i++) {
4540                 struct blame *blame = view->line[i].data;
4542                 if (!blame->commit)
4543                         continue;
4545                 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
4546                         return blame->commit;
4547         }
4549         {
4550                 struct blame_commit *commit = calloc(1, sizeof(*commit));
4552                 if (commit)
4553                         string_ncopy(commit->id, id, SIZEOF_REV);
4554                 return commit;
4555         }
4558 static bool
4559 parse_number(const char **posref, size_t *number, size_t min, size_t max)
4561         const char *pos = *posref;
4563         *posref = NULL;
4564         pos = strchr(pos + 1, ' ');
4565         if (!pos || !isdigit(pos[1]))
4566                 return FALSE;
4567         *number = atoi(pos + 1);
4568         if (*number < min || *number > max)
4569                 return FALSE;
4571         *posref = pos;
4572         return TRUE;
4575 static struct blame_commit *
4576 parse_blame_commit(struct view *view, const char *text, int *blamed)
4578         struct blame_commit *commit;
4579         struct blame *blame;
4580         const char *pos = text + SIZEOF_REV - 2;
4581         size_t orig_lineno = 0;
4582         size_t lineno;
4583         size_t group;
4585         if (strlen(text) <= SIZEOF_REV || pos[1] != ' ')
4586                 return NULL;
4588         if (!parse_number(&pos, &orig_lineno, 1, 9999999) ||
4589             !parse_number(&pos, &lineno, 1, view->lines) ||
4590             !parse_number(&pos, &group, 1, view->lines - lineno + 1))
4591                 return NULL;
4593         commit = get_blame_commit(view, text);
4594         if (!commit)
4595                 return NULL;
4597         *blamed += group;
4598         while (group--) {
4599                 struct line *line = &view->line[lineno + group - 1];
4601                 blame = line->data;
4602                 blame->commit = commit;
4603                 blame->lineno = orig_lineno + group - 1;
4604                 line->dirty = 1;
4605         }
4607         return commit;
4610 static bool
4611 blame_read_file(struct view *view, const char *line, bool *read_file)
4613         if (!line) {
4614                 const char **argv = *opt_ref ? blame_ref_argv : blame_head_argv;
4615                 struct io io = {};
4617                 if (view->lines == 0 && !view->parent)
4618                         die("No blame exist for %s", view->vid);
4620                 if (view->lines == 0 || !run_io_rd(&io, argv, FORMAT_ALL)) {
4621                         report("Failed to load blame data");
4622                         return TRUE;
4623                 }
4625                 done_io(view->pipe);
4626                 view->io = io;
4627                 *read_file = FALSE;
4628                 return FALSE;
4630         } else {
4631                 size_t linelen = strlen(line);
4632                 struct blame *blame = malloc(sizeof(*blame) + linelen);
4634                 if (!blame)
4635                         return FALSE;
4637                 blame->commit = NULL;
4638                 strncpy(blame->text, line, linelen);
4639                 blame->text[linelen] = 0;
4640                 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
4641         }
4644 static bool
4645 match_blame_header(const char *name, char **line)
4647         size_t namelen = strlen(name);
4648         bool matched = !strncmp(name, *line, namelen);
4650         if (matched)
4651                 *line += namelen;
4653         return matched;
4656 static bool
4657 blame_read(struct view *view, char *line)
4659         static struct blame_commit *commit = NULL;
4660         static int blamed = 0;
4661         static bool read_file = TRUE;
4663         if (read_file)
4664                 return blame_read_file(view, line, &read_file);
4666         if (!line) {
4667                 /* Reset all! */
4668                 commit = NULL;
4669                 blamed = 0;
4670                 read_file = TRUE;
4671                 string_format(view->ref, "%s", view->vid);
4672                 if (view_is_displayed(view)) {
4673                         update_view_title(view);
4674                         redraw_view_from(view, 0);
4675                 }
4676                 return TRUE;
4677         }
4679         if (!commit) {
4680                 commit = parse_blame_commit(view, line, &blamed);
4681                 string_format(view->ref, "%s %2d%%", view->vid,
4682                               view->lines ? blamed * 100 / view->lines : 0);
4684         } else if (match_blame_header("author ", &line)) {
4685                 commit->author = get_author(line);
4687         } else if (match_blame_header("author-time ", &line)) {
4688                 commit->time = (time_t) atol(line);
4690         } else if (match_blame_header("author-tz ", &line)) {
4691                 parse_timezone(&commit->time, line);
4693         } else if (match_blame_header("summary ", &line)) {
4694                 string_ncopy(commit->title, line, strlen(line));
4696         } else if (match_blame_header("previous ", &line)) {
4697                 commit->has_previous = TRUE;
4699         } else if (match_blame_header("filename ", &line)) {
4700                 string_ncopy(commit->filename, line, strlen(line));
4701                 commit = NULL;
4702         }
4704         return TRUE;
4707 static bool
4708 blame_draw(struct view *view, struct line *line, unsigned int lineno)
4710         struct blame *blame = line->data;
4711         time_t *time = NULL;
4712         const char *id = NULL, *author = NULL;
4713         char text[SIZEOF_STR];
4715         if (blame->commit && *blame->commit->filename) {
4716                 id = blame->commit->id;
4717                 author = blame->commit->author;
4718                 time = &blame->commit->time;
4719         }
4721         if (opt_date && draw_date(view, time))
4722                 return TRUE;
4724         if (opt_author && draw_author(view, author))
4725                 return TRUE;
4727         if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
4728                 return TRUE;
4730         if (draw_lineno(view, lineno))
4731                 return TRUE;
4733         string_expand(text, sizeof(text), blame->text, opt_tab_size);
4734         draw_text(view, LINE_DEFAULT, text, TRUE);
4735         return TRUE;
4738 static bool
4739 check_blame_commit(struct blame *blame, bool check_null_id)
4741         if (!blame->commit)
4742                 report("Commit data not loaded yet");
4743         else if (check_null_id && !strcmp(blame->commit->id, NULL_ID))
4744                 report("No commit exist for the selected line");
4745         else
4746                 return TRUE;
4747         return FALSE;
4750 static void
4751 setup_blame_parent_line(struct view *view, struct blame *blame)
4753         const char *diff_tree_argv[] = {
4754                 "git", "diff-tree", "-U0", blame->commit->id,
4755                         "--", blame->commit->filename, NULL
4756         };
4757         struct io io = {};
4758         int parent_lineno = -1;
4759         int blamed_lineno = -1;
4760         char *line;
4762         if (!run_io(&io, diff_tree_argv, NULL, IO_RD))
4763                 return;
4765         while ((line = io_get(&io, '\n', TRUE))) {
4766                 if (*line == '@') {
4767                         char *pos = strchr(line, '+');
4769                         parent_lineno = atoi(line + 4);
4770                         if (pos)
4771                                 blamed_lineno = atoi(pos + 1);
4773                 } else if (*line == '+' && parent_lineno != -1) {
4774                         if (blame->lineno == blamed_lineno - 1 &&
4775                             !strcmp(blame->text, line + 1)) {
4776                                 view->lineno = parent_lineno ? parent_lineno - 1 : 0;
4777                                 break;
4778                         }
4779                         blamed_lineno++;
4780                 }
4781         }
4783         done_io(&io);
4786 static enum request
4787 blame_request(struct view *view, enum request request, struct line *line)
4789         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4790         struct blame *blame = line->data;
4792         switch (request) {
4793         case REQ_VIEW_BLAME:
4794                 if (check_blame_commit(blame, TRUE)) {
4795                         string_copy(opt_ref, blame->commit->id);
4796                         string_copy(opt_file, blame->commit->filename);
4797                         if (blame->lineno)
4798                                 view->lineno = blame->lineno;
4799                         open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4800                 }
4801                 break;
4803         case REQ_PARENT:
4804                 if (check_blame_commit(blame, TRUE) &&
4805                     select_commit_parent(blame->commit->id, opt_ref,
4806                                          blame->commit->filename)) {
4807                         string_copy(opt_file, blame->commit->filename);
4808                         setup_blame_parent_line(view, blame);
4809                         open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4810                 }
4811                 break;
4813         case REQ_ENTER:
4814                 if (!check_blame_commit(blame, FALSE))
4815                         break;
4817                 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
4818                     !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
4819                         break;
4821                 if (!strcmp(blame->commit->id, NULL_ID)) {
4822                         struct view *diff = VIEW(REQ_VIEW_DIFF);
4823                         const char *diff_index_argv[] = {
4824                                 "git", "diff-index", "--root", "--patch-with-stat",
4825                                         "-C", "-M", "HEAD", "--", view->vid, NULL
4826                         };
4828                         if (!blame->commit->has_previous) {
4829                                 diff_index_argv[1] = "diff";
4830                                 diff_index_argv[2] = "--no-color";
4831                                 diff_index_argv[6] = "--";
4832                                 diff_index_argv[7] = "/dev/null";
4833                         }
4835                         if (!prepare_update(diff, diff_index_argv, NULL, FORMAT_DASH)) {
4836                                 report("Failed to allocate diff command");
4837                                 break;
4838                         }
4839                         flags |= OPEN_PREPARED;
4840                 }
4842                 open_view(view, REQ_VIEW_DIFF, flags);
4843                 if (VIEW(REQ_VIEW_DIFF)->pipe && !strcmp(blame->commit->id, NULL_ID))
4844                         string_copy_rev(VIEW(REQ_VIEW_DIFF)->ref, NULL_ID);
4845                 break;
4847         default:
4848                 return request;
4849         }
4851         return REQ_NONE;
4854 static bool
4855 blame_grep(struct view *view, struct line *line)
4857         struct blame *blame = line->data;
4858         struct blame_commit *commit = blame->commit;
4859         const char *text[] = {
4860                 blame->text,
4861                 commit ? commit->title : "",
4862                 commit ? commit->id : "",
4863                 commit && opt_author ? commit->author : "",
4864                 commit && opt_date ? mkdate(&commit->time) : "",
4865                 NULL
4866         };
4868         return grep_text(view, text);
4871 static void
4872 blame_select(struct view *view, struct line *line)
4874         struct blame *blame = line->data;
4875         struct blame_commit *commit = blame->commit;
4877         if (!commit)
4878                 return;
4880         if (!strcmp(commit->id, NULL_ID))
4881                 string_ncopy(ref_commit, "HEAD", 4);
4882         else
4883                 string_copy_rev(ref_commit, commit->id);
4886 static struct view_ops blame_ops = {
4887         "line",
4888         NULL,
4889         blame_open,
4890         blame_read,
4891         blame_draw,
4892         blame_request,
4893         blame_grep,
4894         blame_select,
4895 };
4897 /*
4898  * Branch backend
4899  */
4901 struct branch {
4902         const char *author;             /* Author of the last commit. */
4903         time_t time;                    /* Date of the last activity. */
4904         struct ref *ref;                /* Name and commit ID information. */
4905 };
4907 static const enum sort_field branch_sort_fields[] = {
4908         ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
4909 };
4910 static struct sort_state branch_sort_state = SORT_STATE(branch_sort_fields);
4912 static int
4913 branch_compare(const void *l1, const void *l2)
4915         const struct branch *branch1 = ((const struct line *) l1)->data;
4916         const struct branch *branch2 = ((const struct line *) l2)->data;
4918         switch (get_sort_field(branch_sort_state)) {
4919         case ORDERBY_DATE:
4920                 return sort_order(branch_sort_state, branch1->time - branch2->time);
4922         case ORDERBY_AUTHOR:
4923                 return sort_order(branch_sort_state, strcmp(branch1->author, branch2->author));
4925         case ORDERBY_NAME:
4926         default:
4927                 return sort_order(branch_sort_state, strcmp(branch1->ref->name, branch2->ref->name));
4928         }
4931 static bool
4932 branch_draw(struct view *view, struct line *line, unsigned int lineno)
4934         struct branch *branch = line->data;
4935         enum line_type type = branch->ref->head ? LINE_MAIN_HEAD : LINE_DEFAULT;
4937         if (opt_date && draw_date(view, branch->author ? &branch->time : NULL))
4938                 return TRUE;
4940         if (opt_author && draw_author(view, branch->author))
4941                 return TRUE;
4943         draw_text(view, type, branch->ref->name, TRUE);
4944         return TRUE;
4947 static enum request
4948 branch_request(struct view *view, enum request request, struct line *line)
4950         switch (request) {
4951         case REQ_REFRESH:
4952                 load_refs();
4953                 open_view(view, REQ_VIEW_BRANCH, OPEN_REFRESH);
4954                 return REQ_NONE;
4956         case REQ_TOGGLE_SORT_FIELD:
4957         case REQ_TOGGLE_SORT_ORDER:
4958                 sort_view(view, request, &branch_sort_state, branch_compare);
4959                 return REQ_NONE;
4961         case REQ_ENTER:
4962                 open_view(view, REQ_VIEW_MAIN, OPEN_SPLIT);
4963                 return REQ_NONE;
4965         default:
4966                 return request;
4967         }
4970 static bool
4971 branch_read(struct view *view, char *line)
4973         static char id[SIZEOF_REV];
4974         struct branch *reference;
4975         size_t i;
4977         if (!line)
4978                 return TRUE;
4980         switch (get_line_type(line)) {
4981         case LINE_COMMIT:
4982                 string_copy_rev(id, line + STRING_SIZE("commit "));
4983                 return TRUE;
4985         case LINE_AUTHOR:
4986                 for (i = 0, reference = NULL; i < view->lines; i++) {
4987                         struct branch *branch = view->line[i].data;
4989                         if (strcmp(branch->ref->id, id))
4990                                 continue;
4992                         view->line[i].dirty = TRUE;
4993                         if (reference) {
4994                                 branch->author = reference->author;
4995                                 branch->time = reference->time;
4996                                 continue;
4997                         }
4999                         parse_author_line(line + STRING_SIZE("author "),
5000                                           &branch->author, &branch->time);
5001                         reference = branch;
5002                 }
5003                 return TRUE;
5005         default:
5006                 return TRUE;
5007         }
5011 static bool
5012 branch_open_visitor(void *data, struct ref *ref)
5014         struct view *view = data;
5015         struct branch *branch;
5017         if (ref->tag || ref->ltag || ref->remote)
5018                 return TRUE;
5020         branch = calloc(1, sizeof(*branch));
5021         if (!branch)
5022                 return FALSE;
5024         branch->ref = ref;
5025         return !!add_line_data(view, branch, LINE_DEFAULT);
5028 static bool
5029 branch_open(struct view *view)
5031         const char *branch_log[] = {
5032                 "git", "log", "--no-color", "--pretty=raw",
5033                         "--simplify-by-decoration", "--all", NULL
5034         };
5036         if (!run_io_rd(&view->io, branch_log, FORMAT_NONE)) {
5037                 report("Failed to load branch data");
5038                 return TRUE;
5039         }
5041         setup_update(view, view->id);
5042         foreach_ref(branch_open_visitor, view);
5043         view->p_restore = TRUE;
5045         return TRUE;
5048 static bool
5049 branch_grep(struct view *view, struct line *line)
5051         struct branch *branch = line->data;
5052         const char *text[] = {
5053                 branch->ref->name,
5054                 branch->author,
5055                 NULL
5056         };
5058         return grep_text(view, text);
5061 static void
5062 branch_select(struct view *view, struct line *line)
5064         struct branch *branch = line->data;
5066         string_copy_rev(view->ref, branch->ref->id);
5067         string_copy_rev(ref_commit, branch->ref->id);
5068         string_copy_rev(ref_head, branch->ref->id);
5071 static struct view_ops branch_ops = {
5072         "branch",
5073         NULL,
5074         branch_open,
5075         branch_read,
5076         branch_draw,
5077         branch_request,
5078         branch_grep,
5079         branch_select,
5080 };
5082 /*
5083  * Status backend
5084  */
5086 struct status {
5087         char status;
5088         struct {
5089                 mode_t mode;
5090                 char rev[SIZEOF_REV];
5091                 char name[SIZEOF_STR];
5092         } old;
5093         struct {
5094                 mode_t mode;
5095                 char rev[SIZEOF_REV];
5096                 char name[SIZEOF_STR];
5097         } new;
5098 };
5100 static char status_onbranch[SIZEOF_STR];
5101 static struct status stage_status;
5102 static enum line_type stage_line_type;
5103 static size_t stage_chunks;
5104 static int *stage_chunk;
5106 DEFINE_ALLOCATOR(realloc_ints, int, 32)
5108 /* This should work even for the "On branch" line. */
5109 static inline bool
5110 status_has_none(struct view *view, struct line *line)
5112         return line < view->line + view->lines && !line[1].data;
5115 /* Get fields from the diff line:
5116  * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
5117  */
5118 static inline bool
5119 status_get_diff(struct status *file, const char *buf, size_t bufsize)
5121         const char *old_mode = buf +  1;
5122         const char *new_mode = buf +  8;
5123         const char *old_rev  = buf + 15;
5124         const char *new_rev  = buf + 56;
5125         const char *status   = buf + 97;
5127         if (bufsize < 98 ||
5128             old_mode[-1] != ':' ||
5129             new_mode[-1] != ' ' ||
5130             old_rev[-1]  != ' ' ||
5131             new_rev[-1]  != ' ' ||
5132             status[-1]   != ' ')
5133                 return FALSE;
5135         file->status = *status;
5137         string_copy_rev(file->old.rev, old_rev);
5138         string_copy_rev(file->new.rev, new_rev);
5140         file->old.mode = strtoul(old_mode, NULL, 8);
5141         file->new.mode = strtoul(new_mode, NULL, 8);
5143         file->old.name[0] = file->new.name[0] = 0;
5145         return TRUE;
5148 static bool
5149 status_run(struct view *view, const char *argv[], char status, enum line_type type)
5151         struct status *unmerged = NULL;
5152         char *buf;
5153         struct io io = {};
5155         if (!run_io(&io, argv, NULL, IO_RD))
5156                 return FALSE;
5158         add_line_data(view, NULL, type);
5160         while ((buf = io_get(&io, 0, TRUE))) {
5161                 struct status *file = unmerged;
5163                 if (!file) {
5164                         file = calloc(1, sizeof(*file));
5165                         if (!file || !add_line_data(view, file, type))
5166                                 goto error_out;
5167                 }
5169                 /* Parse diff info part. */
5170                 if (status) {
5171                         file->status = status;
5172                         if (status == 'A')
5173                                 string_copy(file->old.rev, NULL_ID);
5175                 } else if (!file->status || file == unmerged) {
5176                         if (!status_get_diff(file, buf, strlen(buf)))
5177                                 goto error_out;
5179                         buf = io_get(&io, 0, TRUE);
5180                         if (!buf)
5181                                 break;
5183                         /* Collapse all modified entries that follow an
5184                          * associated unmerged entry. */
5185                         if (unmerged == file) {
5186                                 unmerged->status = 'U';
5187                                 unmerged = NULL;
5188                         } else if (file->status == 'U') {
5189                                 unmerged = file;
5190                         }
5191                 }
5193                 /* Grab the old name for rename/copy. */
5194                 if (!*file->old.name &&
5195                     (file->status == 'R' || file->status == 'C')) {
5196                         string_ncopy(file->old.name, buf, strlen(buf));
5198                         buf = io_get(&io, 0, TRUE);
5199                         if (!buf)
5200                                 break;
5201                 }
5203                 /* git-ls-files just delivers a NUL separated list of
5204                  * file names similar to the second half of the
5205                  * git-diff-* output. */
5206                 string_ncopy(file->new.name, buf, strlen(buf));
5207                 if (!*file->old.name)
5208                         string_copy(file->old.name, file->new.name);
5209                 file = NULL;
5210         }
5212         if (io_error(&io)) {
5213 error_out:
5214                 done_io(&io);
5215                 return FALSE;
5216         }
5218         if (!view->line[view->lines - 1].data)
5219                 add_line_data(view, NULL, LINE_STAT_NONE);
5221         done_io(&io);
5222         return TRUE;
5225 /* Don't show unmerged entries in the staged section. */
5226 static const char *status_diff_index_argv[] = {
5227         "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
5228                              "--cached", "-M", "HEAD", NULL
5229 };
5231 static const char *status_diff_files_argv[] = {
5232         "git", "diff-files", "-z", NULL
5233 };
5235 static const char *status_list_other_argv[] = {
5236         "git", "ls-files", "-z", "--others", "--exclude-standard", NULL
5237 };
5239 static const char *status_list_no_head_argv[] = {
5240         "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
5241 };
5243 static const char *update_index_argv[] = {
5244         "git", "update-index", "-q", "--unmerged", "--refresh", NULL
5245 };
5247 /* Restore the previous line number to stay in the context or select a
5248  * line with something that can be updated. */
5249 static void
5250 status_restore(struct view *view)
5252         if (view->p_lineno >= view->lines)
5253                 view->p_lineno = view->lines - 1;
5254         while (view->p_lineno < view->lines && !view->line[view->p_lineno].data)
5255                 view->p_lineno++;
5256         while (view->p_lineno > 0 && !view->line[view->p_lineno].data)
5257                 view->p_lineno--;
5259         /* If the above fails, always skip the "On branch" line. */
5260         if (view->p_lineno < view->lines)
5261                 view->lineno = view->p_lineno;
5262         else
5263                 view->lineno = 1;
5265         if (view->lineno < view->offset)
5266                 view->offset = view->lineno;
5267         else if (view->offset + view->height <= view->lineno)
5268                 view->offset = view->lineno - view->height + 1;
5270         view->p_restore = FALSE;
5273 static void
5274 status_update_onbranch(void)
5276         static const char *paths[][2] = {
5277                 { "rebase-apply/rebasing",      "Rebasing" },
5278                 { "rebase-apply/applying",      "Applying mailbox" },
5279                 { "rebase-apply/",              "Rebasing mailbox" },
5280                 { "rebase-merge/interactive",   "Interactive rebase" },
5281                 { "rebase-merge/",              "Rebase merge" },
5282                 { "MERGE_HEAD",                 "Merging" },
5283                 { "BISECT_LOG",                 "Bisecting" },
5284                 { "HEAD",                       "On branch" },
5285         };
5286         char buf[SIZEOF_STR];
5287         struct stat stat;
5288         int i;
5290         if (is_initial_commit()) {
5291                 string_copy(status_onbranch, "Initial commit");
5292                 return;
5293         }
5295         for (i = 0; i < ARRAY_SIZE(paths); i++) {
5296                 char *head = opt_head;
5298                 if (!string_format(buf, "%s/%s", opt_git_dir, paths[i][0]) ||
5299                     lstat(buf, &stat) < 0)
5300                         continue;
5302                 if (!*opt_head) {
5303                         struct io io = {};
5305                         if (string_format(buf, "%s/rebase-merge/head-name", opt_git_dir) &&
5306                             io_open(&io, buf) &&
5307                             io_read_buf(&io, buf, sizeof(buf))) {
5308                                 head = buf;
5309                                 if (!prefixcmp(head, "refs/heads/"))
5310                                         head += STRING_SIZE("refs/heads/");
5311                         }
5312                 }
5314                 if (!string_format(status_onbranch, "%s %s", paths[i][1], head))
5315                         string_copy(status_onbranch, opt_head);
5316                 return;
5317         }
5319         string_copy(status_onbranch, "Not currently on any branch");
5322 /* First parse staged info using git-diff-index(1), then parse unstaged
5323  * info using git-diff-files(1), and finally untracked files using
5324  * git-ls-files(1). */
5325 static bool
5326 status_open(struct view *view)
5328         reset_view(view);
5330         add_line_data(view, NULL, LINE_STAT_HEAD);
5331         status_update_onbranch();
5333         run_io_bg(update_index_argv);
5335         if (is_initial_commit()) {
5336                 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
5337                         return FALSE;
5338         } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
5339                 return FALSE;
5340         }
5342         if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
5343             !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
5344                 return FALSE;
5346         /* Restore the exact position or use the specialized restore
5347          * mode? */
5348         if (!view->p_restore)
5349                 status_restore(view);
5350         return TRUE;
5353 static bool
5354 status_draw(struct view *view, struct line *line, unsigned int lineno)
5356         struct status *status = line->data;
5357         enum line_type type;
5358         const char *text;
5360         if (!status) {
5361                 switch (line->type) {
5362                 case LINE_STAT_STAGED:
5363                         type = LINE_STAT_SECTION;
5364                         text = "Changes to be committed:";
5365                         break;
5367                 case LINE_STAT_UNSTAGED:
5368                         type = LINE_STAT_SECTION;
5369                         text = "Changed but not updated:";
5370                         break;
5372                 case LINE_STAT_UNTRACKED:
5373                         type = LINE_STAT_SECTION;
5374                         text = "Untracked files:";
5375                         break;
5377                 case LINE_STAT_NONE:
5378                         type = LINE_DEFAULT;
5379                         text = "  (no files)";
5380                         break;
5382                 case LINE_STAT_HEAD:
5383                         type = LINE_STAT_HEAD;
5384                         text = status_onbranch;
5385                         break;
5387                 default:
5388                         return FALSE;
5389                 }
5390         } else {
5391                 static char buf[] = { '?', ' ', ' ', ' ', 0 };
5393                 buf[0] = status->status;
5394                 if (draw_text(view, line->type, buf, TRUE))
5395                         return TRUE;
5396                 type = LINE_DEFAULT;
5397                 text = status->new.name;
5398         }
5400         draw_text(view, type, text, TRUE);
5401         return TRUE;
5404 static enum request
5405 status_load_error(struct view *view, struct view *stage, const char *path)
5407         if (displayed_views() == 2 || display[current_view] != view)
5408                 maximize_view(view);
5409         report("Failed to load '%s': %s", path, io_strerror(&stage->io));
5410         return REQ_NONE;
5413 static enum request
5414 status_enter(struct view *view, struct line *line)
5416         struct status *status = line->data;
5417         const char *oldpath = status ? status->old.name : NULL;
5418         /* Diffs for unmerged entries are empty when passing the new
5419          * path, so leave it empty. */
5420         const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
5421         const char *info;
5422         enum open_flags split;
5423         struct view *stage = VIEW(REQ_VIEW_STAGE);
5425         if (line->type == LINE_STAT_NONE ||
5426             (!status && line[1].type == LINE_STAT_NONE)) {
5427                 report("No file to diff");
5428                 return REQ_NONE;
5429         }
5431         switch (line->type) {
5432         case LINE_STAT_STAGED:
5433                 if (is_initial_commit()) {
5434                         const char *no_head_diff_argv[] = {
5435                                 "git", "diff", "--no-color", "--patch-with-stat",
5436                                         "--", "/dev/null", newpath, NULL
5437                         };
5439                         if (!prepare_update(stage, no_head_diff_argv, opt_cdup, FORMAT_DASH))
5440                                 return status_load_error(view, stage, newpath);
5441                 } else {
5442                         const char *index_show_argv[] = {
5443                                 "git", "diff-index", "--root", "--patch-with-stat",
5444                                         "-C", "-M", "--cached", "HEAD", "--",
5445                                         oldpath, newpath, NULL
5446                         };
5448                         if (!prepare_update(stage, index_show_argv, opt_cdup, FORMAT_DASH))
5449                                 return status_load_error(view, stage, newpath);
5450                 }
5452                 if (status)
5453                         info = "Staged changes to %s";
5454                 else
5455                         info = "Staged changes";
5456                 break;
5458         case LINE_STAT_UNSTAGED:
5459         {
5460                 const char *files_show_argv[] = {
5461                         "git", "diff-files", "--root", "--patch-with-stat",
5462                                 "-C", "-M", "--", oldpath, newpath, NULL
5463                 };
5465                 if (!prepare_update(stage, files_show_argv, opt_cdup, FORMAT_DASH))
5466                         return status_load_error(view, stage, newpath);
5467                 if (status)
5468                         info = "Unstaged changes to %s";
5469                 else
5470                         info = "Unstaged changes";
5471                 break;
5472         }
5473         case LINE_STAT_UNTRACKED:
5474                 if (!newpath) {
5475                         report("No file to show");
5476                         return REQ_NONE;
5477                 }
5479                 if (!suffixcmp(status->new.name, -1, "/")) {
5480                         report("Cannot display a directory");
5481                         return REQ_NONE;
5482                 }
5484                 if (!prepare_update_file(stage, newpath))
5485                         return status_load_error(view, stage, newpath);
5486                 info = "Untracked file %s";
5487                 break;
5489         case LINE_STAT_HEAD:
5490                 return REQ_NONE;
5492         default:
5493                 die("line type %d not handled in switch", line->type);
5494         }
5496         split = view_is_displayed(view) ? OPEN_SPLIT : 0;
5497         open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split);
5498         if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
5499                 if (status) {
5500                         stage_status = *status;
5501                 } else {
5502                         memset(&stage_status, 0, sizeof(stage_status));
5503                 }
5505                 stage_line_type = line->type;
5506                 stage_chunks = 0;
5507                 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
5508         }
5510         return REQ_NONE;
5513 static bool
5514 status_exists(struct status *status, enum line_type type)
5516         struct view *view = VIEW(REQ_VIEW_STATUS);
5517         unsigned long lineno;
5519         for (lineno = 0; lineno < view->lines; lineno++) {
5520                 struct line *line = &view->line[lineno];
5521                 struct status *pos = line->data;
5523                 if (line->type != type)
5524                         continue;
5525                 if (!pos && (!status || !status->status) && line[1].data) {
5526                         select_view_line(view, lineno);
5527                         return TRUE;
5528                 }
5529                 if (pos && !strcmp(status->new.name, pos->new.name)) {
5530                         select_view_line(view, lineno);
5531                         return TRUE;
5532                 }
5533         }
5535         return FALSE;
5539 static bool
5540 status_update_prepare(struct io *io, enum line_type type)
5542         const char *staged_argv[] = {
5543                 "git", "update-index", "-z", "--index-info", NULL
5544         };
5545         const char *others_argv[] = {
5546                 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
5547         };
5549         switch (type) {
5550         case LINE_STAT_STAGED:
5551                 return run_io(io, staged_argv, opt_cdup, IO_WR);
5553         case LINE_STAT_UNSTAGED:
5554                 return run_io(io, others_argv, opt_cdup, IO_WR);
5556         case LINE_STAT_UNTRACKED:
5557                 return run_io(io, others_argv, NULL, IO_WR);
5559         default:
5560                 die("line type %d not handled in switch", type);
5561                 return FALSE;
5562         }
5565 static bool
5566 status_update_write(struct io *io, struct status *status, enum line_type type)
5568         char buf[SIZEOF_STR];
5569         size_t bufsize = 0;
5571         switch (type) {
5572         case LINE_STAT_STAGED:
5573                 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
5574                                         status->old.mode,
5575                                         status->old.rev,
5576                                         status->old.name, 0))
5577                         return FALSE;
5578                 break;
5580         case LINE_STAT_UNSTAGED:
5581         case LINE_STAT_UNTRACKED:
5582                 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
5583                         return FALSE;
5584                 break;
5586         default:
5587                 die("line type %d not handled in switch", type);
5588         }
5590         return io_write(io, buf, bufsize);
5593 static bool
5594 status_update_file(struct status *status, enum line_type type)
5596         struct io io = {};
5597         bool result;
5599         if (!status_update_prepare(&io, type))
5600                 return FALSE;
5602         result = status_update_write(&io, status, type);
5603         return done_io(&io) && result;
5606 static bool
5607 status_update_files(struct view *view, struct line *line)
5609         char buf[sizeof(view->ref)];
5610         struct io io = {};
5611         bool result = TRUE;
5612         struct line *pos = view->line + view->lines;
5613         int files = 0;
5614         int file, done;
5615         int cursor_y = -1, cursor_x = -1;
5617         if (!status_update_prepare(&io, line->type))
5618                 return FALSE;
5620         for (pos = line; pos < view->line + view->lines && pos->data; pos++)
5621                 files++;
5623         string_copy(buf, view->ref);
5624         getsyx(cursor_y, cursor_x);
5625         for (file = 0, done = 5; result && file < files; line++, file++) {
5626                 int almost_done = file * 100 / files;
5628                 if (almost_done > done) {
5629                         done = almost_done;
5630                         string_format(view->ref, "updating file %u of %u (%d%% done)",
5631                                       file, files, done);
5632                         update_view_title(view);
5633                         setsyx(cursor_y, cursor_x);
5634                         doupdate();
5635                 }
5636                 result = status_update_write(&io, line->data, line->type);
5637         }
5638         string_copy(view->ref, buf);
5640         return done_io(&io) && result;
5643 static bool
5644 status_update(struct view *view)
5646         struct line *line = &view->line[view->lineno];
5648         assert(view->lines);
5650         if (!line->data) {
5651                 /* This should work even for the "On branch" line. */
5652                 if (line < view->line + view->lines && !line[1].data) {
5653                         report("Nothing to update");
5654                         return FALSE;
5655                 }
5657                 if (!status_update_files(view, line + 1)) {
5658                         report("Failed to update file status");
5659                         return FALSE;
5660                 }
5662         } else if (!status_update_file(line->data, line->type)) {
5663                 report("Failed to update file status");
5664                 return FALSE;
5665         }
5667         return TRUE;
5670 static bool
5671 status_revert(struct status *status, enum line_type type, bool has_none)
5673         if (!status || type != LINE_STAT_UNSTAGED) {
5674                 if (type == LINE_STAT_STAGED) {
5675                         report("Cannot revert changes to staged files");
5676                 } else if (type == LINE_STAT_UNTRACKED) {
5677                         report("Cannot revert changes to untracked files");
5678                 } else if (has_none) {
5679                         report("Nothing to revert");
5680                 } else {
5681                         report("Cannot revert changes to multiple files");
5682                 }
5683                 return FALSE;
5685         } else {
5686                 char mode[10] = "100644";
5687                 const char *reset_argv[] = {
5688                         "git", "update-index", "--cacheinfo", mode,
5689                                 status->old.rev, status->old.name, NULL
5690                 };
5691                 const char *checkout_argv[] = {
5692                         "git", "checkout", "--", status->old.name, NULL
5693                 };
5695                 if (!prompt_yesno("Are you sure you want to overwrite any changes?"))
5696                         return FALSE;
5697                 string_format(mode, "%o", status->old.mode);
5698                 return (status->status != 'U' || run_io_fg(reset_argv, opt_cdup)) &&
5699                         run_io_fg(checkout_argv, opt_cdup);
5700         }
5703 static enum request
5704 status_request(struct view *view, enum request request, struct line *line)
5706         struct status *status = line->data;
5708         switch (request) {
5709         case REQ_STATUS_UPDATE:
5710                 if (!status_update(view))
5711                         return REQ_NONE;
5712                 break;
5714         case REQ_STATUS_REVERT:
5715                 if (!status_revert(status, line->type, status_has_none(view, line)))
5716                         return REQ_NONE;
5717                 break;
5719         case REQ_STATUS_MERGE:
5720                 if (!status || status->status != 'U') {
5721                         report("Merging only possible for files with unmerged status ('U').");
5722                         return REQ_NONE;
5723                 }
5724                 open_mergetool(status->new.name);
5725                 break;
5727         case REQ_EDIT:
5728                 if (!status)
5729                         return request;
5730                 if (status->status == 'D') {
5731                         report("File has been deleted.");
5732                         return REQ_NONE;
5733                 }
5735                 open_editor(status->status != '?', status->new.name);
5736                 break;
5738         case REQ_VIEW_BLAME:
5739                 if (status) {
5740                         string_copy(opt_file, status->new.name);
5741                         opt_ref[0] = 0;
5742                 }
5743                 return request;
5745         case REQ_ENTER:
5746                 /* After returning the status view has been split to
5747                  * show the stage view. No further reloading is
5748                  * necessary. */
5749                 return status_enter(view, line);
5751         case REQ_REFRESH:
5752                 /* Simply reload the view. */
5753                 break;
5755         default:
5756                 return request;
5757         }
5759         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
5761         return REQ_NONE;
5764 static void
5765 status_select(struct view *view, struct line *line)
5767         struct status *status = line->data;
5768         char file[SIZEOF_STR] = "all files";
5769         const char *text;
5770         const char *key;
5772         if (status && !string_format(file, "'%s'", status->new.name))
5773                 return;
5775         if (!status && line[1].type == LINE_STAT_NONE)
5776                 line++;
5778         switch (line->type) {
5779         case LINE_STAT_STAGED:
5780                 text = "Press %s to unstage %s for commit";
5781                 break;
5783         case LINE_STAT_UNSTAGED:
5784                 text = "Press %s to stage %s for commit";
5785                 break;
5787         case LINE_STAT_UNTRACKED:
5788                 text = "Press %s to stage %s for addition";
5789                 break;
5791         case LINE_STAT_HEAD:
5792         case LINE_STAT_NONE:
5793                 text = "Nothing to update";
5794                 break;
5796         default:
5797                 die("line type %d not handled in switch", line->type);
5798         }
5800         if (status && status->status == 'U') {
5801                 text = "Press %s to resolve conflict in %s";
5802                 key = get_key(KEYMAP_STATUS, REQ_STATUS_MERGE);
5804         } else {
5805                 key = get_key(KEYMAP_STATUS, REQ_STATUS_UPDATE);
5806         }
5808         string_format(view->ref, text, key, file);
5811 static bool
5812 status_grep(struct view *view, struct line *line)
5814         struct status *status = line->data;
5816         if (status) {
5817                 const char buf[2] = { status->status, 0 };
5818                 const char *text[] = { status->new.name, buf, NULL };
5820                 return grep_text(view, text);
5821         }
5823         return FALSE;
5826 static struct view_ops status_ops = {
5827         "file",
5828         NULL,
5829         status_open,
5830         NULL,
5831         status_draw,
5832         status_request,
5833         status_grep,
5834         status_select,
5835 };
5838 static bool
5839 stage_diff_write(struct io *io, struct line *line, struct line *end)
5841         while (line < end) {
5842                 if (!io_write(io, line->data, strlen(line->data)) ||
5843                     !io_write(io, "\n", 1))
5844                         return FALSE;
5845                 line++;
5846                 if (line->type == LINE_DIFF_CHUNK ||
5847                     line->type == LINE_DIFF_HEADER)
5848                         break;
5849         }
5851         return TRUE;
5854 static struct line *
5855 stage_diff_find(struct view *view, struct line *line, enum line_type type)
5857         for (; view->line < line; line--)
5858                 if (line->type == type)
5859                         return line;
5861         return NULL;
5864 static bool
5865 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
5867         const char *apply_argv[SIZEOF_ARG] = {
5868                 "git", "apply", "--whitespace=nowarn", NULL
5869         };
5870         struct line *diff_hdr;
5871         struct io io = {};
5872         int argc = 3;
5874         diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
5875         if (!diff_hdr)
5876                 return FALSE;
5878         if (!revert)
5879                 apply_argv[argc++] = "--cached";
5880         if (revert || stage_line_type == LINE_STAT_STAGED)
5881                 apply_argv[argc++] = "-R";
5882         apply_argv[argc++] = "-";
5883         apply_argv[argc++] = NULL;
5884         if (!run_io(&io, apply_argv, opt_cdup, IO_WR))
5885                 return FALSE;
5887         if (!stage_diff_write(&io, diff_hdr, chunk) ||
5888             !stage_diff_write(&io, chunk, view->line + view->lines))
5889                 chunk = NULL;
5891         done_io(&io);
5892         run_io_bg(update_index_argv);
5894         return chunk ? TRUE : FALSE;
5897 static bool
5898 stage_update(struct view *view, struct line *line)
5900         struct line *chunk = NULL;
5902         if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
5903                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
5905         if (chunk) {
5906                 if (!stage_apply_chunk(view, chunk, FALSE)) {
5907                         report("Failed to apply chunk");
5908                         return FALSE;
5909                 }
5911         } else if (!stage_status.status) {
5912                 view = VIEW(REQ_VIEW_STATUS);
5914                 for (line = view->line; line < view->line + view->lines; line++)
5915                         if (line->type == stage_line_type)
5916                                 break;
5918                 if (!status_update_files(view, line + 1)) {
5919                         report("Failed to update files");
5920                         return FALSE;
5921                 }
5923         } else if (!status_update_file(&stage_status, stage_line_type)) {
5924                 report("Failed to update file");
5925                 return FALSE;
5926         }
5928         return TRUE;
5931 static bool
5932 stage_revert(struct view *view, struct line *line)
5934         struct line *chunk = NULL;
5936         if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
5937                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
5939         if (chunk) {
5940                 if (!prompt_yesno("Are you sure you want to revert changes?"))
5941                         return FALSE;
5943                 if (!stage_apply_chunk(view, chunk, TRUE)) {
5944                         report("Failed to revert chunk");
5945                         return FALSE;
5946                 }
5947                 return TRUE;
5949         } else {
5950                 return status_revert(stage_status.status ? &stage_status : NULL,
5951                                      stage_line_type, FALSE);
5952         }
5956 static void
5957 stage_next(struct view *view, struct line *line)
5959         int i;
5961         if (!stage_chunks) {
5962                 for (line = view->line; line < view->line + view->lines; line++) {
5963                         if (line->type != LINE_DIFF_CHUNK)
5964                                 continue;
5966                         if (!realloc_ints(&stage_chunk, stage_chunks, 1)) {
5967                                 report("Allocation failure");
5968                                 return;
5969                         }
5971                         stage_chunk[stage_chunks++] = line - view->line;
5972                 }
5973         }
5975         for (i = 0; i < stage_chunks; i++) {
5976                 if (stage_chunk[i] > view->lineno) {
5977                         do_scroll_view(view, stage_chunk[i] - view->lineno);
5978                         report("Chunk %d of %d", i + 1, stage_chunks);
5979                         return;
5980                 }
5981         }
5983         report("No next chunk found");
5986 static enum request
5987 stage_request(struct view *view, enum request request, struct line *line)
5989         switch (request) {
5990         case REQ_STATUS_UPDATE:
5991                 if (!stage_update(view, line))
5992                         return REQ_NONE;
5993                 break;
5995         case REQ_STATUS_REVERT:
5996                 if (!stage_revert(view, line))
5997                         return REQ_NONE;
5998                 break;
6000         case REQ_STAGE_NEXT:
6001                 if (stage_line_type == LINE_STAT_UNTRACKED) {
6002                         report("File is untracked; press %s to add",
6003                                get_key(KEYMAP_STAGE, REQ_STATUS_UPDATE));
6004                         return REQ_NONE;
6005                 }
6006                 stage_next(view, line);
6007                 return REQ_NONE;
6009         case REQ_EDIT:
6010                 if (!stage_status.new.name[0])
6011                         return request;
6012                 if (stage_status.status == 'D') {
6013                         report("File has been deleted.");
6014                         return REQ_NONE;
6015                 }
6017                 open_editor(stage_status.status != '?', stage_status.new.name);
6018                 break;
6020         case REQ_REFRESH:
6021                 /* Reload everything ... */
6022                 break;
6024         case REQ_VIEW_BLAME:
6025                 if (stage_status.new.name[0]) {
6026                         string_copy(opt_file, stage_status.new.name);
6027                         opt_ref[0] = 0;
6028                 }
6029                 return request;
6031         case REQ_ENTER:
6032                 return pager_request(view, request, line);
6034         default:
6035                 return request;
6036         }
6038         VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
6039         open_view(view, REQ_VIEW_STATUS, OPEN_REFRESH);
6041         /* Check whether the staged entry still exists, and close the
6042          * stage view if it doesn't. */
6043         if (!status_exists(&stage_status, stage_line_type)) {
6044                 status_restore(VIEW(REQ_VIEW_STATUS));
6045                 return REQ_VIEW_CLOSE;
6046         }
6048         if (stage_line_type == LINE_STAT_UNTRACKED) {
6049                 if (!suffixcmp(stage_status.new.name, -1, "/")) {
6050                         report("Cannot display a directory");
6051                         return REQ_NONE;
6052                 }
6054                 if (!prepare_update_file(view, stage_status.new.name)) {
6055                         report("Failed to open file: %s", strerror(errno));
6056                         return REQ_NONE;
6057                 }
6058         }
6059         open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
6061         return REQ_NONE;
6064 static struct view_ops stage_ops = {
6065         "line",
6066         NULL,
6067         NULL,
6068         pager_read,
6069         pager_draw,
6070         stage_request,
6071         pager_grep,
6072         pager_select,
6073 };
6076 /*
6077  * Revision graph
6078  */
6080 struct commit {
6081         char id[SIZEOF_REV];            /* SHA1 ID. */
6082         char title[128];                /* First line of the commit message. */
6083         const char *author;             /* Author of the commit. */
6084         time_t time;                    /* Date from the author ident. */
6085         struct ref_list *refs;          /* Repository references. */
6086         chtype graph[SIZEOF_REVGRAPH];  /* Ancestry chain graphics. */
6087         size_t graph_size;              /* The width of the graph array. */
6088         bool has_parents;               /* Rewritten --parents seen. */
6089 };
6091 /* Size of rev graph with no  "padding" columns */
6092 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
6094 struct rev_graph {
6095         struct rev_graph *prev, *next, *parents;
6096         char rev[SIZEOF_REVITEMS][SIZEOF_REV];
6097         size_t size;
6098         struct commit *commit;
6099         size_t pos;
6100         unsigned int boundary:1;
6101 };
6103 /* Parents of the commit being visualized. */
6104 static struct rev_graph graph_parents[4];
6106 /* The current stack of revisions on the graph. */
6107 static struct rev_graph graph_stacks[4] = {
6108         { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
6109         { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
6110         { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
6111         { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
6112 };
6114 static inline bool
6115 graph_parent_is_merge(struct rev_graph *graph)
6117         return graph->parents->size > 1;
6120 static inline void
6121 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
6123         struct commit *commit = graph->commit;
6125         if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
6126                 commit->graph[commit->graph_size++] = symbol;
6129 static void
6130 clear_rev_graph(struct rev_graph *graph)
6132         graph->boundary = 0;
6133         graph->size = graph->pos = 0;
6134         graph->commit = NULL;
6135         memset(graph->parents, 0, sizeof(*graph->parents));
6138 static void
6139 done_rev_graph(struct rev_graph *graph)
6141         if (graph_parent_is_merge(graph) &&
6142             graph->pos < graph->size - 1 &&
6143             graph->next->size == graph->size + graph->parents->size - 1) {
6144                 size_t i = graph->pos + graph->parents->size - 1;
6146                 graph->commit->graph_size = i * 2;
6147                 while (i < graph->next->size - 1) {
6148                         append_to_rev_graph(graph, ' ');
6149                         append_to_rev_graph(graph, '\\');
6150                         i++;
6151                 }
6152         }
6154         clear_rev_graph(graph);
6157 static void
6158 push_rev_graph(struct rev_graph *graph, const char *parent)
6160         int i;
6162         /* "Collapse" duplicate parents lines.
6163          *
6164          * FIXME: This needs to also update update the drawn graph but
6165          * for now it just serves as a method for pruning graph lines. */
6166         for (i = 0; i < graph->size; i++)
6167                 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
6168                         return;
6170         if (graph->size < SIZEOF_REVITEMS) {
6171                 string_copy_rev(graph->rev[graph->size++], parent);
6172         }
6175 static chtype
6176 get_rev_graph_symbol(struct rev_graph *graph)
6178         chtype symbol;
6180         if (graph->boundary)
6181                 symbol = REVGRAPH_BOUND;
6182         else if (graph->parents->size == 0)
6183                 symbol = REVGRAPH_INIT;
6184         else if (graph_parent_is_merge(graph))
6185                 symbol = REVGRAPH_MERGE;
6186         else if (graph->pos >= graph->size)
6187                 symbol = REVGRAPH_BRANCH;
6188         else
6189                 symbol = REVGRAPH_COMMIT;
6191         return symbol;
6194 static void
6195 draw_rev_graph(struct rev_graph *graph)
6197         struct rev_filler {
6198                 chtype separator, line;
6199         };
6200         enum { DEFAULT, RSHARP, RDIAG, LDIAG };
6201         static struct rev_filler fillers[] = {
6202                 { ' ',  '|' },
6203                 { '`',  '.' },
6204                 { '\'', ' ' },
6205                 { '/',  ' ' },
6206         };
6207         chtype symbol = get_rev_graph_symbol(graph);
6208         struct rev_filler *filler;
6209         size_t i;
6211         if (opt_line_graphics)
6212                 fillers[DEFAULT].line = line_graphics[LINE_GRAPHIC_VLINE];
6214         filler = &fillers[DEFAULT];
6216         for (i = 0; i < graph->pos; i++) {
6217                 append_to_rev_graph(graph, filler->line);
6218                 if (graph_parent_is_merge(graph->prev) &&
6219                     graph->prev->pos == i)
6220                         filler = &fillers[RSHARP];
6222                 append_to_rev_graph(graph, filler->separator);
6223         }
6225         /* Place the symbol for this revision. */
6226         append_to_rev_graph(graph, symbol);
6228         if (graph->prev->size > graph->size)
6229                 filler = &fillers[RDIAG];
6230         else
6231                 filler = &fillers[DEFAULT];
6233         i++;
6235         for (; i < graph->size; i++) {
6236                 append_to_rev_graph(graph, filler->separator);
6237                 append_to_rev_graph(graph, filler->line);
6238                 if (graph_parent_is_merge(graph->prev) &&
6239                     i < graph->prev->pos + graph->parents->size)
6240                         filler = &fillers[RSHARP];
6241                 if (graph->prev->size > graph->size)
6242                         filler = &fillers[LDIAG];
6243         }
6245         if (graph->prev->size > graph->size) {
6246                 append_to_rev_graph(graph, filler->separator);
6247                 if (filler->line != ' ')
6248                         append_to_rev_graph(graph, filler->line);
6249         }
6252 /* Prepare the next rev graph */
6253 static void
6254 prepare_rev_graph(struct rev_graph *graph)
6256         size_t i;
6258         /* First, traverse all lines of revisions up to the active one. */
6259         for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
6260                 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
6261                         break;
6263                 push_rev_graph(graph->next, graph->rev[graph->pos]);
6264         }
6266         /* Interleave the new revision parent(s). */
6267         for (i = 0; !graph->boundary && i < graph->parents->size; i++)
6268                 push_rev_graph(graph->next, graph->parents->rev[i]);
6270         /* Lastly, put any remaining revisions. */
6271         for (i = graph->pos + 1; i < graph->size; i++)
6272                 push_rev_graph(graph->next, graph->rev[i]);
6275 static void
6276 update_rev_graph(struct view *view, struct rev_graph *graph)
6278         /* If this is the finalizing update ... */
6279         if (graph->commit)
6280                 prepare_rev_graph(graph);
6282         /* Graph visualization needs a one rev look-ahead,
6283          * so the first update doesn't visualize anything. */
6284         if (!graph->prev->commit)
6285                 return;
6287         if (view->lines > 2)
6288                 view->line[view->lines - 3].dirty = 1;
6289         if (view->lines > 1)
6290                 view->line[view->lines - 2].dirty = 1;
6291         draw_rev_graph(graph->prev);
6292         done_rev_graph(graph->prev->prev);
6296 /*
6297  * Main view backend
6298  */
6300 static const char *main_argv[SIZEOF_ARG] = {
6301         "git", "log", "--no-color", "--pretty=raw", "--parents",
6302                       "--topo-order", "%(head)", NULL
6303 };
6305 static bool
6306 main_draw(struct view *view, struct line *line, unsigned int lineno)
6308         struct commit *commit = line->data;
6310         if (!commit->author)
6311                 return FALSE;
6313         if (opt_date && draw_date(view, &commit->time))
6314                 return TRUE;
6316         if (opt_author && draw_author(view, commit->author))
6317                 return TRUE;
6319         if (opt_rev_graph && commit->graph_size &&
6320             draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
6321                 return TRUE;
6323         if (opt_show_refs && commit->refs) {
6324                 size_t i;
6326                 for (i = 0; i < commit->refs->size; i++) {
6327                         struct ref *ref = commit->refs->refs[i];
6328                         enum line_type type;
6330                         if (ref->head)
6331                                 type = LINE_MAIN_HEAD;
6332                         else if (ref->ltag)
6333                                 type = LINE_MAIN_LOCAL_TAG;
6334                         else if (ref->tag)
6335                                 type = LINE_MAIN_TAG;
6336                         else if (ref->tracked)
6337                                 type = LINE_MAIN_TRACKED;
6338                         else if (ref->remote)
6339                                 type = LINE_MAIN_REMOTE;
6340                         else
6341                                 type = LINE_MAIN_REF;
6343                         if (draw_text(view, type, "[", TRUE) ||
6344                             draw_text(view, type, ref->name, TRUE) ||
6345                             draw_text(view, type, "]", TRUE))
6346                                 return TRUE;
6348                         if (draw_text(view, LINE_DEFAULT, " ", TRUE))
6349                                 return TRUE;
6350                 }
6351         }
6353         draw_text(view, LINE_DEFAULT, commit->title, TRUE);
6354         return TRUE;
6357 /* Reads git log --pretty=raw output and parses it into the commit struct. */
6358 static bool
6359 main_read(struct view *view, char *line)
6361         static struct rev_graph *graph = graph_stacks;
6362         enum line_type type;
6363         struct commit *commit;
6365         if (!line) {
6366                 int i;
6368                 if (!view->lines && !view->parent)
6369                         die("No revisions match the given arguments.");
6370                 if (view->lines > 0) {
6371                         commit = view->line[view->lines - 1].data;
6372                         view->line[view->lines - 1].dirty = 1;
6373                         if (!commit->author) {
6374                                 view->lines--;
6375                                 free(commit);
6376                                 graph->commit = NULL;
6377                         }
6378                 }
6379                 update_rev_graph(view, graph);
6381                 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
6382                         clear_rev_graph(&graph_stacks[i]);
6383                 return TRUE;
6384         }
6386         type = get_line_type(line);
6387         if (type == LINE_COMMIT) {
6388                 commit = calloc(1, sizeof(struct commit));
6389                 if (!commit)
6390                         return FALSE;
6392                 line += STRING_SIZE("commit ");
6393                 if (*line == '-') {
6394                         graph->boundary = 1;
6395                         line++;
6396                 }
6398                 string_copy_rev(commit->id, line);
6399                 commit->refs = get_ref_list(commit->id);
6400                 graph->commit = commit;
6401                 add_line_data(view, commit, LINE_MAIN_COMMIT);
6403                 while ((line = strchr(line, ' '))) {
6404                         line++;
6405                         push_rev_graph(graph->parents, line);
6406                         commit->has_parents = TRUE;
6407                 }
6408                 return TRUE;
6409         }
6411         if (!view->lines)
6412                 return TRUE;
6413         commit = view->line[view->lines - 1].data;
6415         switch (type) {
6416         case LINE_PARENT:
6417                 if (commit->has_parents)
6418                         break;
6419                 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
6420                 break;
6422         case LINE_AUTHOR:
6423                 parse_author_line(line + STRING_SIZE("author "),
6424                                   &commit->author, &commit->time);
6425                 update_rev_graph(view, graph);
6426                 graph = graph->next;
6427                 break;
6429         default:
6430                 /* Fill in the commit title if it has not already been set. */
6431                 if (commit->title[0])
6432                         break;
6434                 /* Require titles to start with a non-space character at the
6435                  * offset used by git log. */
6436                 if (strncmp(line, "    ", 4))
6437                         break;
6438                 line += 4;
6439                 /* Well, if the title starts with a whitespace character,
6440                  * try to be forgiving.  Otherwise we end up with no title. */
6441                 while (isspace(*line))
6442                         line++;
6443                 if (*line == '\0')
6444                         break;
6445                 /* FIXME: More graceful handling of titles; append "..." to
6446                  * shortened titles, etc. */
6448                 string_expand(commit->title, sizeof(commit->title), line, 1);
6449                 view->line[view->lines - 1].dirty = 1;
6450         }
6452         return TRUE;
6455 static enum request
6456 main_request(struct view *view, enum request request, struct line *line)
6458         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
6460         switch (request) {
6461         case REQ_ENTER:
6462                 open_view(view, REQ_VIEW_DIFF, flags);
6463                 break;
6464         case REQ_REFRESH:
6465                 load_refs();
6466                 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
6467                 break;
6468         default:
6469                 return request;
6470         }
6472         return REQ_NONE;
6475 static bool
6476 grep_refs(struct ref_list *list, regex_t *regex)
6478         regmatch_t pmatch;
6479         size_t i;
6481         if (!opt_show_refs || !list)
6482                 return FALSE;
6484         for (i = 0; i < list->size; i++) {
6485                 if (regexec(regex, list->refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
6486                         return TRUE;
6487         }
6489         return FALSE;
6492 static bool
6493 main_grep(struct view *view, struct line *line)
6495         struct commit *commit = line->data;
6496         const char *text[] = {
6497                 commit->title,
6498                 opt_author ? commit->author : "",
6499                 opt_date ? mkdate(&commit->time) : "",
6500                 NULL
6501         };
6503         return grep_text(view, text) || grep_refs(commit->refs, view->regex);
6506 static void
6507 main_select(struct view *view, struct line *line)
6509         struct commit *commit = line->data;
6511         string_copy_rev(view->ref, commit->id);
6512         string_copy_rev(ref_commit, view->ref);
6515 static struct view_ops main_ops = {
6516         "commit",
6517         main_argv,
6518         NULL,
6519         main_read,
6520         main_draw,
6521         main_request,
6522         main_grep,
6523         main_select,
6524 };
6527 /*
6528  * Unicode / UTF-8 handling
6529  *
6530  * NOTE: Much of the following code for dealing with Unicode is derived from
6531  * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
6532  * src/intl/charset.c from the UTF-8 branch commit elinks-0.11.0-g31f2c28.
6533  */
6535 static inline int
6536 unicode_width(unsigned long c)
6538         if (c >= 0x1100 &&
6539            (c <= 0x115f                         /* Hangul Jamo */
6540             || c == 0x2329
6541             || c == 0x232a
6542             || (c >= 0x2e80  && c <= 0xa4cf && c != 0x303f)
6543                                                 /* CJK ... Yi */
6544             || (c >= 0xac00  && c <= 0xd7a3)    /* Hangul Syllables */
6545             || (c >= 0xf900  && c <= 0xfaff)    /* CJK Compatibility Ideographs */
6546             || (c >= 0xfe30  && c <= 0xfe6f)    /* CJK Compatibility Forms */
6547             || (c >= 0xff00  && c <= 0xff60)    /* Fullwidth Forms */
6548             || (c >= 0xffe0  && c <= 0xffe6)
6549             || (c >= 0x20000 && c <= 0x2fffd)
6550             || (c >= 0x30000 && c <= 0x3fffd)))
6551                 return 2;
6553         if (c == '\t')
6554                 return opt_tab_size;
6556         return 1;
6559 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
6560  * Illegal bytes are set one. */
6561 static const unsigned char utf8_bytes[256] = {
6562         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,
6563         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,
6564         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,
6565         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,
6566         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,
6567         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,
6568         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,
6569         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,
6570 };
6572 /* Decode UTF-8 multi-byte representation into a Unicode character. */
6573 static inline unsigned long
6574 utf8_to_unicode(const char *string, size_t length)
6576         unsigned long unicode;
6578         switch (length) {
6579         case 1:
6580                 unicode  =   string[0];
6581                 break;
6582         case 2:
6583                 unicode  =  (string[0] & 0x1f) << 6;
6584                 unicode +=  (string[1] & 0x3f);
6585                 break;
6586         case 3:
6587                 unicode  =  (string[0] & 0x0f) << 12;
6588                 unicode += ((string[1] & 0x3f) << 6);
6589                 unicode +=  (string[2] & 0x3f);
6590                 break;
6591         case 4:
6592                 unicode  =  (string[0] & 0x0f) << 18;
6593                 unicode += ((string[1] & 0x3f) << 12);
6594                 unicode += ((string[2] & 0x3f) << 6);
6595                 unicode +=  (string[3] & 0x3f);
6596                 break;
6597         case 5:
6598                 unicode  =  (string[0] & 0x0f) << 24;
6599                 unicode += ((string[1] & 0x3f) << 18);
6600                 unicode += ((string[2] & 0x3f) << 12);
6601                 unicode += ((string[3] & 0x3f) << 6);
6602                 unicode +=  (string[4] & 0x3f);
6603                 break;
6604         case 6:
6605                 unicode  =  (string[0] & 0x01) << 30;
6606                 unicode += ((string[1] & 0x3f) << 24);
6607                 unicode += ((string[2] & 0x3f) << 18);
6608                 unicode += ((string[3] & 0x3f) << 12);
6609                 unicode += ((string[4] & 0x3f) << 6);
6610                 unicode +=  (string[5] & 0x3f);
6611                 break;
6612         default:
6613                 die("Invalid Unicode length");
6614         }
6616         /* Invalid characters could return the special 0xfffd value but NUL
6617          * should be just as good. */
6618         return unicode > 0xffff ? 0 : unicode;
6621 /* Calculates how much of string can be shown within the given maximum width
6622  * and sets trimmed parameter to non-zero value if all of string could not be
6623  * shown. If the reserve flag is TRUE, it will reserve at least one
6624  * trailing character, which can be useful when drawing a delimiter.
6625  *
6626  * Returns the number of bytes to output from string to satisfy max_width. */
6627 static size_t
6628 utf8_length(const char **start, size_t skip, int *width, size_t max_width, int *trimmed, bool reserve)
6630         const char *string = *start;
6631         const char *end = strchr(string, '\0');
6632         unsigned char last_bytes = 0;
6633         size_t last_ucwidth = 0;
6635         *width = 0;
6636         *trimmed = 0;
6638         while (string < end) {
6639                 int c = *(unsigned char *) string;
6640                 unsigned char bytes = utf8_bytes[c];
6641                 size_t ucwidth;
6642                 unsigned long unicode;
6644                 if (string + bytes > end)
6645                         break;
6647                 /* Change representation to figure out whether
6648                  * it is a single- or double-width character. */
6650                 unicode = utf8_to_unicode(string, bytes);
6651                 /* FIXME: Graceful handling of invalid Unicode character. */
6652                 if (!unicode)
6653                         break;
6655                 ucwidth = unicode_width(unicode);
6656                 if (skip > 0) {
6657                         skip -= ucwidth <= skip ? ucwidth : skip;
6658                         *start += bytes;
6659                 }
6660                 *width  += ucwidth;
6661                 if (*width > max_width) {
6662                         *trimmed = 1;
6663                         *width -= ucwidth;
6664                         if (reserve && *width == max_width) {
6665                                 string -= last_bytes;
6666                                 *width -= last_ucwidth;
6667                         }
6668                         break;
6669                 }
6671                 string  += bytes;
6672                 last_bytes = ucwidth ? bytes : 0;
6673                 last_ucwidth = ucwidth;
6674         }
6676         return string - *start;
6680 /*
6681  * Status management
6682  */
6684 /* Whether or not the curses interface has been initialized. */
6685 static bool cursed = FALSE;
6687 /* Terminal hacks and workarounds. */
6688 static bool use_scroll_redrawwin;
6689 static bool use_scroll_status_wclear;
6691 /* The status window is used for polling keystrokes. */
6692 static WINDOW *status_win;
6694 /* Reading from the prompt? */
6695 static bool input_mode = FALSE;
6697 static bool status_empty = FALSE;
6699 /* Update status and title window. */
6700 static void
6701 report(const char *msg, ...)
6703         struct view *view = display[current_view];
6705         if (input_mode)
6706                 return;
6708         if (!view) {
6709                 char buf[SIZEOF_STR];
6710                 va_list args;
6712                 va_start(args, msg);
6713                 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
6714                         buf[sizeof(buf) - 1] = 0;
6715                         buf[sizeof(buf) - 2] = '.';
6716                         buf[sizeof(buf) - 3] = '.';
6717                         buf[sizeof(buf) - 4] = '.';
6718                 }
6719                 va_end(args);
6720                 die("%s", buf);
6721         }
6723         if (!status_empty || *msg) {
6724                 va_list args;
6726                 va_start(args, msg);
6728                 wmove(status_win, 0, 0);
6729                 if (view->has_scrolled && use_scroll_status_wclear)
6730                         wclear(status_win);
6731                 if (*msg) {
6732                         vwprintw(status_win, msg, args);
6733                         status_empty = FALSE;
6734                 } else {
6735                         status_empty = TRUE;
6736                 }
6737                 wclrtoeol(status_win);
6738                 wnoutrefresh(status_win);
6740                 va_end(args);
6741         }
6743         update_view_title(view);
6746 /* Controls when nodelay should be in effect when polling user input. */
6747 static void
6748 set_nonblocking_input(bool loading)
6750         static unsigned int loading_views;
6752         if ((loading == FALSE && loading_views-- == 1) ||
6753             (loading == TRUE  && loading_views++ == 0))
6754                 nodelay(status_win, loading);
6757 static void
6758 init_display(void)
6760         const char *term;
6761         int x, y;
6763         /* Initialize the curses library */
6764         if (isatty(STDIN_FILENO)) {
6765                 cursed = !!initscr();
6766                 opt_tty = stdin;
6767         } else {
6768                 /* Leave stdin and stdout alone when acting as a pager. */
6769                 opt_tty = fopen("/dev/tty", "r+");
6770                 if (!opt_tty)
6771                         die("Failed to open /dev/tty");
6772                 cursed = !!newterm(NULL, opt_tty, opt_tty);
6773         }
6775         if (!cursed)
6776                 die("Failed to initialize curses");
6778         nonl();         /* Disable conversion and detect newlines from input. */
6779         cbreak();       /* Take input chars one at a time, no wait for \n */
6780         noecho();       /* Don't echo input */
6781         leaveok(stdscr, FALSE);
6783         if (has_colors())
6784                 init_colors();
6786         getmaxyx(stdscr, y, x);
6787         status_win = newwin(1, 0, y - 1, 0);
6788         if (!status_win)
6789                 die("Failed to create status window");
6791         /* Enable keyboard mapping */
6792         keypad(status_win, TRUE);
6793         wbkgdset(status_win, get_line_attr(LINE_STATUS));
6795         TABSIZE = opt_tab_size;
6796         if (opt_line_graphics) {
6797                 line_graphics[LINE_GRAPHIC_VLINE] = ACS_VLINE;
6798         }
6800         term = getenv("XTERM_VERSION") ? NULL : getenv("COLORTERM");
6801         if (term && !strcmp(term, "gnome-terminal")) {
6802                 /* In the gnome-terminal-emulator, the message from
6803                  * scrolling up one line when impossible followed by
6804                  * scrolling down one line causes corruption of the
6805                  * status line. This is fixed by calling wclear. */
6806                 use_scroll_status_wclear = TRUE;
6807                 use_scroll_redrawwin = FALSE;
6809         } else if (term && !strcmp(term, "xrvt-xpm")) {
6810                 /* No problems with full optimizations in xrvt-(unicode)
6811                  * and aterm. */
6812                 use_scroll_status_wclear = use_scroll_redrawwin = FALSE;
6814         } else {
6815                 /* When scrolling in (u)xterm the last line in the
6816                  * scrolling direction will update slowly. */
6817                 use_scroll_redrawwin = TRUE;
6818                 use_scroll_status_wclear = FALSE;
6819         }
6822 static int
6823 get_input(int prompt_position)
6825         struct view *view;
6826         int i, key, cursor_y, cursor_x;
6828         if (prompt_position)
6829                 input_mode = TRUE;
6831         while (TRUE) {
6832                 foreach_view (view, i) {
6833                         update_view(view);
6834                         if (view_is_displayed(view) && view->has_scrolled &&
6835                             use_scroll_redrawwin)
6836                                 redrawwin(view->win);
6837                         view->has_scrolled = FALSE;
6838                 }
6840                 /* Update the cursor position. */
6841                 if (prompt_position) {
6842                         getbegyx(status_win, cursor_y, cursor_x);
6843                         cursor_x = prompt_position;
6844                 } else {
6845                         view = display[current_view];
6846                         getbegyx(view->win, cursor_y, cursor_x);
6847                         cursor_x = view->width - 1;
6848                         cursor_y += view->lineno - view->offset;
6849                 }
6850                 setsyx(cursor_y, cursor_x);
6852                 /* Refresh, accept single keystroke of input */
6853                 doupdate();
6854                 key = wgetch(status_win);
6856                 /* wgetch() with nodelay() enabled returns ERR when
6857                  * there's no input. */
6858                 if (key == ERR) {
6860                 } else if (key == KEY_RESIZE) {
6861                         int height, width;
6863                         getmaxyx(stdscr, height, width);
6865                         wresize(status_win, 1, width);
6866                         mvwin(status_win, height - 1, 0);
6867                         wnoutrefresh(status_win);
6868                         resize_display();
6869                         redraw_display(TRUE);
6871                 } else {
6872                         input_mode = FALSE;
6873                         return key;
6874                 }
6875         }
6878 static char *
6879 prompt_input(const char *prompt, input_handler handler, void *data)
6881         enum input_status status = INPUT_OK;
6882         static char buf[SIZEOF_STR];
6883         size_t pos = 0;
6885         buf[pos] = 0;
6887         while (status == INPUT_OK || status == INPUT_SKIP) {
6888                 int key;
6890                 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
6891                 wclrtoeol(status_win);
6893                 key = get_input(pos + 1);
6894                 switch (key) {
6895                 case KEY_RETURN:
6896                 case KEY_ENTER:
6897                 case '\n':
6898                         status = pos ? INPUT_STOP : INPUT_CANCEL;
6899                         break;
6901                 case KEY_BACKSPACE:
6902                         if (pos > 0)
6903                                 buf[--pos] = 0;
6904                         else
6905                                 status = INPUT_CANCEL;
6906                         break;
6908                 case KEY_ESC:
6909                         status = INPUT_CANCEL;
6910                         break;
6912                 default:
6913                         if (pos >= sizeof(buf)) {
6914                                 report("Input string too long");
6915                                 return NULL;
6916                         }
6918                         status = handler(data, buf, key);
6919                         if (status == INPUT_OK)
6920                                 buf[pos++] = (char) key;
6921                 }
6922         }
6924         /* Clear the status window */
6925         status_empty = FALSE;
6926         report("");
6928         if (status == INPUT_CANCEL)
6929                 return NULL;
6931         buf[pos++] = 0;
6933         return buf;
6936 static enum input_status
6937 prompt_yesno_handler(void *data, char *buf, int c)
6939         if (c == 'y' || c == 'Y')
6940                 return INPUT_STOP;
6941         if (c == 'n' || c == 'N')
6942                 return INPUT_CANCEL;
6943         return INPUT_SKIP;
6946 static bool
6947 prompt_yesno(const char *prompt)
6949         char prompt2[SIZEOF_STR];
6951         if (!string_format(prompt2, "%s [Yy/Nn]", prompt))
6952                 return FALSE;
6954         return !!prompt_input(prompt2, prompt_yesno_handler, NULL);
6957 static enum input_status
6958 read_prompt_handler(void *data, char *buf, int c)
6960         return isprint(c) ? INPUT_OK : INPUT_SKIP;
6963 static char *
6964 read_prompt(const char *prompt)
6966         return prompt_input(prompt, read_prompt_handler, NULL);
6969 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected)
6971         enum input_status status = INPUT_OK;
6972         int size = 0;
6974         while (items[size].text)
6975                 size++;
6977         while (status == INPUT_OK) {
6978                 const struct menu_item *item = &items[*selected];
6979                 int key;
6980                 int i;
6982                 mvwprintw(status_win, 0, 0, "%s (%d of %d) ",
6983                           prompt, *selected + 1, size);
6984                 if (item->hotkey)
6985                         wprintw(status_win, "[%c] ", (char) item->hotkey);
6986                 wprintw(status_win, "%s", item->text);
6987                 wclrtoeol(status_win);
6989                 key = get_input(COLS - 1);
6990                 switch (key) {
6991                 case KEY_RETURN:
6992                 case KEY_ENTER:
6993                 case '\n':
6994                         status = INPUT_STOP;
6995                         break;
6997                 case KEY_LEFT:
6998                 case KEY_UP:
6999                         *selected = *selected - 1;
7000                         if (*selected < 0)
7001                                 *selected = size - 1;
7002                         break;
7004                 case KEY_RIGHT:
7005                 case KEY_DOWN:
7006                         *selected = (*selected + 1) % size;
7007                         break;
7009                 case KEY_ESC:
7010                         status = INPUT_CANCEL;
7011                         break;
7013                 default:
7014                         for (i = 0; items[i].text; i++)
7015                                 if (items[i].hotkey == key) {
7016                                         *selected = i;
7017                                         status = INPUT_STOP;
7018                                         break;
7019                                 }
7020                 }
7021         }
7023         /* Clear the status window */
7024         status_empty = FALSE;
7025         report("");
7027         return status != INPUT_CANCEL;
7030 /*
7031  * Repository properties
7032  */
7034 static struct ref **refs = NULL;
7035 static size_t refs_size = 0;
7037 static struct ref_list **ref_lists = NULL;
7038 static size_t ref_lists_size = 0;
7040 DEFINE_ALLOCATOR(realloc_refs, struct ref *, 256)
7041 DEFINE_ALLOCATOR(realloc_refs_list, struct ref *, 8)
7042 DEFINE_ALLOCATOR(realloc_ref_lists, struct ref_list *, 8)
7044 static int
7045 compare_refs(const void *ref1_, const void *ref2_)
7047         const struct ref *ref1 = *(const struct ref **)ref1_;
7048         const struct ref *ref2 = *(const struct ref **)ref2_;
7050         if (ref1->tag != ref2->tag)
7051                 return ref2->tag - ref1->tag;
7052         if (ref1->ltag != ref2->ltag)
7053                 return ref2->ltag - ref2->ltag;
7054         if (ref1->head != ref2->head)
7055                 return ref2->head - ref1->head;
7056         if (ref1->tracked != ref2->tracked)
7057                 return ref2->tracked - ref1->tracked;
7058         if (ref1->remote != ref2->remote)
7059                 return ref2->remote - ref1->remote;
7060         return strcmp(ref1->name, ref2->name);
7063 static void
7064 foreach_ref(bool (*visitor)(void *data, struct ref *ref), void *data)
7066         size_t i;
7068         for (i = 0; i < refs_size; i++)
7069                 if (!visitor(data, refs[i]))
7070                         break;
7073 static struct ref_list *
7074 get_ref_list(const char *id)
7076         struct ref_list *list;
7077         size_t i;
7079         for (i = 0; i < ref_lists_size; i++)
7080                 if (!strcmp(id, ref_lists[i]->id))
7081                         return ref_lists[i];
7083         if (!realloc_ref_lists(&ref_lists, ref_lists_size, 1))
7084                 return NULL;
7085         list = calloc(1, sizeof(*list));
7086         if (!list)
7087                 return NULL;
7089         for (i = 0; i < refs_size; i++) {
7090                 if (!strcmp(id, refs[i]->id) &&
7091                     realloc_refs_list(&list->refs, list->size, 1))
7092                         list->refs[list->size++] = refs[i];
7093         }
7095         if (!list->refs) {
7096                 free(list);
7097                 return NULL;
7098         }
7100         qsort(list->refs, list->size, sizeof(*list->refs), compare_refs);
7101         ref_lists[ref_lists_size++] = list;
7102         return list;
7105 static int
7106 read_ref(char *id, size_t idlen, char *name, size_t namelen)
7108         struct ref *ref = NULL;
7109         bool tag = FALSE;
7110         bool ltag = FALSE;
7111         bool remote = FALSE;
7112         bool tracked = FALSE;
7113         bool head = FALSE;
7114         int from = 0, to = refs_size - 1;
7116         if (!prefixcmp(name, "refs/tags/")) {
7117                 if (!suffixcmp(name, namelen, "^{}")) {
7118                         namelen -= 3;
7119                         name[namelen] = 0;
7120                 } else {
7121                         ltag = TRUE;
7122                 }
7124                 tag = TRUE;
7125                 namelen -= STRING_SIZE("refs/tags/");
7126                 name    += STRING_SIZE("refs/tags/");
7128         } else if (!prefixcmp(name, "refs/remotes/")) {
7129                 remote = TRUE;
7130                 namelen -= STRING_SIZE("refs/remotes/");
7131                 name    += STRING_SIZE("refs/remotes/");
7132                 tracked  = !strcmp(opt_remote, name);
7134         } else if (!prefixcmp(name, "refs/heads/")) {
7135                 namelen -= STRING_SIZE("refs/heads/");
7136                 name    += STRING_SIZE("refs/heads/");
7137                 head     = !strncmp(opt_head, name, namelen);
7139         } else if (!strcmp(name, "HEAD")) {
7140                 string_ncopy(opt_head_rev, id, idlen);
7141                 return OK;
7142         }
7144         /* If we are reloading or it's an annotated tag, replace the
7145          * previous SHA1 with the resolved commit id; relies on the fact
7146          * git-ls-remote lists the commit id of an annotated tag right
7147          * before the commit id it points to. */
7148         while (from <= to) {
7149                 size_t pos = (to + from) / 2;
7150                 int cmp = strcmp(name, refs[pos]->name);
7152                 if (!cmp) {
7153                         ref = refs[pos];
7154                         break;
7155                 }
7157                 if (cmp < 0)
7158                         to = pos - 1;
7159                 else
7160                         from = pos + 1;
7161         }
7163         if (!ref) {
7164                 if (!realloc_refs(&refs, refs_size, 1))
7165                         return ERR;
7166                 ref = calloc(1, sizeof(*ref) + namelen);
7167                 if (!ref)
7168                         return ERR;
7169                 memmove(refs + from + 1, refs + from,
7170                         (refs_size - from) * sizeof(*refs));
7171                 refs[from] = ref;
7172                 strncpy(ref->name, name, namelen);
7173                 refs_size++;
7174         }
7176         ref->head = head;
7177         ref->tag = tag;
7178         ref->ltag = ltag;
7179         ref->remote = remote;
7180         ref->tracked = tracked;
7181         string_copy_rev(ref->id, id);
7183         return OK;
7186 static int
7187 load_refs(void)
7189         const char *head_argv[] = {
7190                 "git", "symbolic-ref", "HEAD", NULL
7191         };
7192         static const char *ls_remote_argv[SIZEOF_ARG] = {
7193                 "git", "ls-remote", opt_git_dir, NULL
7194         };
7195         static bool init = FALSE;
7196         size_t i;
7198         if (!init) {
7199                 argv_from_env(ls_remote_argv, "TIG_LS_REMOTE");
7200                 init = TRUE;
7201         }
7203         if (!*opt_git_dir)
7204                 return OK;
7206         if (run_io_buf(head_argv, opt_head, sizeof(opt_head)) &&
7207             !prefixcmp(opt_head, "refs/heads/")) {
7208                 char *offset = opt_head + STRING_SIZE("refs/heads/");
7210                 memmove(opt_head, offset, strlen(offset) + 1);
7211         }
7213         for (i = 0; i < refs_size; i++)
7214                 refs[i]->id[0] = 0;
7216         if (run_io_load(ls_remote_argv, "\t", read_ref) == ERR)
7217                 return ERR;
7219         /* Update the ref lists to reflect changes. */
7220         for (i = 0; i < ref_lists_size; i++) {
7221                 struct ref_list *list = ref_lists[i];
7222                 size_t old, new;
7224                 for (old = new = 0; old < list->size; old++)
7225                         if (!strcmp(list->id, list->refs[old]->id))
7226                                 list->refs[new++] = list->refs[old];
7227                 list->size = new;
7228         }
7230         return OK;
7233 static void
7234 set_remote_branch(const char *name, const char *value, size_t valuelen)
7236         if (!strcmp(name, ".remote")) {
7237                 string_ncopy(opt_remote, value, valuelen);
7239         } else if (*opt_remote && !strcmp(name, ".merge")) {
7240                 size_t from = strlen(opt_remote);
7242                 if (!prefixcmp(value, "refs/heads/"))
7243                         value += STRING_SIZE("refs/heads/");
7245                 if (!string_format_from(opt_remote, &from, "/%s", value))
7246                         opt_remote[0] = 0;
7247         }
7250 static void
7251 set_repo_config_option(char *name, char *value, int (*cmd)(int, const char **))
7253         const char *argv[SIZEOF_ARG] = { name, "=" };
7254         int argc = 1 + (cmd == option_set_command);
7255         int error = ERR;
7257         if (!argv_from_string(argv, &argc, value))
7258                 config_msg = "Too many option arguments";
7259         else
7260                 error = cmd(argc, argv);
7262         if (error == ERR)
7263                 warn("Option 'tig.%s': %s", name, config_msg);
7266 static bool
7267 set_environment_variable(const char *name, const char *value)
7269         size_t len = strlen(name) + 1 + strlen(value) + 1;
7270         char *env = malloc(len);
7272         if (env &&
7273             string_nformat(env, len, NULL, "%s=%s", name, value) &&
7274             putenv(env) == 0)
7275                 return TRUE;
7276         free(env);
7277         return FALSE;
7280 static void
7281 set_work_tree(const char *value)
7283         char cwd[SIZEOF_STR];
7285         if (!getcwd(cwd, sizeof(cwd)))
7286                 die("Failed to get cwd path: %s", strerror(errno));
7287         if (chdir(opt_git_dir) < 0)
7288                 die("Failed to chdir(%s): %s", strerror(errno));
7289         if (!getcwd(opt_git_dir, sizeof(opt_git_dir)))
7290                 die("Failed to get git path: %s", strerror(errno));
7291         if (chdir(cwd) < 0)
7292                 die("Failed to chdir(%s): %s", cwd, strerror(errno));
7293         if (chdir(value) < 0)
7294                 die("Failed to chdir(%s): %s", value, strerror(errno));
7295         if (!getcwd(cwd, sizeof(cwd)))
7296                 die("Failed to get cwd path: %s", strerror(errno));
7297         if (!set_environment_variable("GIT_WORK_TREE", cwd))
7298                 die("Failed to set GIT_WORK_TREE to '%s'", cwd);
7299         if (!set_environment_variable("GIT_DIR", opt_git_dir))
7300                 die("Failed to set GIT_DIR to '%s'", opt_git_dir);
7301         opt_is_inside_work_tree = TRUE;
7304 static int
7305 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
7307         if (!strcmp(name, "i18n.commitencoding"))
7308                 string_ncopy(opt_encoding, value, valuelen);
7310         else if (!strcmp(name, "core.editor"))
7311                 string_ncopy(opt_editor, value, valuelen);
7313         else if (!strcmp(name, "core.worktree"))
7314                 set_work_tree(value);
7316         else if (!prefixcmp(name, "tig.color."))
7317                 set_repo_config_option(name + 10, value, option_color_command);
7319         else if (!prefixcmp(name, "tig.bind."))
7320                 set_repo_config_option(name + 9, value, option_bind_command);
7322         else if (!prefixcmp(name, "tig."))
7323                 set_repo_config_option(name + 4, value, option_set_command);
7325         else if (*opt_head && !prefixcmp(name, "branch.") &&
7326                  !strncmp(name + 7, opt_head, strlen(opt_head)))
7327                 set_remote_branch(name + 7 + strlen(opt_head), value, valuelen);
7329         return OK;
7332 static int
7333 load_git_config(void)
7335         const char *config_list_argv[] = { "git", GIT_CONFIG, "--list", NULL };
7337         return run_io_load(config_list_argv, "=", read_repo_config_option);
7340 static int
7341 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
7343         if (!opt_git_dir[0]) {
7344                 string_ncopy(opt_git_dir, name, namelen);
7346         } else if (opt_is_inside_work_tree == -1) {
7347                 /* This can be 3 different values depending on the
7348                  * version of git being used. If git-rev-parse does not
7349                  * understand --is-inside-work-tree it will simply echo
7350                  * the option else either "true" or "false" is printed.
7351                  * Default to true for the unknown case. */
7352                 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
7354         } else if (*name == '.') {
7355                 string_ncopy(opt_cdup, name, namelen);
7357         } else {
7358                 string_ncopy(opt_prefix, name, namelen);
7359         }
7361         return OK;
7364 static int
7365 load_repo_info(void)
7367         const char *rev_parse_argv[] = {
7368                 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
7369                         "--show-cdup", "--show-prefix", NULL
7370         };
7372         return run_io_load(rev_parse_argv, "=", read_repo_info);
7376 /*
7377  * Main
7378  */
7380 static const char usage[] =
7381 "tig " TIG_VERSION " (" __DATE__ ")\n"
7382 "\n"
7383 "Usage: tig        [options] [revs] [--] [paths]\n"
7384 "   or: tig show   [options] [revs] [--] [paths]\n"
7385 "   or: tig blame  [rev] path\n"
7386 "   or: tig status\n"
7387 "   or: tig <      [git command output]\n"
7388 "\n"
7389 "Options:\n"
7390 "  -v, --version   Show version and exit\n"
7391 "  -h, --help      Show help message and exit";
7393 static void __NORETURN
7394 quit(int sig)
7396         /* XXX: Restore tty modes and let the OS cleanup the rest! */
7397         if (cursed)
7398                 endwin();
7399         exit(0);
7402 static void __NORETURN
7403 die(const char *err, ...)
7405         va_list args;
7407         endwin();
7409         va_start(args, err);
7410         fputs("tig: ", stderr);
7411         vfprintf(stderr, err, args);
7412         fputs("\n", stderr);
7413         va_end(args);
7415         exit(1);
7418 static void
7419 warn(const char *msg, ...)
7421         va_list args;
7423         va_start(args, msg);
7424         fputs("tig warning: ", stderr);
7425         vfprintf(stderr, msg, args);
7426         fputs("\n", stderr);
7427         va_end(args);
7430 static enum request
7431 parse_options(int argc, const char *argv[])
7433         enum request request = REQ_VIEW_MAIN;
7434         const char *subcommand;
7435         bool seen_dashdash = FALSE;
7436         /* XXX: This is vulnerable to the user overriding options
7437          * required for the main view parser. */
7438         const char *custom_argv[SIZEOF_ARG] = {
7439                 "git", "log", "--no-color", "--pretty=raw", "--parents",
7440                         "--topo-order", NULL
7441         };
7442         int i, j = 6;
7444         if (!isatty(STDIN_FILENO)) {
7445                 io_open(&VIEW(REQ_VIEW_PAGER)->io, "");
7446                 return REQ_VIEW_PAGER;
7447         }
7449         if (argc <= 1)
7450                 return REQ_NONE;
7452         subcommand = argv[1];
7453         if (!strcmp(subcommand, "status")) {
7454                 if (argc > 2)
7455                         warn("ignoring arguments after `%s'", subcommand);
7456                 return REQ_VIEW_STATUS;
7458         } else if (!strcmp(subcommand, "blame")) {
7459                 if (argc <= 2 || argc > 4)
7460                         die("invalid number of options to blame\n\n%s", usage);
7462                 i = 2;
7463                 if (argc == 4) {
7464                         string_ncopy(opt_ref, argv[i], strlen(argv[i]));
7465                         i++;
7466                 }
7468                 string_ncopy(opt_file, argv[i], strlen(argv[i]));
7469                 return REQ_VIEW_BLAME;
7471         } else if (!strcmp(subcommand, "show")) {
7472                 request = REQ_VIEW_DIFF;
7474         } else {
7475                 subcommand = NULL;
7476         }
7478         if (subcommand) {
7479                 custom_argv[1] = subcommand;
7480                 j = 2;
7481         }
7483         for (i = 1 + !!subcommand; i < argc; i++) {
7484                 const char *opt = argv[i];
7486                 if (seen_dashdash || !strcmp(opt, "--")) {
7487                         seen_dashdash = TRUE;
7489                 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
7490                         printf("tig version %s\n", TIG_VERSION);
7491                         quit(0);
7493                 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
7494                         printf("%s\n", usage);
7495                         quit(0);
7496                 }
7498                 custom_argv[j++] = opt;
7499                 if (j >= ARRAY_SIZE(custom_argv))
7500                         die("command too long");
7501         }
7503         if (!prepare_update(VIEW(request), custom_argv, NULL, FORMAT_NONE))                                                                        
7504                 die("Failed to format arguments"); 
7506         return request;
7509 int
7510 main(int argc, const char *argv[])
7512         enum request request = parse_options(argc, argv);
7513         struct view *view;
7514         size_t i;
7516         signal(SIGINT, quit);
7517         signal(SIGPIPE, SIG_IGN);
7519         if (setlocale(LC_ALL, "")) {
7520                 char *codeset = nl_langinfo(CODESET);
7522                 string_ncopy(opt_codeset, codeset, strlen(codeset));
7523         }
7525         if (load_repo_info() == ERR)
7526                 die("Failed to load repo info.");
7528         if (load_options() == ERR)
7529                 die("Failed to load user config.");
7531         if (load_git_config() == ERR)
7532                 die("Failed to load repo config.");
7534         /* Require a git repository unless when running in pager mode. */
7535         if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
7536                 die("Not a git repository");
7538         if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
7539                 opt_utf8 = FALSE;
7541         if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
7542                 opt_iconv = iconv_open(opt_codeset, opt_encoding);
7543                 if (opt_iconv == ICONV_NONE)
7544                         die("Failed to initialize character set conversion");
7545         }
7547         if (load_refs() == ERR)
7548                 die("Failed to load refs.");
7550         foreach_view (view, i)
7551                 argv_from_env(view->ops->argv, view->cmd_env);
7553         init_display();
7555         if (request != REQ_NONE)
7556                 open_view(NULL, request, OPEN_PREPARED);
7557         request = request == REQ_NONE ? REQ_VIEW_MAIN : REQ_NONE;
7559         while (view_driver(display[current_view], request)) {
7560                 int key = get_input(0);
7562                 view = display[current_view];
7563                 request = get_keybinding(view->keymap, key);
7565                 /* Some low-level request handling. This keeps access to
7566                  * status_win restricted. */
7567                 switch (request) {
7568                 case REQ_PROMPT:
7569                 {
7570                         char *cmd = read_prompt(":");
7572                         if (cmd && isdigit(*cmd)) {
7573                                 int lineno = view->lineno + 1;
7575                                 if (parse_int(&lineno, cmd, 1, view->lines + 1) == OK) {
7576                                         select_view_line(view, lineno - 1);
7577                                         report("");
7578                                 } else {
7579                                         report("Unable to parse '%s' as a line number", cmd);
7580                                 }
7582                         } else if (cmd) {
7583                                 struct view *next = VIEW(REQ_VIEW_PAGER);
7584                                 const char *argv[SIZEOF_ARG] = { "git" };
7585                                 int argc = 1;
7587                                 /* When running random commands, initially show the
7588                                  * command in the title. However, it maybe later be
7589                                  * overwritten if a commit line is selected. */
7590                                 string_ncopy(next->ref, cmd, strlen(cmd));
7592                                 if (!argv_from_string(argv, &argc, cmd)) {
7593                                         report("Too many arguments");
7594                                 } else if (!prepare_update(next, argv, NULL, FORMAT_DASH)) {
7595                                         report("Failed to format command");
7596                                 } else {
7597                                         open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
7598                                 }
7599                         }
7601                         request = REQ_NONE;
7602                         break;
7603                 }
7604                 case REQ_SEARCH:
7605                 case REQ_SEARCH_BACK:
7606                 {
7607                         const char *prompt = request == REQ_SEARCH ? "/" : "?";
7608                         char *search = read_prompt(prompt);
7610                         if (search)
7611                                 string_ncopy(opt_search, search, strlen(search));
7612                         else if (*opt_search)
7613                                 request = request == REQ_SEARCH ?
7614                                         REQ_FIND_NEXT :
7615                                         REQ_FIND_PREV;
7616                         else
7617                                 request = REQ_NONE;
7618                         break;
7619                 }
7620                 default:
7621                         break;
7622                 }
7623         }
7625         quit(0);
7627         return 0;