Code

Add simple support for showing menues and use it for showing option menu
[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))
76 #define ARRAY_SIZE(x)   (sizeof(x) / sizeof(x[0]))
77 #define STRING_SIZE(x)  (sizeof(x) - 1)
79 #define SIZEOF_STR      1024    /* Default string size. */
80 #define SIZEOF_REF      256     /* Size of symbolic or SHA1 ID. */
81 #define SIZEOF_REV      41      /* Holds a SHA-1 and an ending NUL. */
82 #define SIZEOF_ARG      32      /* Default argument array size. */
84 /* Revision graph */
86 #define REVGRAPH_INIT   'I'
87 #define REVGRAPH_MERGE  'M'
88 #define REVGRAPH_BRANCH '+'
89 #define REVGRAPH_COMMIT '*'
90 #define REVGRAPH_BOUND  '^'
92 #define SIZEOF_REVGRAPH 19      /* Size of revision ancestry graphics. */
94 /* This color name can be used to refer to the default term colors. */
95 #define COLOR_DEFAULT   (-1)
97 #define ICONV_NONE      ((iconv_t) -1)
98 #ifndef ICONV_CONST
99 #define ICONV_CONST     /* nothing */
100 #endif
102 /* The format and size of the date column in the main view. */
103 #define DATE_FORMAT     "%Y-%m-%d %H:%M"
104 #define DATE_COLS       STRING_SIZE("2006-04-29 14:21 ")
106 #define AUTHOR_COLS     20
107 #define ID_COLS         8
109 /* The default interval between line numbers. */
110 #define NUMBER_INTERVAL 5
112 #define TAB_SIZE        8
114 #define SCALE_SPLIT_VIEW(height)        ((height) * 2 / 3)
116 #define NULL_ID         "0000000000000000000000000000000000000000"
118 #define S_ISGITLINK(mode) (((mode) & S_IFMT) == 0160000)
120 #ifndef GIT_CONFIG
121 #define GIT_CONFIG "config"
122 #endif
124 /* Some ASCII-shorthands fitted into the ncurses namespace. */
125 #define KEY_TAB         '\t'
126 #define KEY_RETURN      '\r'
127 #define KEY_ESC         27
130 struct ref {
131         char id[SIZEOF_REV];    /* Commit SHA1 ID */
132         unsigned int head:1;    /* Is it the current HEAD? */
133         unsigned int tag:1;     /* Is it a tag? */
134         unsigned int ltag:1;    /* If so, is the tag local? */
135         unsigned int remote:1;  /* Is it a remote ref? */
136         unsigned int tracked:1; /* Is it the remote for the current HEAD? */
137         char name[1];           /* Ref name; tag or head names are shortened. */
138 };
140 struct ref_list {
141         char id[SIZEOF_REV];    /* Commit SHA1 ID */
142         size_t size;            /* Number of refs. */
143         struct ref **refs;      /* References for this ID. */
144 };
146 static struct ref_list *get_ref_list(const char *id);
147 static void foreach_ref(bool (*visitor)(void *data, struct ref *ref), void *data);
148 static int load_refs(void);
150 enum format_flags {
151         FORMAT_ALL,             /* Perform replacement in all arguments. */
152         FORMAT_DASH,            /* Perform replacement up until "--". */
153         FORMAT_NONE             /* No replacement should be performed. */
154 };
156 static bool format_argv(const char *dst[], const char *src[], enum format_flags flags);
158 enum input_status {
159         INPUT_OK,
160         INPUT_SKIP,
161         INPUT_STOP,
162         INPUT_CANCEL
163 };
165 typedef enum input_status (*input_handler)(void *data, char *buf, int c);
167 static char *prompt_input(const char *prompt, input_handler handler, void *data);
168 static bool prompt_yesno(const char *prompt);
170 struct menu_item {
171         int hotkey;
172         const char *text;
173         void *data;
174 };
176 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected);
178 /*
179  * Allocation helpers ... Entering macro hell to never be seen again.
180  */
182 #define DEFINE_ALLOCATOR(name, type, chunk_size)                                \
183 static type *                                                                   \
184 name(type **mem, size_t size, size_t increase)                                  \
185 {                                                                               \
186         size_t num_chunks = (size + chunk_size - 1) / chunk_size;               \
187         size_t num_chunks_new = (size + increase + chunk_size - 1) / chunk_size;\
188         type *tmp = *mem;                                                       \
189                                                                                 \
190         if (mem == NULL || num_chunks != num_chunks_new) {                      \
191                 tmp = realloc(tmp, num_chunks_new * chunk_size * sizeof(type)); \
192                 if (tmp)                                                        \
193                         *mem = tmp;                                             \
194         }                                                                       \
195                                                                                 \
196         return tmp;                                                             \
199 /*
200  * String helpers
201  */
203 static inline void
204 string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t srclen)
206         if (srclen > dstlen - 1)
207                 srclen = dstlen - 1;
209         strncpy(dst, src, srclen);
210         dst[srclen] = 0;
213 /* Shorthands for safely copying into a fixed buffer. */
215 #define string_copy(dst, src) \
216         string_ncopy_do(dst, sizeof(dst), src, sizeof(src))
218 #define string_ncopy(dst, src, srclen) \
219         string_ncopy_do(dst, sizeof(dst), src, srclen)
221 #define string_copy_rev(dst, src) \
222         string_ncopy_do(dst, SIZEOF_REV, src, SIZEOF_REV - 1)
224 #define string_add(dst, from, src) \
225         string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src))
227 static void
228 string_expand(char *dst, size_t dstlen, const char *src, int tabsize)
230         size_t size, pos;
232         for (size = pos = 0; size < dstlen - 1 && src[pos]; pos++) {
233                 if (src[pos] == '\t') {
234                         size_t expanded = tabsize - (size % tabsize);
236                         if (expanded + size >= dstlen - 1)
237                                 expanded = dstlen - size - 1;
238                         memcpy(dst + size, "        ", expanded);
239                         size += expanded;
240                 } else {
241                         dst[size++] = src[pos];
242                 }
243         }
245         dst[size] = 0;
248 static char *
249 chomp_string(char *name)
251         int namelen;
253         while (isspace(*name))
254                 name++;
256         namelen = strlen(name) - 1;
257         while (namelen > 0 && isspace(name[namelen]))
258                 name[namelen--] = 0;
260         return name;
263 static bool
264 string_nformat(char *buf, size_t bufsize, size_t *bufpos, const char *fmt, ...)
266         va_list args;
267         size_t pos = bufpos ? *bufpos : 0;
269         va_start(args, fmt);
270         pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
271         va_end(args);
273         if (bufpos)
274                 *bufpos = pos;
276         return pos >= bufsize ? FALSE : TRUE;
279 #define string_format(buf, fmt, args...) \
280         string_nformat(buf, sizeof(buf), NULL, fmt, args)
282 #define string_format_from(buf, from, fmt, args...) \
283         string_nformat(buf, sizeof(buf), from, fmt, args)
285 static int
286 string_enum_compare(const char *str1, const char *str2, int len)
288         size_t i;
290 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
292         /* Diff-Header == DIFF_HEADER */
293         for (i = 0; i < len; i++) {
294                 if (toupper(str1[i]) == toupper(str2[i]))
295                         continue;
297                 if (string_enum_sep(str1[i]) &&
298                     string_enum_sep(str2[i]))
299                         continue;
301                 return str1[i] - str2[i];
302         }
304         return 0;
307 struct enum_map {
308         const char *name;
309         int namelen;
310         int value;
311 };
313 #define ENUM_MAP(name, value) { name, STRING_SIZE(name), value }
315 static bool
316 map_enum_do(const struct enum_map *map, size_t map_size, int *value, const char *name)
318         size_t namelen = strlen(name);
319         int i;
321         for (i = 0; i < map_size; i++)
322                 if (namelen == map[i].namelen &&
323                     !string_enum_compare(name, map[i].name, namelen)) {
324                         *value = map[i].value;
325                         return TRUE;
326                 }
328         return FALSE;
331 #define map_enum(attr, map, name) \
332         map_enum_do(map, ARRAY_SIZE(map), attr, name)
334 #define prefixcmp(str1, str2) \
335         strncmp(str1, str2, STRING_SIZE(str2))
337 static inline int
338 suffixcmp(const char *str, int slen, const char *suffix)
340         size_t len = slen >= 0 ? slen : strlen(str);
341         size_t suffixlen = strlen(suffix);
343         return suffixlen < len ? strcmp(str + len - suffixlen, suffix) : -1;
347 static const char *
348 mkdate(const time_t *time)
350         static char buf[DATE_COLS + 1];
351         struct tm tm;
353         gmtime_r(time, &tm);
354         return strftime(buf, sizeof(buf), DATE_FORMAT, &tm) ? buf : NULL;
358 static bool
359 argv_from_string(const char *argv[SIZEOF_ARG], int *argc, char *cmd)
361         int valuelen;
363         while (*cmd && *argc < SIZEOF_ARG && (valuelen = strcspn(cmd, " \t"))) {
364                 bool advance = cmd[valuelen] != 0;
366                 cmd[valuelen] = 0;
367                 argv[(*argc)++] = chomp_string(cmd);
368                 cmd = chomp_string(cmd + valuelen + advance);
369         }
371         if (*argc < SIZEOF_ARG)
372                 argv[*argc] = NULL;
373         return *argc < SIZEOF_ARG;
376 static void
377 argv_from_env(const char **argv, const char *name)
379         char *env = argv ? getenv(name) : NULL;
380         int argc = 0;
382         if (env && *env)
383                 env = strdup(env);
384         if (env && !argv_from_string(argv, &argc, env))
385                 die("Too many arguments in the `%s` environment variable", name);
389 /*
390  * Executing external commands.
391  */
393 enum io_type {
394         IO_FD,                  /* File descriptor based IO. */
395         IO_BG,                  /* Execute command in the background. */
396         IO_FG,                  /* Execute command with same std{in,out,err}. */
397         IO_RD,                  /* Read only fork+exec IO. */
398         IO_WR,                  /* Write only fork+exec IO. */
399         IO_AP,                  /* Append fork+exec output to file. */
400 };
402 struct io {
403         enum io_type type;      /* The requested type of pipe. */
404         const char *dir;        /* Directory from which to execute. */
405         pid_t pid;              /* Pipe for reading or writing. */
406         int pipe;               /* Pipe end for reading or writing. */
407         int error;              /* Error status. */
408         const char *argv[SIZEOF_ARG];   /* Shell command arguments. */
409         char *buf;              /* Read buffer. */
410         size_t bufalloc;        /* Allocated buffer size. */
411         size_t bufsize;         /* Buffer content size. */
412         char *bufpos;           /* Current buffer position. */
413         unsigned int eof:1;     /* Has end of file been reached. */
414 };
416 static void
417 reset_io(struct io *io)
419         io->pipe = -1;
420         io->pid = 0;
421         io->buf = io->bufpos = NULL;
422         io->bufalloc = io->bufsize = 0;
423         io->error = 0;
424         io->eof = 0;
427 static void
428 init_io(struct io *io, const char *dir, enum io_type type)
430         reset_io(io);
431         io->type = type;
432         io->dir = dir;
435 static bool
436 init_io_rd(struct io *io, const char *argv[], const char *dir,
437                 enum format_flags flags)
439         init_io(io, dir, IO_RD);
440         return format_argv(io->argv, argv, flags);
443 static bool
444 io_open(struct io *io, const char *name)
446         init_io(io, NULL, IO_FD);
447         io->pipe = *name ? open(name, O_RDONLY) : STDIN_FILENO;
448         if (io->pipe == -1)
449                 io->error = errno;
450         return io->pipe != -1;
453 static bool
454 kill_io(struct io *io)
456         return io->pid == 0 || kill(io->pid, SIGKILL) != -1;
459 static bool
460 done_io(struct io *io)
462         pid_t pid = io->pid;
464         if (io->pipe != -1)
465                 close(io->pipe);
466         free(io->buf);
467         reset_io(io);
469         while (pid > 0) {
470                 int status;
471                 pid_t waiting = waitpid(pid, &status, 0);
473                 if (waiting < 0) {
474                         if (errno == EINTR)
475                                 continue;
476                         report("waitpid failed (%s)", strerror(errno));
477                         return FALSE;
478                 }
480                 return waiting == pid &&
481                        !WIFSIGNALED(status) &&
482                        WIFEXITED(status) &&
483                        !WEXITSTATUS(status);
484         }
486         return TRUE;
489 static bool
490 start_io(struct io *io)
492         int pipefds[2] = { -1, -1 };
494         if (io->type == IO_FD)
495                 return TRUE;
497         if ((io->type == IO_RD || io->type == IO_WR) &&
498             pipe(pipefds) < 0)
499                 return FALSE;
500         else if (io->type == IO_AP)
501                 pipefds[1] = io->pipe;
503         if ((io->pid = fork())) {
504                 if (pipefds[!(io->type == IO_WR)] != -1)
505                         close(pipefds[!(io->type == IO_WR)]);
506                 if (io->pid != -1) {
507                         io->pipe = pipefds[!!(io->type == IO_WR)];
508                         return TRUE;
509                 }
511         } else {
512                 if (io->type != IO_FG) {
513                         int devnull = open("/dev/null", O_RDWR);
514                         int readfd  = io->type == IO_WR ? pipefds[0] : devnull;
515                         int writefd = (io->type == IO_RD || io->type == IO_AP)
516                                                         ? pipefds[1] : devnull;
518                         dup2(readfd,  STDIN_FILENO);
519                         dup2(writefd, STDOUT_FILENO);
520                         dup2(devnull, STDERR_FILENO);
522                         close(devnull);
523                         if (pipefds[0] != -1)
524                                 close(pipefds[0]);
525                         if (pipefds[1] != -1)
526                                 close(pipefds[1]);
527                 }
529                 if (io->dir && *io->dir && chdir(io->dir) == -1)
530                         die("Failed to change directory: %s", strerror(errno));
532                 execvp(io->argv[0], (char *const*) io->argv);
533                 die("Failed to execute program: %s", strerror(errno));
534         }
536         if (pipefds[!!(io->type == IO_WR)] != -1)
537                 close(pipefds[!!(io->type == IO_WR)]);
538         return FALSE;
541 static bool
542 run_io(struct io *io, const char **argv, const char *dir, enum io_type type)
544         init_io(io, dir, type);
545         if (!format_argv(io->argv, argv, FORMAT_NONE))
546                 return FALSE;
547         return start_io(io);
550 static int
551 run_io_do(struct io *io)
553         return start_io(io) && done_io(io);
556 static int
557 run_io_bg(const char **argv)
559         struct io io = {};
561         init_io(&io, NULL, IO_BG);
562         if (!format_argv(io.argv, argv, FORMAT_NONE))
563                 return FALSE;
564         return run_io_do(&io);
567 static bool
568 run_io_fg(const char **argv, const char *dir)
570         struct io io = {};
572         init_io(&io, dir, IO_FG);
573         if (!format_argv(io.argv, argv, FORMAT_NONE))
574                 return FALSE;
575         return run_io_do(&io);
578 static bool
579 run_io_append(const char **argv, enum format_flags flags, int fd)
581         struct io io = {};
583         init_io(&io, NULL, IO_AP);
584         io.pipe = fd;
585         if (format_argv(io.argv, argv, flags))
586                 return run_io_do(&io);
587         close(fd);
588         return FALSE;
591 static bool
592 run_io_rd(struct io *io, const char **argv, enum format_flags flags)
594         return init_io_rd(io, argv, NULL, flags) && start_io(io);
597 static bool
598 io_eof(struct io *io)
600         return io->eof;
603 static int
604 io_error(struct io *io)
606         return io->error;
609 static char *
610 io_strerror(struct io *io)
612         return strerror(io->error);
615 static bool
616 io_can_read(struct io *io)
618         struct timeval tv = { 0, 500 };
619         fd_set fds;
621         FD_ZERO(&fds);
622         FD_SET(io->pipe, &fds);
624         return select(io->pipe + 1, &fds, NULL, NULL, &tv) > 0;
627 static ssize_t
628 io_read(struct io *io, void *buf, size_t bufsize)
630         do {
631                 ssize_t readsize = read(io->pipe, buf, bufsize);
633                 if (readsize < 0 && (errno == EAGAIN || errno == EINTR))
634                         continue;
635                 else if (readsize == -1)
636                         io->error = errno;
637                 else if (readsize == 0)
638                         io->eof = 1;
639                 return readsize;
640         } while (1);
643 DEFINE_ALLOCATOR(realloc_io_buf, char, BUFSIZ)
645 static char *
646 io_get(struct io *io, int c, bool can_read)
648         char *eol;
649         ssize_t readsize;
651         while (TRUE) {
652                 if (io->bufsize > 0) {
653                         eol = memchr(io->bufpos, c, io->bufsize);
654                         if (eol) {
655                                 char *line = io->bufpos;
657                                 *eol = 0;
658                                 io->bufpos = eol + 1;
659                                 io->bufsize -= io->bufpos - line;
660                                 return line;
661                         }
662                 }
664                 if (io_eof(io)) {
665                         if (io->bufsize) {
666                                 io->bufpos[io->bufsize] = 0;
667                                 io->bufsize = 0;
668                                 return io->bufpos;
669                         }
670                         return NULL;
671                 }
673                 if (!can_read)
674                         return NULL;
676                 if (io->bufsize > 0 && io->bufpos > io->buf)
677                         memmove(io->buf, io->bufpos, io->bufsize);
679                 if (io->bufalloc == io->bufsize) {
680                         if (!realloc_io_buf(&io->buf, io->bufalloc, BUFSIZ))
681                                 return NULL;
682                         io->bufalloc += BUFSIZ;
683                 }
685                 io->bufpos = io->buf;
686                 readsize = io_read(io, io->buf + io->bufsize, io->bufalloc - io->bufsize);
687                 if (io_error(io))
688                         return NULL;
689                 io->bufsize += readsize;
690         }
693 static bool
694 io_write(struct io *io, const void *buf, size_t bufsize)
696         size_t written = 0;
698         while (!io_error(io) && written < bufsize) {
699                 ssize_t size;
701                 size = write(io->pipe, buf + written, bufsize - written);
702                 if (size < 0 && (errno == EAGAIN || errno == EINTR))
703                         continue;
704                 else if (size == -1)
705                         io->error = errno;
706                 else
707                         written += size;
708         }
710         return written == bufsize;
713 static bool
714 io_read_buf(struct io *io, char buf[], size_t bufsize)
716         char *result = io_get(io, '\n', TRUE);
718         if (result) {
719                 result = chomp_string(result);
720                 string_ncopy_do(buf, bufsize, result, strlen(result));
721         }
723         return done_io(io) && result;
726 static bool
727 run_io_buf(const char **argv, char buf[], size_t bufsize)
729         struct io io = {};
731         return run_io_rd(&io, argv, FORMAT_NONE) && io_read_buf(&io, buf, bufsize);
734 static int
735 io_load(struct io *io, const char *separators,
736         int (*read_property)(char *, size_t, char *, size_t))
738         char *name;
739         int state = OK;
741         if (!start_io(io))
742                 return ERR;
744         while (state == OK && (name = io_get(io, '\n', TRUE))) {
745                 char *value;
746                 size_t namelen;
747                 size_t valuelen;
749                 name = chomp_string(name);
750                 namelen = strcspn(name, separators);
752                 if (name[namelen]) {
753                         name[namelen] = 0;
754                         value = chomp_string(name + namelen + 1);
755                         valuelen = strlen(value);
757                 } else {
758                         value = "";
759                         valuelen = 0;
760                 }
762                 state = read_property(name, namelen, value, valuelen);
763         }
765         if (state != ERR && io_error(io))
766                 state = ERR;
767         done_io(io);
769         return state;
772 static int
773 run_io_load(const char **argv, const char *separators,
774             int (*read_property)(char *, size_t, char *, size_t))
776         struct io io = {};
778         return init_io_rd(&io, argv, NULL, FORMAT_NONE)
779                 ? io_load(&io, separators, read_property) : ERR;
783 /*
784  * User requests
785  */
787 #define REQ_INFO \
788         /* XXX: Keep the view request first and in sync with views[]. */ \
789         REQ_GROUP("View switching") \
790         REQ_(VIEW_MAIN,         "Show main view"), \
791         REQ_(VIEW_DIFF,         "Show diff view"), \
792         REQ_(VIEW_LOG,          "Show log view"), \
793         REQ_(VIEW_TREE,         "Show tree view"), \
794         REQ_(VIEW_BLOB,         "Show blob view"), \
795         REQ_(VIEW_BLAME,        "Show blame view"), \
796         REQ_(VIEW_BRANCH,       "Show branch view"), \
797         REQ_(VIEW_HELP,         "Show help page"), \
798         REQ_(VIEW_PAGER,        "Show pager view"), \
799         REQ_(VIEW_STATUS,       "Show status view"), \
800         REQ_(VIEW_STAGE,        "Show stage view"), \
801         \
802         REQ_GROUP("View manipulation") \
803         REQ_(ENTER,             "Enter current line and scroll"), \
804         REQ_(NEXT,              "Move to next"), \
805         REQ_(PREVIOUS,          "Move to previous"), \
806         REQ_(PARENT,            "Move to parent"), \
807         REQ_(VIEW_NEXT,         "Move focus to next view"), \
808         REQ_(REFRESH,           "Reload and refresh"), \
809         REQ_(MAXIMIZE,          "Maximize the current view"), \
810         REQ_(VIEW_CLOSE,        "Close the current view"), \
811         REQ_(QUIT,              "Close all views and quit"), \
812         \
813         REQ_GROUP("View specific requests") \
814         REQ_(STATUS_UPDATE,     "Update file status"), \
815         REQ_(STATUS_REVERT,     "Revert file changes"), \
816         REQ_(STATUS_MERGE,      "Merge file using external tool"), \
817         REQ_(STAGE_NEXT,        "Find next chunk to stage"), \
818         \
819         REQ_GROUP("Cursor navigation") \
820         REQ_(MOVE_UP,           "Move cursor one line up"), \
821         REQ_(MOVE_DOWN,         "Move cursor one line down"), \
822         REQ_(MOVE_PAGE_DOWN,    "Move cursor one page down"), \
823         REQ_(MOVE_PAGE_UP,      "Move cursor one page up"), \
824         REQ_(MOVE_FIRST_LINE,   "Move cursor to first line"), \
825         REQ_(MOVE_LAST_LINE,    "Move cursor to last line"), \
826         \
827         REQ_GROUP("Scrolling") \
828         REQ_(SCROLL_LEFT,       "Scroll two columns left"), \
829         REQ_(SCROLL_RIGHT,      "Scroll two columns right"), \
830         REQ_(SCROLL_LINE_UP,    "Scroll one line up"), \
831         REQ_(SCROLL_LINE_DOWN,  "Scroll one line down"), \
832         REQ_(SCROLL_PAGE_UP,    "Scroll one page up"), \
833         REQ_(SCROLL_PAGE_DOWN,  "Scroll one page down"), \
834         \
835         REQ_GROUP("Searching") \
836         REQ_(SEARCH,            "Search the view"), \
837         REQ_(SEARCH_BACK,       "Search backwards in the view"), \
838         REQ_(FIND_NEXT,         "Find next search match"), \
839         REQ_(FIND_PREV,         "Find previous search match"), \
840         \
841         REQ_GROUP("Option manipulation") \
842         REQ_(OPTIONS,           "Open option menu"), \
843         REQ_(TOGGLE_LINENO,     "Toggle line numbers"), \
844         REQ_(TOGGLE_DATE,       "Toggle date display"), \
845         REQ_(TOGGLE_AUTHOR,     "Toggle author display"), \
846         REQ_(TOGGLE_REV_GRAPH,  "Toggle revision graph visualization"), \
847         REQ_(TOGGLE_REFS,       "Toggle reference display (tags/branches)"), \
848         REQ_(TOGGLE_SORT_ORDER, "Toggle ascending/descending sort order"), \
849         REQ_(TOGGLE_SORT_FIELD, "Toggle field to sort by"), \
850         \
851         REQ_GROUP("Misc") \
852         REQ_(PROMPT,            "Bring up the prompt"), \
853         REQ_(SCREEN_REDRAW,     "Redraw the screen"), \
854         REQ_(SHOW_VERSION,      "Show version information"), \
855         REQ_(STOP_LOADING,      "Stop all loading views"), \
856         REQ_(EDIT,              "Open in editor"), \
857         REQ_(NONE,              "Do nothing")
860 /* User action requests. */
861 enum request {
862 #define REQ_GROUP(help)
863 #define REQ_(req, help) REQ_##req
865         /* Offset all requests to avoid conflicts with ncurses getch values. */
866         REQ_OFFSET = KEY_MAX + 1,
867         REQ_INFO
869 #undef  REQ_GROUP
870 #undef  REQ_
871 };
873 struct request_info {
874         enum request request;
875         const char *name;
876         int namelen;
877         const char *help;
878 };
880 static const struct request_info req_info[] = {
881 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
882 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
883         REQ_INFO
884 #undef  REQ_GROUP
885 #undef  REQ_
886 };
888 static enum request
889 get_request(const char *name)
891         int namelen = strlen(name);
892         int i;
894         for (i = 0; i < ARRAY_SIZE(req_info); i++)
895                 if (req_info[i].namelen == namelen &&
896                     !string_enum_compare(req_info[i].name, name, namelen))
897                         return req_info[i].request;
899         return REQ_NONE;
903 /*
904  * Options
905  */
907 /* Option and state variables. */
908 static bool opt_date                    = TRUE;
909 static bool opt_author                  = TRUE;
910 static bool opt_line_number             = FALSE;
911 static bool opt_line_graphics           = TRUE;
912 static bool opt_rev_graph               = FALSE;
913 static bool opt_show_refs               = TRUE;
914 static int opt_num_interval             = NUMBER_INTERVAL;
915 static double opt_hscroll               = 0.50;
916 static int opt_tab_size                 = TAB_SIZE;
917 static int opt_author_cols              = AUTHOR_COLS-1;
918 static char opt_path[SIZEOF_STR]        = "";
919 static char opt_file[SIZEOF_STR]        = "";
920 static char opt_ref[SIZEOF_REF]         = "";
921 static char opt_head[SIZEOF_REF]        = "";
922 static char opt_head_rev[SIZEOF_REV]    = "";
923 static char opt_remote[SIZEOF_REF]      = "";
924 static char opt_encoding[20]            = "UTF-8";
925 static bool opt_utf8                    = TRUE;
926 static char opt_codeset[20]             = "UTF-8";
927 static iconv_t opt_iconv                = ICONV_NONE;
928 static char opt_search[SIZEOF_STR]      = "";
929 static char opt_cdup[SIZEOF_STR]        = "";
930 static char opt_prefix[SIZEOF_STR]      = "";
931 static char opt_git_dir[SIZEOF_STR]     = "";
932 static signed char opt_is_inside_work_tree      = -1; /* set to TRUE or FALSE */
933 static char opt_editor[SIZEOF_STR]      = "";
934 static FILE *opt_tty                    = NULL;
936 #define is_initial_commit()     (!*opt_head_rev)
937 #define is_head_commit(rev)     (!strcmp((rev), "HEAD") || !strcmp(opt_head_rev, (rev)))
940 /*
941  * Line-oriented content detection.
942  */
944 #define LINE_INFO \
945 LINE(DIFF_HEADER,  "diff --git ",       COLOR_YELLOW,   COLOR_DEFAULT,  0), \
946 LINE(DIFF_CHUNK,   "@@",                COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
947 LINE(DIFF_ADD,     "+",                 COLOR_GREEN,    COLOR_DEFAULT,  0), \
948 LINE(DIFF_DEL,     "-",                 COLOR_RED,      COLOR_DEFAULT,  0), \
949 LINE(DIFF_INDEX,        "index ",         COLOR_BLUE,   COLOR_DEFAULT,  0), \
950 LINE(DIFF_OLDMODE,      "old file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
951 LINE(DIFF_NEWMODE,      "new file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
952 LINE(DIFF_COPY_FROM,    "copy from",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
953 LINE(DIFF_COPY_TO,      "copy to",        COLOR_YELLOW, COLOR_DEFAULT,  0), \
954 LINE(DIFF_RENAME_FROM,  "rename from",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
955 LINE(DIFF_RENAME_TO,    "rename to",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
956 LINE(DIFF_SIMILARITY,   "similarity ",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
957 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
958 LINE(DIFF_TREE,         "diff-tree ",     COLOR_BLUE,   COLOR_DEFAULT,  0), \
959 LINE(PP_AUTHOR,    "Author: ",          COLOR_CYAN,     COLOR_DEFAULT,  0), \
960 LINE(PP_COMMIT,    "Commit: ",          COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
961 LINE(PP_MERGE,     "Merge: ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
962 LINE(PP_DATE,      "Date:   ",          COLOR_YELLOW,   COLOR_DEFAULT,  0), \
963 LINE(PP_ADATE,     "AuthorDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
964 LINE(PP_CDATE,     "CommitDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
965 LINE(PP_REFS,      "Refs: ",            COLOR_RED,      COLOR_DEFAULT,  0), \
966 LINE(COMMIT,       "commit ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
967 LINE(PARENT,       "parent ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
968 LINE(TREE,         "tree ",             COLOR_BLUE,     COLOR_DEFAULT,  0), \
969 LINE(AUTHOR,       "author ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
970 LINE(COMMITTER,    "committer ",        COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
971 LINE(SIGNOFF,      "    Signed-off-by", COLOR_YELLOW,   COLOR_DEFAULT,  0), \
972 LINE(ACKED,        "    Acked-by",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
973 LINE(DEFAULT,      "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
974 LINE(CURSOR,       "",                  COLOR_WHITE,    COLOR_GREEN,    A_BOLD), \
975 LINE(STATUS,       "",                  COLOR_GREEN,    COLOR_DEFAULT,  0), \
976 LINE(DELIMITER,    "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
977 LINE(DATE,         "",                  COLOR_BLUE,     COLOR_DEFAULT,  0), \
978 LINE(MODE,         "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
979 LINE(LINE_NUMBER,  "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
980 LINE(TITLE_BLUR,   "",                  COLOR_WHITE,    COLOR_BLUE,     0), \
981 LINE(TITLE_FOCUS,  "",                  COLOR_WHITE,    COLOR_BLUE,     A_BOLD), \
982 LINE(MAIN_COMMIT,  "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
983 LINE(MAIN_TAG,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  A_BOLD), \
984 LINE(MAIN_LOCAL_TAG,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
985 LINE(MAIN_REMOTE,  "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
986 LINE(MAIN_TRACKED, "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_BOLD), \
987 LINE(MAIN_REF,     "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
988 LINE(MAIN_HEAD,    "",                  COLOR_CYAN,     COLOR_DEFAULT,  A_BOLD), \
989 LINE(MAIN_REVGRAPH,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
990 LINE(TREE_HEAD,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_BOLD), \
991 LINE(TREE_DIR,     "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_NORMAL), \
992 LINE(TREE_FILE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
993 LINE(STAT_HEAD,    "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
994 LINE(STAT_SECTION, "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
995 LINE(STAT_NONE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
996 LINE(STAT_STAGED,  "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
997 LINE(STAT_UNSTAGED,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
998 LINE(STAT_UNTRACKED,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
999 LINE(BLAME_ID,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0)
1001 enum line_type {
1002 #define LINE(type, line, fg, bg, attr) \
1003         LINE_##type
1004         LINE_INFO,
1005         LINE_NONE
1006 #undef  LINE
1007 };
1009 struct line_info {
1010         const char *name;       /* Option name. */
1011         int namelen;            /* Size of option name. */
1012         const char *line;       /* The start of line to match. */
1013         int linelen;            /* Size of string to match. */
1014         int fg, bg, attr;       /* Color and text attributes for the lines. */
1015 };
1017 static struct line_info line_info[] = {
1018 #define LINE(type, line, fg, bg, attr) \
1019         { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
1020         LINE_INFO
1021 #undef  LINE
1022 };
1024 static enum line_type
1025 get_line_type(const char *line)
1027         int linelen = strlen(line);
1028         enum line_type type;
1030         for (type = 0; type < ARRAY_SIZE(line_info); type++)
1031                 /* Case insensitive search matches Signed-off-by lines better. */
1032                 if (linelen >= line_info[type].linelen &&
1033                     !strncasecmp(line_info[type].line, line, line_info[type].linelen))
1034                         return type;
1036         return LINE_DEFAULT;
1039 static inline int
1040 get_line_attr(enum line_type type)
1042         assert(type < ARRAY_SIZE(line_info));
1043         return COLOR_PAIR(type) | line_info[type].attr;
1046 static struct line_info *
1047 get_line_info(const char *name)
1049         size_t namelen = strlen(name);
1050         enum line_type type;
1052         for (type = 0; type < ARRAY_SIZE(line_info); type++)
1053                 if (namelen == line_info[type].namelen &&
1054                     !string_enum_compare(line_info[type].name, name, namelen))
1055                         return &line_info[type];
1057         return NULL;
1060 static void
1061 init_colors(void)
1063         int default_bg = line_info[LINE_DEFAULT].bg;
1064         int default_fg = line_info[LINE_DEFAULT].fg;
1065         enum line_type type;
1067         start_color();
1069         if (assume_default_colors(default_fg, default_bg) == ERR) {
1070                 default_bg = COLOR_BLACK;
1071                 default_fg = COLOR_WHITE;
1072         }
1074         for (type = 0; type < ARRAY_SIZE(line_info); type++) {
1075                 struct line_info *info = &line_info[type];
1076                 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
1077                 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
1079                 init_pair(type, fg, bg);
1080         }
1083 struct line {
1084         enum line_type type;
1086         /* State flags */
1087         unsigned int selected:1;
1088         unsigned int dirty:1;
1089         unsigned int cleareol:1;
1091         void *data;             /* User data */
1092 };
1095 /*
1096  * Keys
1097  */
1099 struct keybinding {
1100         int alias;
1101         enum request request;
1102 };
1104 static const struct keybinding default_keybindings[] = {
1105         /* View switching */
1106         { 'm',          REQ_VIEW_MAIN },
1107         { 'd',          REQ_VIEW_DIFF },
1108         { 'l',          REQ_VIEW_LOG },
1109         { 't',          REQ_VIEW_TREE },
1110         { 'f',          REQ_VIEW_BLOB },
1111         { 'B',          REQ_VIEW_BLAME },
1112         { 'H',          REQ_VIEW_BRANCH },
1113         { 'p',          REQ_VIEW_PAGER },
1114         { 'h',          REQ_VIEW_HELP },
1115         { 'S',          REQ_VIEW_STATUS },
1116         { 'c',          REQ_VIEW_STAGE },
1118         /* View manipulation */
1119         { 'q',          REQ_VIEW_CLOSE },
1120         { KEY_TAB,      REQ_VIEW_NEXT },
1121         { KEY_RETURN,   REQ_ENTER },
1122         { KEY_UP,       REQ_PREVIOUS },
1123         { KEY_DOWN,     REQ_NEXT },
1124         { 'R',          REQ_REFRESH },
1125         { KEY_F(5),     REQ_REFRESH },
1126         { 'O',          REQ_MAXIMIZE },
1128         /* Cursor navigation */
1129         { 'k',          REQ_MOVE_UP },
1130         { 'j',          REQ_MOVE_DOWN },
1131         { KEY_HOME,     REQ_MOVE_FIRST_LINE },
1132         { KEY_END,      REQ_MOVE_LAST_LINE },
1133         { KEY_NPAGE,    REQ_MOVE_PAGE_DOWN },
1134         { ' ',          REQ_MOVE_PAGE_DOWN },
1135         { KEY_PPAGE,    REQ_MOVE_PAGE_UP },
1136         { 'b',          REQ_MOVE_PAGE_UP },
1137         { '-',          REQ_MOVE_PAGE_UP },
1139         /* Scrolling */
1140         { KEY_LEFT,     REQ_SCROLL_LEFT },
1141         { KEY_RIGHT,    REQ_SCROLL_RIGHT },
1142         { KEY_IC,       REQ_SCROLL_LINE_UP },
1143         { KEY_DC,       REQ_SCROLL_LINE_DOWN },
1144         { 'w',          REQ_SCROLL_PAGE_UP },
1145         { 's',          REQ_SCROLL_PAGE_DOWN },
1147         /* Searching */
1148         { '/',          REQ_SEARCH },
1149         { '?',          REQ_SEARCH_BACK },
1150         { 'n',          REQ_FIND_NEXT },
1151         { 'N',          REQ_FIND_PREV },
1153         /* Misc */
1154         { 'Q',          REQ_QUIT },
1155         { 'z',          REQ_STOP_LOADING },
1156         { 'v',          REQ_SHOW_VERSION },
1157         { 'r',          REQ_SCREEN_REDRAW },
1158         { 'o',          REQ_OPTIONS },
1159         { '.',          REQ_TOGGLE_LINENO },
1160         { 'D',          REQ_TOGGLE_DATE },
1161         { 'A',          REQ_TOGGLE_AUTHOR },
1162         { 'g',          REQ_TOGGLE_REV_GRAPH },
1163         { 'F',          REQ_TOGGLE_REFS },
1164         { 'I',          REQ_TOGGLE_SORT_ORDER },
1165         { 'i',          REQ_TOGGLE_SORT_FIELD },
1166         { ':',          REQ_PROMPT },
1167         { 'u',          REQ_STATUS_UPDATE },
1168         { '!',          REQ_STATUS_REVERT },
1169         { 'M',          REQ_STATUS_MERGE },
1170         { '@',          REQ_STAGE_NEXT },
1171         { ',',          REQ_PARENT },
1172         { 'e',          REQ_EDIT },
1173 };
1175 #define KEYMAP_INFO \
1176         KEYMAP_(GENERIC), \
1177         KEYMAP_(MAIN), \
1178         KEYMAP_(DIFF), \
1179         KEYMAP_(LOG), \
1180         KEYMAP_(TREE), \
1181         KEYMAP_(BLOB), \
1182         KEYMAP_(BLAME), \
1183         KEYMAP_(BRANCH), \
1184         KEYMAP_(PAGER), \
1185         KEYMAP_(HELP), \
1186         KEYMAP_(STATUS), \
1187         KEYMAP_(STAGE)
1189 enum keymap {
1190 #define KEYMAP_(name) KEYMAP_##name
1191         KEYMAP_INFO
1192 #undef  KEYMAP_
1193 };
1195 static const struct enum_map keymap_table[] = {
1196 #define KEYMAP_(name) ENUM_MAP(#name, KEYMAP_##name)
1197         KEYMAP_INFO
1198 #undef  KEYMAP_
1199 };
1201 #define set_keymap(map, name) map_enum(map, keymap_table, name)
1203 struct keybinding_table {
1204         struct keybinding *data;
1205         size_t size;
1206 };
1208 static struct keybinding_table keybindings[ARRAY_SIZE(keymap_table)];
1210 static void
1211 add_keybinding(enum keymap keymap, enum request request, int key)
1213         struct keybinding_table *table = &keybindings[keymap];
1215         table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data));
1216         if (!table->data)
1217                 die("Failed to allocate keybinding");
1218         table->data[table->size].alias = key;
1219         table->data[table->size++].request = request;
1222 /* Looks for a key binding first in the given map, then in the generic map, and
1223  * lastly in the default keybindings. */
1224 static enum request
1225 get_keybinding(enum keymap keymap, int key)
1227         size_t i;
1229         for (i = 0; i < keybindings[keymap].size; i++)
1230                 if (keybindings[keymap].data[i].alias == key)
1231                         return keybindings[keymap].data[i].request;
1233         for (i = 0; i < keybindings[KEYMAP_GENERIC].size; i++)
1234                 if (keybindings[KEYMAP_GENERIC].data[i].alias == key)
1235                         return keybindings[KEYMAP_GENERIC].data[i].request;
1237         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1238                 if (default_keybindings[i].alias == key)
1239                         return default_keybindings[i].request;
1241         return (enum request) key;
1245 struct key {
1246         const char *name;
1247         int value;
1248 };
1250 static const struct key key_table[] = {
1251         { "Enter",      KEY_RETURN },
1252         { "Space",      ' ' },
1253         { "Backspace",  KEY_BACKSPACE },
1254         { "Tab",        KEY_TAB },
1255         { "Escape",     KEY_ESC },
1256         { "Left",       KEY_LEFT },
1257         { "Right",      KEY_RIGHT },
1258         { "Up",         KEY_UP },
1259         { "Down",       KEY_DOWN },
1260         { "Insert",     KEY_IC },
1261         { "Delete",     KEY_DC },
1262         { "Hash",       '#' },
1263         { "Home",       KEY_HOME },
1264         { "End",        KEY_END },
1265         { "PageUp",     KEY_PPAGE },
1266         { "PageDown",   KEY_NPAGE },
1267         { "F1",         KEY_F(1) },
1268         { "F2",         KEY_F(2) },
1269         { "F3",         KEY_F(3) },
1270         { "F4",         KEY_F(4) },
1271         { "F5",         KEY_F(5) },
1272         { "F6",         KEY_F(6) },
1273         { "F7",         KEY_F(7) },
1274         { "F8",         KEY_F(8) },
1275         { "F9",         KEY_F(9) },
1276         { "F10",        KEY_F(10) },
1277         { "F11",        KEY_F(11) },
1278         { "F12",        KEY_F(12) },
1279 };
1281 static int
1282 get_key_value(const char *name)
1284         int i;
1286         for (i = 0; i < ARRAY_SIZE(key_table); i++)
1287                 if (!strcasecmp(key_table[i].name, name))
1288                         return key_table[i].value;
1290         if (strlen(name) == 1 && isprint(*name))
1291                 return (int) *name;
1293         return ERR;
1296 static const char *
1297 get_key_name(int key_value)
1299         static char key_char[] = "'X'";
1300         const char *seq = NULL;
1301         int key;
1303         for (key = 0; key < ARRAY_SIZE(key_table); key++)
1304                 if (key_table[key].value == key_value)
1305                         seq = key_table[key].name;
1307         if (seq == NULL &&
1308             key_value < 127 &&
1309             isprint(key_value)) {
1310                 key_char[1] = (char) key_value;
1311                 seq = key_char;
1312         }
1314         return seq ? seq : "(no key)";
1317 static const char *
1318 get_key(enum request request)
1320         static char buf[BUFSIZ];
1321         size_t pos = 0;
1322         char *sep = "";
1323         int i;
1325         buf[pos] = 0;
1327         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1328                 const struct keybinding *keybinding = &default_keybindings[i];
1330                 if (keybinding->request != request)
1331                         continue;
1333                 if (!string_format_from(buf, &pos, "%s%s", sep,
1334                                         get_key_name(keybinding->alias)))
1335                         return "Too many keybindings!";
1336                 sep = ", ";
1337         }
1339         return buf;
1342 struct run_request {
1343         enum keymap keymap;
1344         int key;
1345         const char *argv[SIZEOF_ARG];
1346 };
1348 static struct run_request *run_request;
1349 static size_t run_requests;
1351 DEFINE_ALLOCATOR(realloc_run_requests, struct run_request, 8)
1353 static enum request
1354 add_run_request(enum keymap keymap, int key, int argc, const char **argv)
1356         struct run_request *req;
1358         if (argc >= ARRAY_SIZE(req->argv) - 1)
1359                 return REQ_NONE;
1361         if (!realloc_run_requests(&run_request, run_requests, 1))
1362                 return REQ_NONE;
1364         req = &run_request[run_requests];
1365         req->keymap = keymap;
1366         req->key = key;
1367         req->argv[0] = NULL;
1369         if (!format_argv(req->argv, argv, FORMAT_NONE))
1370                 return REQ_NONE;
1372         return REQ_NONE + ++run_requests;
1375 static struct run_request *
1376 get_run_request(enum request request)
1378         if (request <= REQ_NONE)
1379                 return NULL;
1380         return &run_request[request - REQ_NONE - 1];
1383 static void
1384 add_builtin_run_requests(void)
1386         const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1387         const char *commit[] = { "git", "commit", NULL };
1388         const char *gc[] = { "git", "gc", NULL };
1389         struct {
1390                 enum keymap keymap;
1391                 int key;
1392                 int argc;
1393                 const char **argv;
1394         } reqs[] = {
1395                 { KEYMAP_MAIN,    'C', ARRAY_SIZE(cherry_pick) - 1, cherry_pick },
1396                 { KEYMAP_STATUS,  'C', ARRAY_SIZE(commit) - 1, commit },
1397                 { KEYMAP_GENERIC, 'G', ARRAY_SIZE(gc) - 1, gc },
1398         };
1399         int i;
1401         for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1402                 enum request req;
1404                 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argc, reqs[i].argv);
1405                 if (req != REQ_NONE)
1406                         add_keybinding(reqs[i].keymap, req, reqs[i].key);
1407         }
1410 /*
1411  * User config file handling.
1412  */
1414 static int   config_lineno;
1415 static bool  config_errors;
1416 static const char *config_msg;
1418 static const struct enum_map color_map[] = {
1419 #define COLOR_MAP(name) ENUM_MAP(#name, COLOR_##name)
1420         COLOR_MAP(DEFAULT),
1421         COLOR_MAP(BLACK),
1422         COLOR_MAP(BLUE),
1423         COLOR_MAP(CYAN),
1424         COLOR_MAP(GREEN),
1425         COLOR_MAP(MAGENTA),
1426         COLOR_MAP(RED),
1427         COLOR_MAP(WHITE),
1428         COLOR_MAP(YELLOW),
1429 };
1431 static const struct enum_map attr_map[] = {
1432 #define ATTR_MAP(name) ENUM_MAP(#name, A_##name)
1433         ATTR_MAP(NORMAL),
1434         ATTR_MAP(BLINK),
1435         ATTR_MAP(BOLD),
1436         ATTR_MAP(DIM),
1437         ATTR_MAP(REVERSE),
1438         ATTR_MAP(STANDOUT),
1439         ATTR_MAP(UNDERLINE),
1440 };
1442 #define set_attribute(attr, name)       map_enum(attr, attr_map, name)
1444 static int parse_step(double *opt, const char *arg)
1446         *opt = atoi(arg);
1447         if (!strchr(arg, '%'))
1448                 return OK;
1450         /* "Shift down" so 100% and 1 does not conflict. */
1451         *opt = (*opt - 1) / 100;
1452         if (*opt >= 1.0) {
1453                 *opt = 0.99;
1454                 config_msg = "Step value larger than 100%";
1455                 return ERR;
1456         }
1457         if (*opt < 0.0) {
1458                 *opt = 1;
1459                 config_msg = "Invalid step value";
1460                 return ERR;
1461         }
1462         return OK;
1465 static int
1466 parse_int(int *opt, const char *arg, int min, int max)
1468         int value = atoi(arg);
1470         if (min <= value && value <= max) {
1471                 *opt = value;
1472                 return OK;
1473         }
1475         config_msg = "Integer value out of bound";
1476         return ERR;
1479 static bool
1480 set_color(int *color, const char *name)
1482         if (map_enum(color, color_map, name))
1483                 return TRUE;
1484         if (!prefixcmp(name, "color"))
1485                 return parse_int(color, name + 5, 0, 255) == OK;
1486         return FALSE;
1489 /* Wants: object fgcolor bgcolor [attribute] */
1490 static int
1491 option_color_command(int argc, const char *argv[])
1493         struct line_info *info;
1495         if (argc != 3 && argc != 4) {
1496                 config_msg = "Wrong number of arguments given to color command";
1497                 return ERR;
1498         }
1500         info = get_line_info(argv[0]);
1501         if (!info) {
1502                 static const struct enum_map obsolete[] = {
1503                         ENUM_MAP("main-delim",  LINE_DELIMITER),
1504                         ENUM_MAP("main-date",   LINE_DATE),
1505                         ENUM_MAP("main-author", LINE_AUTHOR),
1506                 };
1507                 int index;
1509                 if (!map_enum(&index, obsolete, argv[0])) {
1510                         config_msg = "Unknown color name";
1511                         return ERR;
1512                 }
1513                 info = &line_info[index];
1514         }
1516         if (!set_color(&info->fg, argv[1]) ||
1517             !set_color(&info->bg, argv[2])) {
1518                 config_msg = "Unknown color";
1519                 return ERR;
1520         }
1522         if (argc == 4 && !set_attribute(&info->attr, argv[3])) {
1523                 config_msg = "Unknown attribute";
1524                 return ERR;
1525         }
1527         return OK;
1530 static int parse_bool(bool *opt, const char *arg)
1532         *opt = (!strcmp(arg, "1") || !strcmp(arg, "true") || !strcmp(arg, "yes"))
1533                 ? TRUE : FALSE;
1534         return OK;
1537 static int
1538 parse_string(char *opt, const char *arg, size_t optsize)
1540         int arglen = strlen(arg);
1542         switch (arg[0]) {
1543         case '\"':
1544         case '\'':
1545                 if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1546                         config_msg = "Unmatched quotation";
1547                         return ERR;
1548                 }
1549                 arg += 1; arglen -= 2;
1550         default:
1551                 string_ncopy_do(opt, optsize, arg, arglen);
1552                 return OK;
1553         }
1556 /* Wants: name = value */
1557 static int
1558 option_set_command(int argc, const char *argv[])
1560         if (argc != 3) {
1561                 config_msg = "Wrong number of arguments given to set command";
1562                 return ERR;
1563         }
1565         if (strcmp(argv[1], "=")) {
1566                 config_msg = "No value assigned";
1567                 return ERR;
1568         }
1570         if (!strcmp(argv[0], "show-author"))
1571                 return parse_bool(&opt_author, argv[2]);
1573         if (!strcmp(argv[0], "show-date"))
1574                 return parse_bool(&opt_date, argv[2]);
1576         if (!strcmp(argv[0], "show-rev-graph"))
1577                 return parse_bool(&opt_rev_graph, argv[2]);
1579         if (!strcmp(argv[0], "show-refs"))
1580                 return parse_bool(&opt_show_refs, argv[2]);
1582         if (!strcmp(argv[0], "show-line-numbers"))
1583                 return parse_bool(&opt_line_number, argv[2]);
1585         if (!strcmp(argv[0], "line-graphics"))
1586                 return parse_bool(&opt_line_graphics, argv[2]);
1588         if (!strcmp(argv[0], "line-number-interval"))
1589                 return parse_int(&opt_num_interval, argv[2], 1, 1024);
1591         if (!strcmp(argv[0], "author-width"))
1592                 return parse_int(&opt_author_cols, argv[2], 0, 1024);
1594         if (!strcmp(argv[0], "horizontal-scroll"))
1595                 return parse_step(&opt_hscroll, argv[2]);
1597         if (!strcmp(argv[0], "tab-size"))
1598                 return parse_int(&opt_tab_size, argv[2], 1, 1024);
1600         if (!strcmp(argv[0], "commit-encoding"))
1601                 return parse_string(opt_encoding, argv[2], sizeof(opt_encoding));
1603         config_msg = "Unknown variable name";
1604         return ERR;
1607 /* Wants: mode request key */
1608 static int
1609 option_bind_command(int argc, const char *argv[])
1611         enum request request;
1612         int keymap;
1613         int key;
1615         if (argc < 3) {
1616                 config_msg = "Wrong number of arguments given to bind command";
1617                 return ERR;
1618         }
1620         if (set_keymap(&keymap, argv[0]) == ERR) {
1621                 config_msg = "Unknown key map";
1622                 return ERR;
1623         }
1625         key = get_key_value(argv[1]);
1626         if (key == ERR) {
1627                 config_msg = "Unknown key";
1628                 return ERR;
1629         }
1631         request = get_request(argv[2]);
1632         if (request == REQ_NONE) {
1633                 static const struct enum_map obsolete[] = {
1634                         ENUM_MAP("cherry-pick",         REQ_NONE),
1635                         ENUM_MAP("screen-resize",       REQ_NONE),
1636                         ENUM_MAP("tree-parent",         REQ_PARENT),
1637                 };
1638                 int alias;
1640                 if (map_enum(&alias, obsolete, argv[2])) {
1641                         if (alias != REQ_NONE)
1642                                 add_keybinding(keymap, alias, key);
1643                         config_msg = "Obsolete request name";
1644                         return ERR;
1645                 }
1646         }
1647         if (request == REQ_NONE && *argv[2]++ == '!')
1648                 request = add_run_request(keymap, key, argc - 2, argv + 2);
1649         if (request == REQ_NONE) {
1650                 config_msg = "Unknown request name";
1651                 return ERR;
1652         }
1654         add_keybinding(keymap, request, key);
1656         return OK;
1659 static int
1660 set_option(const char *opt, char *value)
1662         const char *argv[SIZEOF_ARG];
1663         int argc = 0;
1665         if (!argv_from_string(argv, &argc, value)) {
1666                 config_msg = "Too many option arguments";
1667                 return ERR;
1668         }
1670         if (!strcmp(opt, "color"))
1671                 return option_color_command(argc, argv);
1673         if (!strcmp(opt, "set"))
1674                 return option_set_command(argc, argv);
1676         if (!strcmp(opt, "bind"))
1677                 return option_bind_command(argc, argv);
1679         config_msg = "Unknown option command";
1680         return ERR;
1683 static int
1684 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
1686         int status = OK;
1688         config_lineno++;
1689         config_msg = "Internal error";
1691         /* Check for comment markers, since read_properties() will
1692          * only ensure opt and value are split at first " \t". */
1693         optlen = strcspn(opt, "#");
1694         if (optlen == 0)
1695                 return OK;
1697         if (opt[optlen] != 0) {
1698                 config_msg = "No option value";
1699                 status = ERR;
1701         }  else {
1702                 /* Look for comment endings in the value. */
1703                 size_t len = strcspn(value, "#");
1705                 if (len < valuelen) {
1706                         valuelen = len;
1707                         value[valuelen] = 0;
1708                 }
1710                 status = set_option(opt, value);
1711         }
1713         if (status == ERR) {
1714                 warn("Error on line %d, near '%.*s': %s",
1715                      config_lineno, (int) optlen, opt, config_msg);
1716                 config_errors = TRUE;
1717         }
1719         /* Always keep going if errors are encountered. */
1720         return OK;
1723 static void
1724 load_option_file(const char *path)
1726         struct io io = {};
1728         /* It's OK that the file doesn't exist. */
1729         if (!io_open(&io, path))
1730                 return;
1732         config_lineno = 0;
1733         config_errors = FALSE;
1735         if (io_load(&io, " \t", read_option) == ERR ||
1736             config_errors == TRUE)
1737                 warn("Errors while loading %s.", path);
1740 static int
1741 load_options(void)
1743         const char *home = getenv("HOME");
1744         const char *tigrc_user = getenv("TIGRC_USER");
1745         const char *tigrc_system = getenv("TIGRC_SYSTEM");
1746         char buf[SIZEOF_STR];
1748         add_builtin_run_requests();
1750         if (!tigrc_system)
1751                 tigrc_system = SYSCONFDIR "/tigrc";
1752         load_option_file(tigrc_system);
1754         if (!tigrc_user) {
1755                 if (!home || !string_format(buf, "%s/.tigrc", home))
1756                         return ERR;
1757                 tigrc_user = buf;
1758         }
1759         load_option_file(tigrc_user);
1761         return OK;
1765 /*
1766  * The viewer
1767  */
1769 struct view;
1770 struct view_ops;
1772 /* The display array of active views and the index of the current view. */
1773 static struct view *display[2];
1774 static unsigned int current_view;
1776 #define foreach_displayed_view(view, i) \
1777         for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1779 #define displayed_views()       (display[1] != NULL ? 2 : 1)
1781 /* Current head and commit ID */
1782 static char ref_blob[SIZEOF_REF]        = "";
1783 static char ref_commit[SIZEOF_REF]      = "HEAD";
1784 static char ref_head[SIZEOF_REF]        = "HEAD";
1786 struct view {
1787         const char *name;       /* View name */
1788         const char *cmd_env;    /* Command line set via environment */
1789         const char *id;         /* Points to either of ref_{head,commit,blob} */
1791         struct view_ops *ops;   /* View operations */
1793         enum keymap keymap;     /* What keymap does this view have */
1794         bool git_dir;           /* Whether the view requires a git directory. */
1796         char ref[SIZEOF_REF];   /* Hovered commit reference */
1797         char vid[SIZEOF_REF];   /* View ID. Set to id member when updating. */
1799         int height, width;      /* The width and height of the main window */
1800         WINDOW *win;            /* The main window */
1801         WINDOW *title;          /* The title window living below the main window */
1803         /* Navigation */
1804         unsigned long offset;   /* Offset of the window top */
1805         unsigned long yoffset;  /* Offset from the window side. */
1806         unsigned long lineno;   /* Current line number */
1807         unsigned long p_offset; /* Previous offset of the window top */
1808         unsigned long p_yoffset;/* Previous offset from the window side */
1809         unsigned long p_lineno; /* Previous current line number */
1810         bool p_restore;         /* Should the previous position be restored. */
1812         /* Searching */
1813         char grep[SIZEOF_STR];  /* Search string */
1814         regex_t *regex;         /* Pre-compiled regexp */
1816         /* If non-NULL, points to the view that opened this view. If this view
1817          * is closed tig will switch back to the parent view. */
1818         struct view *parent;
1820         /* Buffering */
1821         size_t lines;           /* Total number of lines */
1822         struct line *line;      /* Line index */
1823         unsigned int digits;    /* Number of digits in the lines member. */
1825         /* Drawing */
1826         struct line *curline;   /* Line currently being drawn. */
1827         enum line_type curtype; /* Attribute currently used for drawing. */
1828         unsigned long col;      /* Column when drawing. */
1829         bool has_scrolled;      /* View was scrolled. */
1831         /* Loading */
1832         struct io io;
1833         struct io *pipe;
1834         time_t start_time;
1835         time_t update_secs;
1836 };
1838 struct view_ops {
1839         /* What type of content being displayed. Used in the title bar. */
1840         const char *type;
1841         /* Default command arguments. */
1842         const char **argv;
1843         /* Open and reads in all view content. */
1844         bool (*open)(struct view *view);
1845         /* Read one line; updates view->line. */
1846         bool (*read)(struct view *view, char *data);
1847         /* Draw one line; @lineno must be < view->height. */
1848         bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
1849         /* Depending on view handle a special requests. */
1850         enum request (*request)(struct view *view, enum request request, struct line *line);
1851         /* Search for regexp in a line. */
1852         bool (*grep)(struct view *view, struct line *line);
1853         /* Select line */
1854         void (*select)(struct view *view, struct line *line);
1855 };
1857 static struct view_ops blame_ops;
1858 static struct view_ops blob_ops;
1859 static struct view_ops diff_ops;
1860 static struct view_ops help_ops;
1861 static struct view_ops log_ops;
1862 static struct view_ops main_ops;
1863 static struct view_ops pager_ops;
1864 static struct view_ops stage_ops;
1865 static struct view_ops status_ops;
1866 static struct view_ops tree_ops;
1867 static struct view_ops branch_ops;
1869 #define VIEW_STR(name, env, ref, ops, map, git) \
1870         { name, #env, ref, ops, map, git }
1872 #define VIEW_(id, name, ops, git, ref) \
1873         VIEW_STR(name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
1876 static struct view views[] = {
1877         VIEW_(MAIN,   "main",   &main_ops,   TRUE,  ref_head),
1878         VIEW_(DIFF,   "diff",   &diff_ops,   TRUE,  ref_commit),
1879         VIEW_(LOG,    "log",    &log_ops,    TRUE,  ref_head),
1880         VIEW_(TREE,   "tree",   &tree_ops,   TRUE,  ref_commit),
1881         VIEW_(BLOB,   "blob",   &blob_ops,   TRUE,  ref_blob),
1882         VIEW_(BLAME,  "blame",  &blame_ops,  TRUE,  ref_commit),
1883         VIEW_(BRANCH, "branch", &branch_ops, TRUE,  ref_head),
1884         VIEW_(HELP,   "help",   &help_ops,   FALSE, ""),
1885         VIEW_(PAGER,  "pager",  &pager_ops,  FALSE, "stdin"),
1886         VIEW_(STATUS, "status", &status_ops, TRUE,  ""),
1887         VIEW_(STAGE,  "stage",  &stage_ops,  TRUE,  ""),
1888 };
1890 #define VIEW(req)       (&views[(req) - REQ_OFFSET - 1])
1891 #define VIEW_REQ(view)  ((view) - views + REQ_OFFSET + 1)
1893 #define foreach_view(view, i) \
1894         for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
1896 #define view_is_displayed(view) \
1897         (view == display[0] || view == display[1])
1900 enum line_graphic {
1901         LINE_GRAPHIC_VLINE
1902 };
1904 static chtype line_graphics[] = {
1905         /* LINE_GRAPHIC_VLINE: */ '|'
1906 };
1908 static inline void
1909 set_view_attr(struct view *view, enum line_type type)
1911         if (!view->curline->selected && view->curtype != type) {
1912                 wattrset(view->win, get_line_attr(type));
1913                 wchgat(view->win, -1, 0, type, NULL);
1914                 view->curtype = type;
1915         }
1918 static int
1919 draw_chars(struct view *view, enum line_type type, const char *string,
1920            int max_len, bool use_tilde)
1922         int len = 0;
1923         int col = 0;
1924         int trimmed = FALSE;
1925         size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
1927         if (max_len <= 0)
1928                 return 0;
1930         if (opt_utf8) {
1931                 len = utf8_length(&string, skip, &col, max_len, &trimmed, use_tilde);
1932         } else {
1933                 col = len = strlen(string);
1934                 if (len > max_len) {
1935                         if (use_tilde) {
1936                                 max_len -= 1;
1937                         }
1938                         col = len = max_len;
1939                         trimmed = TRUE;
1940                 }
1941         }
1943         set_view_attr(view, type);
1944         if (len > 0)
1945                 waddnstr(view->win, string, len);
1946         if (trimmed && use_tilde) {
1947                 set_view_attr(view, LINE_DELIMITER);
1948                 waddch(view->win, '~');
1949                 col++;
1950         }
1952         return col;
1955 static int
1956 draw_space(struct view *view, enum line_type type, int max, int spaces)
1958         static char space[] = "                    ";
1959         int col = 0;
1961         spaces = MIN(max, spaces);
1963         while (spaces > 0) {
1964                 int len = MIN(spaces, sizeof(space) - 1);
1966                 col += draw_chars(view, type, space, len, FALSE);
1967                 spaces -= len;
1968         }
1970         return col;
1973 static bool
1974 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
1976         view->col += draw_chars(view, type, string, view->width + view->yoffset - view->col, trim);
1977         return view->width + view->yoffset <= view->col;
1980 static bool
1981 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
1983         size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
1984         int max = view->width + view->yoffset - view->col;
1985         int i;
1987         if (max < size)
1988                 size = max;
1990         set_view_attr(view, type);
1991         /* Using waddch() instead of waddnstr() ensures that
1992          * they'll be rendered correctly for the cursor line. */
1993         for (i = skip; i < size; i++)
1994                 waddch(view->win, graphic[i]);
1996         view->col += size;
1997         if (size < max && skip <= size)
1998                 waddch(view->win, ' ');
1999         view->col++;
2001         return view->width + view->yoffset <= view->col;
2004 static bool
2005 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
2007         int max = MIN(view->width + view->yoffset - view->col, len);
2008         int col;
2010         if (text)
2011                 col = draw_chars(view, type, text, max - 1, trim);
2012         else
2013                 col = draw_space(view, type, max - 1, max - 1);
2015         view->col += col;
2016         view->col += draw_space(view, LINE_DEFAULT, max - col, max - col);
2017         return view->width + view->yoffset <= view->col;
2020 static bool
2021 draw_date(struct view *view, time_t *time)
2023         const char *date = mkdate(time);
2025         return draw_field(view, LINE_DATE, date, DATE_COLS, FALSE);
2028 static bool
2029 draw_author(struct view *view, const char *author)
2031         bool trim = opt_author_cols == 0 || opt_author_cols > 5 || !author;
2033         if (!trim) {
2034                 static char initials[10];
2035                 size_t pos;
2037 #define is_initial_sep(c) (isspace(c) || ispunct(c) || (c) == '@')
2039                 memset(initials, 0, sizeof(initials));
2040                 for (pos = 0; *author && pos < opt_author_cols - 1; author++, pos++) {
2041                         while (is_initial_sep(*author))
2042                                 author++;
2043                         strncpy(&initials[pos], author, sizeof(initials) - 1 - pos);
2044                         while (*author && !is_initial_sep(author[1]))
2045                                 author++;
2046                 }
2048                 author = initials;
2049         }
2051         return draw_field(view, LINE_AUTHOR, author, opt_author_cols, trim);
2054 static bool
2055 draw_mode(struct view *view, mode_t mode)
2057         const char *str;
2059         if (S_ISDIR(mode))
2060                 str = "drwxr-xr-x";
2061         else if (S_ISLNK(mode))
2062                 str = "lrwxrwxrwx";
2063         else if (S_ISGITLINK(mode))
2064                 str = "m---------";
2065         else if (S_ISREG(mode) && mode & S_IXUSR)
2066                 str = "-rwxr-xr-x";
2067         else if (S_ISREG(mode))
2068                 str = "-rw-r--r--";
2069         else
2070                 str = "----------";
2072         return draw_field(view, LINE_MODE, str, STRING_SIZE("-rw-r--r-- "), FALSE);
2075 static bool
2076 draw_lineno(struct view *view, unsigned int lineno)
2078         char number[10];
2079         int digits3 = view->digits < 3 ? 3 : view->digits;
2080         int max = MIN(view->width + view->yoffset - view->col, digits3);
2081         char *text = NULL;
2083         lineno += view->offset + 1;
2084         if (lineno == 1 || (lineno % opt_num_interval) == 0) {
2085                 static char fmt[] = "%1ld";
2087                 fmt[1] = '0' + (view->digits <= 9 ? digits3 : 1);
2088                 if (string_format(number, fmt, lineno))
2089                         text = number;
2090         }
2091         if (text)
2092                 view->col += draw_chars(view, LINE_LINE_NUMBER, text, max, TRUE);
2093         else
2094                 view->col += draw_space(view, LINE_LINE_NUMBER, max, digits3);
2095         return draw_graphic(view, LINE_DEFAULT, &line_graphics[LINE_GRAPHIC_VLINE], 1);
2098 static bool
2099 draw_view_line(struct view *view, unsigned int lineno)
2101         struct line *line;
2102         bool selected = (view->offset + lineno == view->lineno);
2104         assert(view_is_displayed(view));
2106         if (view->offset + lineno >= view->lines)
2107                 return FALSE;
2109         line = &view->line[view->offset + lineno];
2111         wmove(view->win, lineno, 0);
2112         if (line->cleareol)
2113                 wclrtoeol(view->win);
2114         view->col = 0;
2115         view->curline = line;
2116         view->curtype = LINE_NONE;
2117         line->selected = FALSE;
2118         line->dirty = line->cleareol = 0;
2120         if (selected) {
2121                 set_view_attr(view, LINE_CURSOR);
2122                 line->selected = TRUE;
2123                 view->ops->select(view, line);
2124         }
2126         return view->ops->draw(view, line, lineno);
2129 static void
2130 redraw_view_dirty(struct view *view)
2132         bool dirty = FALSE;
2133         int lineno;
2135         for (lineno = 0; lineno < view->height; lineno++) {
2136                 if (view->offset + lineno >= view->lines)
2137                         break;
2138                 if (!view->line[view->offset + lineno].dirty)
2139                         continue;
2140                 dirty = TRUE;
2141                 if (!draw_view_line(view, lineno))
2142                         break;
2143         }
2145         if (!dirty)
2146                 return;
2147         wnoutrefresh(view->win);
2150 static void
2151 redraw_view_from(struct view *view, int lineno)
2153         assert(0 <= lineno && lineno < view->height);
2155         for (; lineno < view->height; lineno++) {
2156                 if (!draw_view_line(view, lineno))
2157                         break;
2158         }
2160         wnoutrefresh(view->win);
2163 static void
2164 redraw_view(struct view *view)
2166         werase(view->win);
2167         redraw_view_from(view, 0);
2171 static void
2172 update_view_title(struct view *view)
2174         char buf[SIZEOF_STR];
2175         char state[SIZEOF_STR];
2176         size_t bufpos = 0, statelen = 0;
2178         assert(view_is_displayed(view));
2180         if (view != VIEW(REQ_VIEW_STATUS) && view->lines) {
2181                 unsigned int view_lines = view->offset + view->height;
2182                 unsigned int lines = view->lines
2183                                    ? MIN(view_lines, view->lines) * 100 / view->lines
2184                                    : 0;
2186                 string_format_from(state, &statelen, " - %s %d of %d (%d%%)",
2187                                    view->ops->type,
2188                                    view->lineno + 1,
2189                                    view->lines,
2190                                    lines);
2192         }
2194         if (view->pipe) {
2195                 time_t secs = time(NULL) - view->start_time;
2197                 /* Three git seconds are a long time ... */
2198                 if (secs > 2)
2199                         string_format_from(state, &statelen, " loading %lds", secs);
2200         }
2202         string_format_from(buf, &bufpos, "[%s]", view->name);
2203         if (*view->ref && bufpos < view->width) {
2204                 size_t refsize = strlen(view->ref);
2205                 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2207                 if (minsize < view->width)
2208                         refsize = view->width - minsize + 7;
2209                 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2210         }
2212         if (statelen && bufpos < view->width) {
2213                 string_format_from(buf, &bufpos, "%s", state);
2214         }
2216         if (view == display[current_view])
2217                 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2218         else
2219                 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2221         mvwaddnstr(view->title, 0, 0, buf, bufpos);
2222         wclrtoeol(view->title);
2223         wnoutrefresh(view->title);
2226 static void
2227 resize_display(void)
2229         int offset, i;
2230         struct view *base = display[0];
2231         struct view *view = display[1] ? display[1] : display[0];
2233         /* Setup window dimensions */
2235         getmaxyx(stdscr, base->height, base->width);
2237         /* Make room for the status window. */
2238         base->height -= 1;
2240         if (view != base) {
2241                 /* Horizontal split. */
2242                 view->width   = base->width;
2243                 view->height  = SCALE_SPLIT_VIEW(base->height);
2244                 base->height -= view->height;
2246                 /* Make room for the title bar. */
2247                 view->height -= 1;
2248         }
2250         /* Make room for the title bar. */
2251         base->height -= 1;
2253         offset = 0;
2255         foreach_displayed_view (view, i) {
2256                 if (!view->win) {
2257                         view->win = newwin(view->height, 0, offset, 0);
2258                         if (!view->win)
2259                                 die("Failed to create %s view", view->name);
2261                         scrollok(view->win, FALSE);
2263                         view->title = newwin(1, 0, offset + view->height, 0);
2264                         if (!view->title)
2265                                 die("Failed to create title window");
2267                 } else {
2268                         wresize(view->win, view->height, view->width);
2269                         mvwin(view->win,   offset, 0);
2270                         mvwin(view->title, offset + view->height, 0);
2271                 }
2273                 offset += view->height + 1;
2274         }
2277 static void
2278 redraw_display(bool clear)
2280         struct view *view;
2281         int i;
2283         foreach_displayed_view (view, i) {
2284                 if (clear)
2285                         wclear(view->win);
2286                 redraw_view(view);
2287                 update_view_title(view);
2288         }
2291 static void
2292 toggle_view_option(bool *option, const char *help)
2294         *option = !*option;
2295         redraw_display(FALSE);
2296         report("%sabling %s", *option ? "En" : "Dis", help);
2299 static void
2300 open_option_menu(void)
2302         const struct menu_item menu[] = {
2303                 { '.', "line numbers", &opt_line_number },
2304                 { 'D', "date display", &opt_date },
2305                 { 'A', "author display", &opt_author },
2306                 { 'g', "revision graph display", &opt_rev_graph },
2307                 { 'F', "reference display", &opt_show_refs },
2308                 { 0 }
2309         };
2310         int selected = 0;
2312         if (prompt_menu("Toggle option", menu, &selected))
2313                 toggle_view_option(menu[selected].data, menu[selected].text);
2316 static void
2317 maximize_view(struct view *view)
2319         memset(display, 0, sizeof(display));
2320         current_view = 0;
2321         display[current_view] = view;
2322         resize_display();
2323         redraw_display(FALSE);
2324         report("");
2328 /*
2329  * Navigation
2330  */
2332 static bool
2333 goto_view_line(struct view *view, unsigned long offset, unsigned long lineno)
2335         if (lineno >= view->lines)
2336                 lineno = view->lines > 0 ? view->lines - 1 : 0;
2338         if (offset > lineno || offset + view->height <= lineno) {
2339                 unsigned long half = view->height / 2;
2341                 if (lineno > half)
2342                         offset = lineno - half;
2343                 else
2344                         offset = 0;
2345         }
2347         if (offset != view->offset || lineno != view->lineno) {
2348                 view->offset = offset;
2349                 view->lineno = lineno;
2350                 return TRUE;
2351         }
2353         return FALSE;
2356 static int
2357 apply_step(double step, int value)
2359         if (step >= 1)
2360                 return (int) step;
2361         value *= step + 0.01;
2362         return value ? value : 1;
2365 /* Scrolling backend */
2366 static void
2367 do_scroll_view(struct view *view, int lines)
2369         bool redraw_current_line = FALSE;
2371         /* The rendering expects the new offset. */
2372         view->offset += lines;
2374         assert(0 <= view->offset && view->offset < view->lines);
2375         assert(lines);
2377         /* Move current line into the view. */
2378         if (view->lineno < view->offset) {
2379                 view->lineno = view->offset;
2380                 redraw_current_line = TRUE;
2381         } else if (view->lineno >= view->offset + view->height) {
2382                 view->lineno = view->offset + view->height - 1;
2383                 redraw_current_line = TRUE;
2384         }
2386         assert(view->offset <= view->lineno && view->lineno < view->lines);
2388         /* Redraw the whole screen if scrolling is pointless. */
2389         if (view->height < ABS(lines)) {
2390                 redraw_view(view);
2392         } else {
2393                 int line = lines > 0 ? view->height - lines : 0;
2394                 int end = line + ABS(lines);
2396                 scrollok(view->win, TRUE);
2397                 wscrl(view->win, lines);
2398                 scrollok(view->win, FALSE);
2400                 while (line < end && draw_view_line(view, line))
2401                         line++;
2403                 if (redraw_current_line)
2404                         draw_view_line(view, view->lineno - view->offset);
2405                 wnoutrefresh(view->win);
2406         }
2408         view->has_scrolled = TRUE;
2409         report("");
2412 /* Scroll frontend */
2413 static void
2414 scroll_view(struct view *view, enum request request)
2416         int lines = 1;
2418         assert(view_is_displayed(view));
2420         switch (request) {
2421         case REQ_SCROLL_LEFT:
2422                 if (view->yoffset == 0) {
2423                         report("Cannot scroll beyond the first column");
2424                         return;
2425                 }
2426                 if (view->yoffset <= apply_step(opt_hscroll, view->width))
2427                         view->yoffset = 0;
2428                 else
2429                         view->yoffset -= apply_step(opt_hscroll, view->width);
2430                 redraw_view_from(view, 0);
2431                 report("");
2432                 return;
2433         case REQ_SCROLL_RIGHT:
2434                 view->yoffset += apply_step(opt_hscroll, view->width);
2435                 redraw_view(view);
2436                 report("");
2437                 return;
2438         case REQ_SCROLL_PAGE_DOWN:
2439                 lines = view->height;
2440         case REQ_SCROLL_LINE_DOWN:
2441                 if (view->offset + lines > view->lines)
2442                         lines = view->lines - view->offset;
2444                 if (lines == 0 || view->offset + view->height >= view->lines) {
2445                         report("Cannot scroll beyond the last line");
2446                         return;
2447                 }
2448                 break;
2450         case REQ_SCROLL_PAGE_UP:
2451                 lines = view->height;
2452         case REQ_SCROLL_LINE_UP:
2453                 if (lines > view->offset)
2454                         lines = view->offset;
2456                 if (lines == 0) {
2457                         report("Cannot scroll beyond the first line");
2458                         return;
2459                 }
2461                 lines = -lines;
2462                 break;
2464         default:
2465                 die("request %d not handled in switch", request);
2466         }
2468         do_scroll_view(view, lines);
2471 /* Cursor moving */
2472 static void
2473 move_view(struct view *view, enum request request)
2475         int scroll_steps = 0;
2476         int steps;
2478         switch (request) {
2479         case REQ_MOVE_FIRST_LINE:
2480                 steps = -view->lineno;
2481                 break;
2483         case REQ_MOVE_LAST_LINE:
2484                 steps = view->lines - view->lineno - 1;
2485                 break;
2487         case REQ_MOVE_PAGE_UP:
2488                 steps = view->height > view->lineno
2489                       ? -view->lineno : -view->height;
2490                 break;
2492         case REQ_MOVE_PAGE_DOWN:
2493                 steps = view->lineno + view->height >= view->lines
2494                       ? view->lines - view->lineno - 1 : view->height;
2495                 break;
2497         case REQ_MOVE_UP:
2498                 steps = -1;
2499                 break;
2501         case REQ_MOVE_DOWN:
2502                 steps = 1;
2503                 break;
2505         default:
2506                 die("request %d not handled in switch", request);
2507         }
2509         if (steps <= 0 && view->lineno == 0) {
2510                 report("Cannot move beyond the first line");
2511                 return;
2513         } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2514                 report("Cannot move beyond the last line");
2515                 return;
2516         }
2518         /* Move the current line */
2519         view->lineno += steps;
2520         assert(0 <= view->lineno && view->lineno < view->lines);
2522         /* Check whether the view needs to be scrolled */
2523         if (view->lineno < view->offset ||
2524             view->lineno >= view->offset + view->height) {
2525                 scroll_steps = steps;
2526                 if (steps < 0 && -steps > view->offset) {
2527                         scroll_steps = -view->offset;
2529                 } else if (steps > 0) {
2530                         if (view->lineno == view->lines - 1 &&
2531                             view->lines > view->height) {
2532                                 scroll_steps = view->lines - view->offset - 1;
2533                                 if (scroll_steps >= view->height)
2534                                         scroll_steps -= view->height - 1;
2535                         }
2536                 }
2537         }
2539         if (!view_is_displayed(view)) {
2540                 view->offset += scroll_steps;
2541                 assert(0 <= view->offset && view->offset < view->lines);
2542                 view->ops->select(view, &view->line[view->lineno]);
2543                 return;
2544         }
2546         /* Repaint the old "current" line if we be scrolling */
2547         if (ABS(steps) < view->height)
2548                 draw_view_line(view, view->lineno - steps - view->offset);
2550         if (scroll_steps) {
2551                 do_scroll_view(view, scroll_steps);
2552                 return;
2553         }
2555         /* Draw the current line */
2556         draw_view_line(view, view->lineno - view->offset);
2558         wnoutrefresh(view->win);
2559         report("");
2563 /*
2564  * Searching
2565  */
2567 static void search_view(struct view *view, enum request request);
2569 static bool
2570 grep_text(struct view *view, const char *text[])
2572         regmatch_t pmatch;
2573         size_t i;
2575         for (i = 0; text[i]; i++)
2576                 if (*text[i] &&
2577                     regexec(view->regex, text[i], 1, &pmatch, 0) != REG_NOMATCH)
2578                         return TRUE;
2579         return FALSE;
2582 static void
2583 select_view_line(struct view *view, unsigned long lineno)
2585         unsigned long old_lineno = view->lineno;
2586         unsigned long old_offset = view->offset;
2588         if (goto_view_line(view, view->offset, lineno)) {
2589                 if (view_is_displayed(view)) {
2590                         if (old_offset != view->offset) {
2591                                 redraw_view(view);
2592                         } else {
2593                                 draw_view_line(view, old_lineno - view->offset);
2594                                 draw_view_line(view, view->lineno - view->offset);
2595                                 wnoutrefresh(view->win);
2596                         }
2597                 } else {
2598                         view->ops->select(view, &view->line[view->lineno]);
2599                 }
2600         }
2603 static void
2604 find_next(struct view *view, enum request request)
2606         unsigned long lineno = view->lineno;
2607         int direction;
2609         if (!*view->grep) {
2610                 if (!*opt_search)
2611                         report("No previous search");
2612                 else
2613                         search_view(view, request);
2614                 return;
2615         }
2617         switch (request) {
2618         case REQ_SEARCH:
2619         case REQ_FIND_NEXT:
2620                 direction = 1;
2621                 break;
2623         case REQ_SEARCH_BACK:
2624         case REQ_FIND_PREV:
2625                 direction = -1;
2626                 break;
2628         default:
2629                 return;
2630         }
2632         if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
2633                 lineno += direction;
2635         /* Note, lineno is unsigned long so will wrap around in which case it
2636          * will become bigger than view->lines. */
2637         for (; lineno < view->lines; lineno += direction) {
2638                 if (view->ops->grep(view, &view->line[lineno])) {
2639                         select_view_line(view, lineno);
2640                         report("Line %ld matches '%s'", lineno + 1, view->grep);
2641                         return;
2642                 }
2643         }
2645         report("No match found for '%s'", view->grep);
2648 static void
2649 search_view(struct view *view, enum request request)
2651         int regex_err;
2653         if (view->regex) {
2654                 regfree(view->regex);
2655                 *view->grep = 0;
2656         } else {
2657                 view->regex = calloc(1, sizeof(*view->regex));
2658                 if (!view->regex)
2659                         return;
2660         }
2662         regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
2663         if (regex_err != 0) {
2664                 char buf[SIZEOF_STR] = "unknown error";
2666                 regerror(regex_err, view->regex, buf, sizeof(buf));
2667                 report("Search failed: %s", buf);
2668                 return;
2669         }
2671         string_copy(view->grep, opt_search);
2673         find_next(view, request);
2676 /*
2677  * Incremental updating
2678  */
2680 static void
2681 reset_view(struct view *view)
2683         int i;
2685         for (i = 0; i < view->lines; i++)
2686                 free(view->line[i].data);
2687         free(view->line);
2689         view->p_offset = view->offset;
2690         view->p_yoffset = view->yoffset;
2691         view->p_lineno = view->lineno;
2693         view->line = NULL;
2694         view->offset = 0;
2695         view->yoffset = 0;
2696         view->lines  = 0;
2697         view->lineno = 0;
2698         view->vid[0] = 0;
2699         view->update_secs = 0;
2702 static void
2703 free_argv(const char *argv[])
2705         int argc;
2707         for (argc = 0; argv[argc]; argc++)
2708                 free((void *) argv[argc]);
2711 static bool
2712 format_argv(const char *dst_argv[], const char *src_argv[], enum format_flags flags)
2714         char buf[SIZEOF_STR];
2715         int argc;
2716         bool noreplace = flags == FORMAT_NONE;
2718         free_argv(dst_argv);
2720         for (argc = 0; src_argv[argc]; argc++) {
2721                 const char *arg = src_argv[argc];
2722                 size_t bufpos = 0;
2724                 while (arg) {
2725                         char *next = strstr(arg, "%(");
2726                         int len = next - arg;
2727                         const char *value;
2729                         if (!next || noreplace) {
2730                                 if (flags == FORMAT_DASH && !strcmp(arg, "--"))
2731                                         noreplace = TRUE;
2732                                 len = strlen(arg);
2733                                 value = "";
2735                         } else if (!prefixcmp(next, "%(directory)")) {
2736                                 value = opt_path;
2738                         } else if (!prefixcmp(next, "%(file)")) {
2739                                 value = opt_file;
2741                         } else if (!prefixcmp(next, "%(ref)")) {
2742                                 value = *opt_ref ? opt_ref : "HEAD";
2744                         } else if (!prefixcmp(next, "%(head)")) {
2745                                 value = ref_head;
2747                         } else if (!prefixcmp(next, "%(commit)")) {
2748                                 value = ref_commit;
2750                         } else if (!prefixcmp(next, "%(blob)")) {
2751                                 value = ref_blob;
2753                         } else {
2754                                 report("Unknown replacement: `%s`", next);
2755                                 return FALSE;
2756                         }
2758                         if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
2759                                 return FALSE;
2761                         arg = next && !noreplace ? strchr(next, ')') + 1 : NULL;
2762                 }
2764                 dst_argv[argc] = strdup(buf);
2765                 if (!dst_argv[argc])
2766                         break;
2767         }
2769         dst_argv[argc] = NULL;
2771         return src_argv[argc] == NULL;
2774 static bool
2775 restore_view_position(struct view *view)
2777         if (!view->p_restore || (view->pipe && view->lines <= view->p_lineno))
2778                 return FALSE;
2780         /* Changing the view position cancels the restoring. */
2781         /* FIXME: Changing back to the first line is not detected. */
2782         if (view->offset != 0 || view->lineno != 0) {
2783                 view->p_restore = FALSE;
2784                 return FALSE;
2785         }
2787         if (goto_view_line(view, view->p_offset, view->p_lineno) &&
2788             view_is_displayed(view))
2789                 werase(view->win);
2791         view->yoffset = view->p_yoffset;
2792         view->p_restore = FALSE;
2794         return TRUE;
2797 static void
2798 end_update(struct view *view, bool force)
2800         if (!view->pipe)
2801                 return;
2802         while (!view->ops->read(view, NULL))
2803                 if (!force)
2804                         return;
2805         set_nonblocking_input(FALSE);
2806         if (force)
2807                 kill_io(view->pipe);
2808         done_io(view->pipe);
2809         view->pipe = NULL;
2812 static void
2813 setup_update(struct view *view, const char *vid)
2815         set_nonblocking_input(TRUE);
2816         reset_view(view);
2817         string_copy_rev(view->vid, vid);
2818         view->pipe = &view->io;
2819         view->start_time = time(NULL);
2822 static bool
2823 prepare_update(struct view *view, const char *argv[], const char *dir,
2824                enum format_flags flags)
2826         if (view->pipe)
2827                 end_update(view, TRUE);
2828         return init_io_rd(&view->io, argv, dir, flags);
2831 static bool
2832 prepare_update_file(struct view *view, const char *name)
2834         if (view->pipe)
2835                 end_update(view, TRUE);
2836         return io_open(&view->io, name);
2839 static bool
2840 begin_update(struct view *view, bool refresh)
2842         if (view->pipe)
2843                 end_update(view, TRUE);
2845         if (refresh) {
2846                 if (!start_io(&view->io))
2847                         return FALSE;
2849         } else {
2850                 if (view == VIEW(REQ_VIEW_TREE) && strcmp(view->vid, view->id))
2851                         opt_path[0] = 0;
2853                 if (!run_io_rd(&view->io, view->ops->argv, FORMAT_ALL))
2854                         return FALSE;
2856                 /* Put the current ref_* value to the view title ref
2857                  * member. This is needed by the blob view. Most other
2858                  * views sets it automatically after loading because the
2859                  * first line is a commit line. */
2860                 string_copy_rev(view->ref, view->id);
2861         }
2863         setup_update(view, view->id);
2865         return TRUE;
2868 static bool
2869 update_view(struct view *view)
2871         char out_buffer[BUFSIZ * 2];
2872         char *line;
2873         /* Clear the view and redraw everything since the tree sorting
2874          * might have rearranged things. */
2875         bool redraw = view->lines == 0;
2876         bool can_read = TRUE;
2878         if (!view->pipe)
2879                 return TRUE;
2881         if (!io_can_read(view->pipe)) {
2882                 if (view->lines == 0 && view_is_displayed(view)) {
2883                         time_t secs = time(NULL) - view->start_time;
2885                         if (secs > 1 && secs > view->update_secs) {
2886                                 if (view->update_secs == 0)
2887                                         redraw_view(view);
2888                                 update_view_title(view);
2889                                 view->update_secs = secs;
2890                         }
2891                 }
2892                 return TRUE;
2893         }
2895         for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
2896                 if (opt_iconv != ICONV_NONE) {
2897                         ICONV_CONST char *inbuf = line;
2898                         size_t inlen = strlen(line) + 1;
2900                         char *outbuf = out_buffer;
2901                         size_t outlen = sizeof(out_buffer);
2903                         size_t ret;
2905                         ret = iconv(opt_iconv, &inbuf, &inlen, &outbuf, &outlen);
2906                         if (ret != (size_t) -1)
2907                                 line = out_buffer;
2908                 }
2910                 if (!view->ops->read(view, line)) {
2911                         report("Allocation failure");
2912                         end_update(view, TRUE);
2913                         return FALSE;
2914                 }
2915         }
2917         {
2918                 unsigned long lines = view->lines;
2919                 int digits;
2921                 for (digits = 0; lines; digits++)
2922                         lines /= 10;
2924                 /* Keep the displayed view in sync with line number scaling. */
2925                 if (digits != view->digits) {
2926                         view->digits = digits;
2927                         if (opt_line_number || view == VIEW(REQ_VIEW_BLAME))
2928                                 redraw = TRUE;
2929                 }
2930         }
2932         if (io_error(view->pipe)) {
2933                 report("Failed to read: %s", io_strerror(view->pipe));
2934                 end_update(view, TRUE);
2936         } else if (io_eof(view->pipe)) {
2937                 report("");
2938                 end_update(view, FALSE);
2939         }
2941         if (restore_view_position(view))
2942                 redraw = TRUE;
2944         if (!view_is_displayed(view))
2945                 return TRUE;
2947         if (redraw)
2948                 redraw_view_from(view, 0);
2949         else
2950                 redraw_view_dirty(view);
2952         /* Update the title _after_ the redraw so that if the redraw picks up a
2953          * commit reference in view->ref it'll be available here. */
2954         update_view_title(view);
2955         return TRUE;
2958 DEFINE_ALLOCATOR(realloc_lines, struct line, 256)
2960 static struct line *
2961 add_line_data(struct view *view, void *data, enum line_type type)
2963         struct line *line;
2965         if (!realloc_lines(&view->line, view->lines, 1))
2966                 return NULL;
2968         line = &view->line[view->lines++];
2969         memset(line, 0, sizeof(*line));
2970         line->type = type;
2971         line->data = data;
2972         line->dirty = 1;
2974         return line;
2977 static struct line *
2978 add_line_text(struct view *view, const char *text, enum line_type type)
2980         char *data = text ? strdup(text) : NULL;
2982         return data ? add_line_data(view, data, type) : NULL;
2985 static struct line *
2986 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
2988         char buf[SIZEOF_STR];
2989         va_list args;
2991         va_start(args, fmt);
2992         if (vsnprintf(buf, sizeof(buf), fmt, args) >= sizeof(buf))
2993                 buf[0] = 0;
2994         va_end(args);
2996         return buf[0] ? add_line_text(view, buf, type) : NULL;
2999 /*
3000  * View opening
3001  */
3003 enum open_flags {
3004         OPEN_DEFAULT = 0,       /* Use default view switching. */
3005         OPEN_SPLIT = 1,         /* Split current view. */
3006         OPEN_RELOAD = 4,        /* Reload view even if it is the current. */
3007         OPEN_REFRESH = 16,      /* Refresh view using previous command. */
3008         OPEN_PREPARED = 32,     /* Open already prepared command. */
3009 };
3011 static void
3012 open_view(struct view *prev, enum request request, enum open_flags flags)
3014         bool split = !!(flags & OPEN_SPLIT);
3015         bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
3016         bool nomaximize = !!(flags & OPEN_REFRESH);
3017         struct view *view = VIEW(request);
3018         int nviews = displayed_views();
3019         struct view *base_view = display[0];
3021         if (view == prev && nviews == 1 && !reload) {
3022                 report("Already in %s view", view->name);
3023                 return;
3024         }
3026         if (view->git_dir && !opt_git_dir[0]) {
3027                 report("The %s view is disabled in pager view", view->name);
3028                 return;
3029         }
3031         if (split) {
3032                 display[1] = view;
3033                 current_view = 1;
3034         } else if (!nomaximize) {
3035                 /* Maximize the current view. */
3036                 memset(display, 0, sizeof(display));
3037                 current_view = 0;
3038                 display[current_view] = view;
3039         }
3041         /* Resize the view when switching between split- and full-screen,
3042          * or when switching between two different full-screen views. */
3043         if (nviews != displayed_views() ||
3044             (nviews == 1 && base_view != display[0]))
3045                 resize_display();
3047         if (view->ops->open) {
3048                 if (view->pipe)
3049                         end_update(view, TRUE);
3050                 if (!view->ops->open(view)) {
3051                         report("Failed to load %s view", view->name);
3052                         return;
3053                 }
3054                 restore_view_position(view);
3056         } else if ((reload || strcmp(view->vid, view->id)) &&
3057                    !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
3058                 report("Failed to load %s view", view->name);
3059                 return;
3060         }
3062         if (split && prev->lineno - prev->offset >= prev->height) {
3063                 /* Take the title line into account. */
3064                 int lines = prev->lineno - prev->offset - prev->height + 1;
3066                 /* Scroll the view that was split if the current line is
3067                  * outside the new limited view. */
3068                 do_scroll_view(prev, lines);
3069         }
3071         if (prev && view != prev) {
3072                 if (split) {
3073                         /* "Blur" the previous view. */
3074                         update_view_title(prev);
3075                 }
3077                 view->parent = prev;
3078         }
3080         if (view->pipe && view->lines == 0) {
3081                 /* Clear the old view and let the incremental updating refill
3082                  * the screen. */
3083                 werase(view->win);
3084                 view->p_restore = flags & (OPEN_RELOAD | OPEN_REFRESH);
3085                 report("");
3086         } else if (view_is_displayed(view)) {
3087                 redraw_view(view);
3088                 report("");
3089         }
3092 static void
3093 open_external_viewer(const char *argv[], const char *dir)
3095         def_prog_mode();           /* save current tty modes */
3096         endwin();                  /* restore original tty modes */
3097         run_io_fg(argv, dir);
3098         fprintf(stderr, "Press Enter to continue");
3099         getc(opt_tty);
3100         reset_prog_mode();
3101         redraw_display(TRUE);
3104 static void
3105 open_mergetool(const char *file)
3107         const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
3109         open_external_viewer(mergetool_argv, opt_cdup);
3112 static void
3113 open_editor(bool from_root, const char *file)
3115         const char *editor_argv[] = { "vi", file, NULL };
3116         const char *editor;
3118         editor = getenv("GIT_EDITOR");
3119         if (!editor && *opt_editor)
3120                 editor = opt_editor;
3121         if (!editor)
3122                 editor = getenv("VISUAL");
3123         if (!editor)
3124                 editor = getenv("EDITOR");
3125         if (!editor)
3126                 editor = "vi";
3128         editor_argv[0] = editor;
3129         open_external_viewer(editor_argv, from_root ? opt_cdup : NULL);
3132 static void
3133 open_run_request(enum request request)
3135         struct run_request *req = get_run_request(request);
3136         const char *argv[ARRAY_SIZE(req->argv)] = { NULL };
3138         if (!req) {
3139                 report("Unknown run request");
3140                 return;
3141         }
3143         if (format_argv(argv, req->argv, FORMAT_ALL))
3144                 open_external_viewer(argv, NULL);
3145         free_argv(argv);
3148 /*
3149  * User request switch noodle
3150  */
3152 static int
3153 view_driver(struct view *view, enum request request)
3155         int i;
3157         if (request == REQ_NONE)
3158                 return TRUE;
3160         if (request > REQ_NONE) {
3161                 open_run_request(request);
3162                 /* FIXME: When all views can refresh always do this. */
3163                 if (view == VIEW(REQ_VIEW_STATUS) ||
3164                     view == VIEW(REQ_VIEW_MAIN) ||
3165                     view == VIEW(REQ_VIEW_LOG) ||
3166                     view == VIEW(REQ_VIEW_BRANCH) ||
3167                     view == VIEW(REQ_VIEW_STAGE))
3168                         request = REQ_REFRESH;
3169                 else
3170                         return TRUE;
3171         }
3173         if (view && view->lines) {
3174                 request = view->ops->request(view, request, &view->line[view->lineno]);
3175                 if (request == REQ_NONE)
3176                         return TRUE;
3177         }
3179         switch (request) {
3180         case REQ_MOVE_UP:
3181         case REQ_MOVE_DOWN:
3182         case REQ_MOVE_PAGE_UP:
3183         case REQ_MOVE_PAGE_DOWN:
3184         case REQ_MOVE_FIRST_LINE:
3185         case REQ_MOVE_LAST_LINE:
3186                 move_view(view, request);
3187                 break;
3189         case REQ_SCROLL_LEFT:
3190         case REQ_SCROLL_RIGHT:
3191         case REQ_SCROLL_LINE_DOWN:
3192         case REQ_SCROLL_LINE_UP:
3193         case REQ_SCROLL_PAGE_DOWN:
3194         case REQ_SCROLL_PAGE_UP:
3195                 scroll_view(view, request);
3196                 break;
3198         case REQ_VIEW_BLAME:
3199                 if (!opt_file[0]) {
3200                         report("No file chosen, press %s to open tree view",
3201                                get_key(REQ_VIEW_TREE));
3202                         break;
3203                 }
3204                 open_view(view, request, OPEN_DEFAULT);
3205                 break;
3207         case REQ_VIEW_BLOB:
3208                 if (!ref_blob[0]) {
3209                         report("No file chosen, press %s to open tree view",
3210                                get_key(REQ_VIEW_TREE));
3211                         break;
3212                 }
3213                 open_view(view, request, OPEN_DEFAULT);
3214                 break;
3216         case REQ_VIEW_PAGER:
3217                 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3218                         report("No pager content, press %s to run command from prompt",
3219                                get_key(REQ_PROMPT));
3220                         break;
3221                 }
3222                 open_view(view, request, OPEN_DEFAULT);
3223                 break;
3225         case REQ_VIEW_STAGE:
3226                 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3227                         report("No stage content, press %s to open the status view and choose file",
3228                                get_key(REQ_VIEW_STATUS));
3229                         break;
3230                 }
3231                 open_view(view, request, OPEN_DEFAULT);
3232                 break;
3234         case REQ_VIEW_STATUS:
3235                 if (opt_is_inside_work_tree == FALSE) {
3236                         report("The status view requires a working tree");
3237                         break;
3238                 }
3239                 open_view(view, request, OPEN_DEFAULT);
3240                 break;
3242         case REQ_VIEW_MAIN:
3243         case REQ_VIEW_DIFF:
3244         case REQ_VIEW_LOG:
3245         case REQ_VIEW_TREE:
3246         case REQ_VIEW_HELP:
3247         case REQ_VIEW_BRANCH:
3248                 open_view(view, request, OPEN_DEFAULT);
3249                 break;
3251         case REQ_NEXT:
3252         case REQ_PREVIOUS:
3253                 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3255                 if ((view == VIEW(REQ_VIEW_DIFF) &&
3256                      view->parent == VIEW(REQ_VIEW_MAIN)) ||
3257                    (view == VIEW(REQ_VIEW_DIFF) &&
3258                      view->parent == VIEW(REQ_VIEW_BLAME)) ||
3259                    (view == VIEW(REQ_VIEW_STAGE) &&
3260                      view->parent == VIEW(REQ_VIEW_STATUS)) ||
3261                    (view == VIEW(REQ_VIEW_BLOB) &&
3262                      view->parent == VIEW(REQ_VIEW_TREE)) ||
3263                    (view == VIEW(REQ_VIEW_MAIN) &&
3264                      view->parent == VIEW(REQ_VIEW_BRANCH))) {
3265                         int line;
3267                         view = view->parent;
3268                         line = view->lineno;
3269                         move_view(view, request);
3270                         if (view_is_displayed(view))
3271                                 update_view_title(view);
3272                         if (line != view->lineno)
3273                                 view->ops->request(view, REQ_ENTER,
3274                                                    &view->line[view->lineno]);
3276                 } else {
3277                         move_view(view, request);
3278                 }
3279                 break;
3281         case REQ_VIEW_NEXT:
3282         {
3283                 int nviews = displayed_views();
3284                 int next_view = (current_view + 1) % nviews;
3286                 if (next_view == current_view) {
3287                         report("Only one view is displayed");
3288                         break;
3289                 }
3291                 current_view = next_view;
3292                 /* Blur out the title of the previous view. */
3293                 update_view_title(view);
3294                 report("");
3295                 break;
3296         }
3297         case REQ_REFRESH:
3298                 report("Refreshing is not yet supported for the %s view", view->name);
3299                 break;
3301         case REQ_MAXIMIZE:
3302                 if (displayed_views() == 2)
3303                         maximize_view(view);
3304                 break;
3306         case REQ_OPTIONS:
3307                 open_option_menu();
3308                 break;
3310         case REQ_TOGGLE_LINENO:
3311                 toggle_view_option(&opt_line_number, "line numbers");
3312                 break;
3314         case REQ_TOGGLE_DATE:
3315                 toggle_view_option(&opt_date, "date display");
3316                 break;
3318         case REQ_TOGGLE_AUTHOR:
3319                 toggle_view_option(&opt_author, "author display");
3320                 break;
3322         case REQ_TOGGLE_REV_GRAPH:
3323                 toggle_view_option(&opt_rev_graph, "revision graph display");
3324                 break;
3326         case REQ_TOGGLE_REFS:
3327                 toggle_view_option(&opt_show_refs, "reference display");
3328                 break;
3330         case REQ_TOGGLE_SORT_FIELD:
3331         case REQ_TOGGLE_SORT_ORDER:
3332                 report("Sorting is not yet supported for the %s view", view->name);
3333                 break;
3335         case REQ_SEARCH:
3336         case REQ_SEARCH_BACK:
3337                 search_view(view, request);
3338                 break;
3340         case REQ_FIND_NEXT:
3341         case REQ_FIND_PREV:
3342                 find_next(view, request);
3343                 break;
3345         case REQ_STOP_LOADING:
3346                 for (i = 0; i < ARRAY_SIZE(views); i++) {
3347                         view = &views[i];
3348                         if (view->pipe)
3349                                 report("Stopped loading the %s view", view->name),
3350                         end_update(view, TRUE);
3351                 }
3352                 break;
3354         case REQ_SHOW_VERSION:
3355                 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3356                 return TRUE;
3358         case REQ_SCREEN_REDRAW:
3359                 redraw_display(TRUE);
3360                 break;
3362         case REQ_EDIT:
3363                 report("Nothing to edit");
3364                 break;
3366         case REQ_ENTER:
3367                 report("Nothing to enter");
3368                 break;
3370         case REQ_VIEW_CLOSE:
3371                 /* XXX: Mark closed views by letting view->parent point to the
3372                  * view itself. Parents to closed view should never be
3373                  * followed. */
3374                 if (view->parent &&
3375                     view->parent->parent != view->parent) {
3376                         maximize_view(view->parent);
3377                         view->parent = view;
3378                         break;
3379                 }
3380                 /* Fall-through */
3381         case REQ_QUIT:
3382                 return FALSE;
3384         default:
3385                 report("Unknown key, press 'h' for help");
3386                 return TRUE;
3387         }
3389         return TRUE;
3393 /*
3394  * View backend utilities
3395  */
3397 enum sort_field {
3398         ORDERBY_NAME,
3399         ORDERBY_DATE,
3400         ORDERBY_AUTHOR,
3401 };
3403 struct sort_state {
3404         const enum sort_field *fields;
3405         size_t size, current;
3406         bool reverse;
3407 };
3409 #define SORT_STATE(fields) { fields, ARRAY_SIZE(fields), 0 }
3410 #define get_sort_field(state) ((state).fields[(state).current])
3411 #define sort_order(state, result) ((state).reverse ? -(result) : (result))
3413 static void
3414 sort_view(struct view *view, enum request request, struct sort_state *state,
3415           int (*compare)(const void *, const void *))
3417         switch (request) {
3418         case REQ_TOGGLE_SORT_FIELD:
3419                 state->current = (state->current + 1) % state->size;
3420                 break;
3422         case REQ_TOGGLE_SORT_ORDER:
3423                 state->reverse = !state->reverse;
3424                 break;
3425         default:
3426                 die("Not a sort request");
3427         }
3429         qsort(view->line, view->lines, sizeof(*view->line), compare);
3430         redraw_view(view);
3433 DEFINE_ALLOCATOR(realloc_authors, const char *, 256)
3435 /* Small author cache to reduce memory consumption. It uses binary
3436  * search to lookup or find place to position new entries. No entries
3437  * are ever freed. */
3438 static const char *
3439 get_author(const char *name)
3441         static const char **authors;
3442         static size_t authors_size;
3443         int from = 0, to = authors_size - 1;
3445         while (from <= to) {
3446                 size_t pos = (to + from) / 2;
3447                 int cmp = strcmp(name, authors[pos]);
3449                 if (!cmp)
3450                         return authors[pos];
3452                 if (cmp < 0)
3453                         to = pos - 1;
3454                 else
3455                         from = pos + 1;
3456         }
3458         if (!realloc_authors(&authors, authors_size, 1))
3459                 return NULL;
3460         name = strdup(name);
3461         if (!name)
3462                 return NULL;
3464         memmove(authors + from + 1, authors + from, (authors_size - from) * sizeof(*authors));
3465         authors[from] = name;
3466         authors_size++;
3468         return name;
3471 static void
3472 parse_timezone(time_t *time, const char *zone)
3474         long tz;
3476         tz  = ('0' - zone[1]) * 60 * 60 * 10;
3477         tz += ('0' - zone[2]) * 60 * 60;
3478         tz += ('0' - zone[3]) * 60;
3479         tz += ('0' - zone[4]);
3481         if (zone[0] == '-')
3482                 tz = -tz;
3484         *time -= tz;
3487 /* Parse author lines where the name may be empty:
3488  *      author  <email@address.tld> 1138474660 +0100
3489  */
3490 static void
3491 parse_author_line(char *ident, const char **author, time_t *time)
3493         char *nameend = strchr(ident, '<');
3494         char *emailend = strchr(ident, '>');
3496         if (nameend && emailend)
3497                 *nameend = *emailend = 0;
3498         ident = chomp_string(ident);
3499         if (!*ident) {
3500                 if (nameend)
3501                         ident = chomp_string(nameend + 1);
3502                 if (!*ident)
3503                         ident = "Unknown";
3504         }
3506         *author = get_author(ident);
3508         /* Parse epoch and timezone */
3509         if (emailend && emailend[1] == ' ') {
3510                 char *secs = emailend + 2;
3511                 char *zone = strchr(secs, ' ');
3513                 *time = (time_t) atol(secs);
3515                 if (zone && strlen(zone) == STRING_SIZE(" +0700"))
3516                         parse_timezone(time, zone + 1);
3517         }
3520 static enum input_status
3521 select_commit_parent_handler(void *data, char *buf, int c)
3523         size_t parents = *(size_t *) data;
3524         int parent = 0;
3526         if (!isdigit(c))
3527                 return INPUT_SKIP;
3529         if (*buf)
3530                 parent = atoi(buf) * 10;
3531         parent += c - '0';
3533         if (parent > parents)
3534                 return INPUT_SKIP;
3535         return INPUT_OK;
3538 static bool
3539 select_commit_parent(const char *id, char rev[SIZEOF_REV], const char *path)
3541         char buf[SIZEOF_STR * 4];
3542         const char *revlist_argv[] = {
3543                 "git", "rev-list", "-1", "--parents", id, "--", path, NULL
3544         };
3545         int parents;
3547         if (!run_io_buf(revlist_argv, buf, sizeof(buf)) ||
3548             (parents = (strlen(buf) / 40) - 1) < 0) {
3549                 report("Failed to get parent information");
3550                 return FALSE;
3552         } else if (parents == 0) {
3553                 if (path)
3554                         report("Path '%s' does not exist in the parent", path);
3555                 else
3556                         report("The selected commit has no parents");
3557                 return FALSE;
3558         }
3560         if (parents > 1) {
3561                 char prompt[SIZEOF_STR];
3562                 char *result;
3564                 if (!string_format(prompt, "Which parent? [1..%d] ", parents))
3565                         return FALSE;
3566                 result = prompt_input(prompt, select_commit_parent_handler, &parents);
3567                 if (!result)
3568                         return FALSE;
3569                 parents = atoi(result);
3570         }
3572         string_copy_rev(rev, &buf[41 * parents]);
3573         return TRUE;
3576 /*
3577  * Pager backend
3578  */
3580 static bool
3581 pager_draw(struct view *view, struct line *line, unsigned int lineno)
3583         char text[SIZEOF_STR];
3585         if (opt_line_number && draw_lineno(view, lineno))
3586                 return TRUE;
3588         string_expand(text, sizeof(text), line->data, opt_tab_size);
3589         draw_text(view, line->type, text, TRUE);
3590         return TRUE;
3593 static bool
3594 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
3596         const char *describe_argv[] = { "git", "describe", commit_id, NULL };
3597         char ref[SIZEOF_STR];
3599         if (!run_io_buf(describe_argv, ref, sizeof(ref)) || !*ref)
3600                 return TRUE;
3602         /* This is the only fatal call, since it can "corrupt" the buffer. */
3603         if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
3604                 return FALSE;
3606         return TRUE;
3609 static void
3610 add_pager_refs(struct view *view, struct line *line)
3612         char buf[SIZEOF_STR];
3613         char *commit_id = (char *)line->data + STRING_SIZE("commit ");
3614         struct ref_list *list;
3615         size_t bufpos = 0, i;
3616         const char *sep = "Refs: ";
3617         bool is_tag = FALSE;
3619         assert(line->type == LINE_COMMIT);
3621         list = get_ref_list(commit_id);
3622         if (!list) {
3623                 if (view == VIEW(REQ_VIEW_DIFF))
3624                         goto try_add_describe_ref;
3625                 return;
3626         }
3628         for (i = 0; i < list->size; i++) {
3629                 struct ref *ref = list->refs[i];
3630                 const char *fmt = ref->tag    ? "%s[%s]" :
3631                                   ref->remote ? "%s<%s>" : "%s%s";
3633                 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
3634                         return;
3635                 sep = ", ";
3636                 if (ref->tag)
3637                         is_tag = TRUE;
3638         }
3640         if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
3641 try_add_describe_ref:
3642                 /* Add <tag>-g<commit_id> "fake" reference. */
3643                 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
3644                         return;
3645         }
3647         if (bufpos == 0)
3648                 return;
3650         add_line_text(view, buf, LINE_PP_REFS);
3653 static bool
3654 pager_read(struct view *view, char *data)
3656         struct line *line;
3658         if (!data)
3659                 return TRUE;
3661         line = add_line_text(view, data, get_line_type(data));
3662         if (!line)
3663                 return FALSE;
3665         if (line->type == LINE_COMMIT &&
3666             (view == VIEW(REQ_VIEW_DIFF) ||
3667              view == VIEW(REQ_VIEW_LOG)))
3668                 add_pager_refs(view, line);
3670         return TRUE;
3673 static enum request
3674 pager_request(struct view *view, enum request request, struct line *line)
3676         int split = 0;
3678         if (request != REQ_ENTER)
3679                 return request;
3681         if (line->type == LINE_COMMIT &&
3682            (view == VIEW(REQ_VIEW_LOG) ||
3683             view == VIEW(REQ_VIEW_PAGER))) {
3684                 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
3685                 split = 1;
3686         }
3688         /* Always scroll the view even if it was split. That way
3689          * you can use Enter to scroll through the log view and
3690          * split open each commit diff. */
3691         scroll_view(view, REQ_SCROLL_LINE_DOWN);
3693         /* FIXME: A minor workaround. Scrolling the view will call report("")
3694          * but if we are scrolling a non-current view this won't properly
3695          * update the view title. */
3696         if (split)
3697                 update_view_title(view);
3699         return REQ_NONE;
3702 static bool
3703 pager_grep(struct view *view, struct line *line)
3705         const char *text[] = { line->data, NULL };
3707         return grep_text(view, text);
3710 static void
3711 pager_select(struct view *view, struct line *line)
3713         if (line->type == LINE_COMMIT) {
3714                 char *text = (char *)line->data + STRING_SIZE("commit ");
3716                 if (view != VIEW(REQ_VIEW_PAGER))
3717                         string_copy_rev(view->ref, text);
3718                 string_copy_rev(ref_commit, text);
3719         }
3722 static struct view_ops pager_ops = {
3723         "line",
3724         NULL,
3725         NULL,
3726         pager_read,
3727         pager_draw,
3728         pager_request,
3729         pager_grep,
3730         pager_select,
3731 };
3733 static const char *log_argv[SIZEOF_ARG] = {
3734         "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
3735 };
3737 static enum request
3738 log_request(struct view *view, enum request request, struct line *line)
3740         switch (request) {
3741         case REQ_REFRESH:
3742                 load_refs();
3743                 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
3744                 return REQ_NONE;
3745         default:
3746                 return pager_request(view, request, line);
3747         }
3750 static struct view_ops log_ops = {
3751         "line",
3752         log_argv,
3753         NULL,
3754         pager_read,
3755         pager_draw,
3756         log_request,
3757         pager_grep,
3758         pager_select,
3759 };
3761 static const char *diff_argv[SIZEOF_ARG] = {
3762         "git", "show", "--pretty=fuller", "--no-color", "--root",
3763                 "--patch-with-stat", "--find-copies-harder", "-C", "%(commit)", NULL
3764 };
3766 static struct view_ops diff_ops = {
3767         "line",
3768         diff_argv,
3769         NULL,
3770         pager_read,
3771         pager_draw,
3772         pager_request,
3773         pager_grep,
3774         pager_select,
3775 };
3777 /*
3778  * Help backend
3779  */
3781 static bool
3782 help_open(struct view *view)
3784         char buf[SIZEOF_STR];
3785         size_t bufpos;
3786         int i;
3788         if (view->lines > 0)
3789                 return TRUE;
3791         add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
3793         for (i = 0; i < ARRAY_SIZE(req_info); i++) {
3794                 const char *key;
3796                 if (req_info[i].request == REQ_NONE)
3797                         continue;
3799                 if (!req_info[i].request) {
3800                         add_line_text(view, "", LINE_DEFAULT);
3801                         add_line_text(view, req_info[i].help, LINE_DEFAULT);
3802                         continue;
3803                 }
3805                 key = get_key(req_info[i].request);
3806                 if (!*key)
3807                         key = "(no key defined)";
3809                 for (bufpos = 0; bufpos <= req_info[i].namelen; bufpos++) {
3810                         buf[bufpos] = tolower(req_info[i].name[bufpos]);
3811                         if (buf[bufpos] == '_')
3812                                 buf[bufpos] = '-';
3813                 }
3815                 add_line_format(view, LINE_DEFAULT, "    %-25s %-20s %s",
3816                                 key, buf, req_info[i].help);
3817         }
3819         if (run_requests) {
3820                 add_line_text(view, "", LINE_DEFAULT);
3821                 add_line_text(view, "External commands:", LINE_DEFAULT);
3822         }
3824         for (i = 0; i < run_requests; i++) {
3825                 struct run_request *req = get_run_request(REQ_NONE + i + 1);
3826                 const char *key;
3827                 int argc;
3829                 if (!req)
3830                         continue;
3832                 key = get_key_name(req->key);
3833                 if (!*key)
3834                         key = "(no key defined)";
3836                 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
3837                         if (!string_format_from(buf, &bufpos, "%s%s",
3838                                                 argc ? " " : "", req->argv[argc]))
3839                                 return REQ_NONE;
3841                 add_line_format(view, LINE_DEFAULT, "    %-10s %-14s `%s`",
3842                                 keymap_table[req->keymap].name, key, buf);
3843         }
3845         return TRUE;
3848 static struct view_ops help_ops = {
3849         "line",
3850         NULL,
3851         help_open,
3852         NULL,
3853         pager_draw,
3854         pager_request,
3855         pager_grep,
3856         pager_select,
3857 };
3860 /*
3861  * Tree backend
3862  */
3864 struct tree_stack_entry {
3865         struct tree_stack_entry *prev;  /* Entry below this in the stack */
3866         unsigned long lineno;           /* Line number to restore */
3867         char *name;                     /* Position of name in opt_path */
3868 };
3870 /* The top of the path stack. */
3871 static struct tree_stack_entry *tree_stack = NULL;
3872 unsigned long tree_lineno = 0;
3874 static void
3875 pop_tree_stack_entry(void)
3877         struct tree_stack_entry *entry = tree_stack;
3879         tree_lineno = entry->lineno;
3880         entry->name[0] = 0;
3881         tree_stack = entry->prev;
3882         free(entry);
3885 static void
3886 push_tree_stack_entry(const char *name, unsigned long lineno)
3888         struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
3889         size_t pathlen = strlen(opt_path);
3891         if (!entry)
3892                 return;
3894         entry->prev = tree_stack;
3895         entry->name = opt_path + pathlen;
3896         tree_stack = entry;
3898         if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
3899                 pop_tree_stack_entry();
3900                 return;
3901         }
3903         /* Move the current line to the first tree entry. */
3904         tree_lineno = 1;
3905         entry->lineno = lineno;
3908 /* Parse output from git-ls-tree(1):
3909  *
3910  * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
3911  */
3913 #define SIZEOF_TREE_ATTR \
3914         STRING_SIZE("100644 blob f931e1d229c3e185caad4449bf5b66ed72462657\t")
3916 #define SIZEOF_TREE_MODE \
3917         STRING_SIZE("100644 ")
3919 #define TREE_ID_OFFSET \
3920         STRING_SIZE("100644 blob ")
3922 struct tree_entry {
3923         char id[SIZEOF_REV];
3924         mode_t mode;
3925         time_t time;                    /* Date from the author ident. */
3926         const char *author;             /* Author of the commit. */
3927         char name[1];
3928 };
3930 static const char *
3931 tree_path(const struct line *line)
3933         return ((struct tree_entry *) line->data)->name;
3936 static int
3937 tree_compare_entry(const struct line *line1, const struct line *line2)
3939         if (line1->type != line2->type)
3940                 return line1->type == LINE_TREE_DIR ? -1 : 1;
3941         return strcmp(tree_path(line1), tree_path(line2));
3944 static const enum sort_field tree_sort_fields[] = {
3945         ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
3946 };
3947 static struct sort_state tree_sort_state = SORT_STATE(tree_sort_fields);
3949 static int
3950 tree_compare(const void *l1, const void *l2)
3952         const struct line *line1 = (const struct line *) l1;
3953         const struct line *line2 = (const struct line *) l2;
3954         const struct tree_entry *entry1 = ((const struct line *) l1)->data;
3955         const struct tree_entry *entry2 = ((const struct line *) l2)->data;
3957         if (line1->type == LINE_TREE_HEAD)
3958                 return -1;
3959         if (line2->type == LINE_TREE_HEAD)
3960                 return 1;
3962         switch (get_sort_field(tree_sort_state)) {
3963         case ORDERBY_DATE:
3964                 return sort_order(tree_sort_state, entry1->time - entry2->time);
3966         case ORDERBY_AUTHOR:
3967                 return sort_order(tree_sort_state, strcmp(entry1->author, entry2->author));
3969         case ORDERBY_NAME:
3970         default:
3971                 return sort_order(tree_sort_state, tree_compare_entry(line1, line2));
3972         }
3976 static struct line *
3977 tree_entry(struct view *view, enum line_type type, const char *path,
3978            const char *mode, const char *id)
3980         struct tree_entry *entry = calloc(1, sizeof(*entry) + strlen(path));
3981         struct line *line = entry ? add_line_data(view, entry, type) : NULL;
3983         if (!entry || !line) {
3984                 free(entry);
3985                 return NULL;
3986         }
3988         strncpy(entry->name, path, strlen(path));
3989         if (mode)
3990                 entry->mode = strtoul(mode, NULL, 8);
3991         if (id)
3992                 string_copy_rev(entry->id, id);
3994         return line;
3997 static bool
3998 tree_read_date(struct view *view, char *text, bool *read_date)
4000         static const char *author_name;
4001         static time_t author_time;
4003         if (!text && *read_date) {
4004                 *read_date = FALSE;
4005                 return TRUE;
4007         } else if (!text) {
4008                 char *path = *opt_path ? opt_path : ".";
4009                 /* Find next entry to process */
4010                 const char *log_file[] = {
4011                         "git", "log", "--no-color", "--pretty=raw",
4012                                 "--cc", "--raw", view->id, "--", path, NULL
4013                 };
4014                 struct io io = {};
4016                 if (!view->lines) {
4017                         tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL);
4018                         report("Tree is empty");
4019                         return TRUE;
4020                 }
4022                 if (!run_io_rd(&io, log_file, FORMAT_NONE)) {
4023                         report("Failed to load tree data");
4024                         return TRUE;
4025                 }
4027                 done_io(view->pipe);
4028                 view->io = io;
4029                 *read_date = TRUE;
4030                 return FALSE;
4032         } else if (*text == 'a' && get_line_type(text) == LINE_AUTHOR) {
4033                 parse_author_line(text + STRING_SIZE("author "),
4034                                   &author_name, &author_time);
4036         } else if (*text == ':') {
4037                 char *pos;
4038                 size_t annotated = 1;
4039                 size_t i;
4041                 pos = strchr(text, '\t');
4042                 if (!pos)
4043                         return TRUE;
4044                 text = pos + 1;
4045                 if (*opt_prefix && !strncmp(text, opt_prefix, strlen(opt_prefix)))
4046                         text += strlen(opt_prefix);
4047                 if (*opt_path && !strncmp(text, opt_path, strlen(opt_path)))
4048                         text += strlen(opt_path);
4049                 pos = strchr(text, '/');
4050                 if (pos)
4051                         *pos = 0;
4053                 for (i = 1; i < view->lines; i++) {
4054                         struct line *line = &view->line[i];
4055                         struct tree_entry *entry = line->data;
4057                         annotated += !!entry->author;
4058                         if (entry->author || strcmp(entry->name, text))
4059                                 continue;
4061                         entry->author = author_name;
4062                         entry->time = author_time;
4063                         line->dirty = 1;
4064                         break;
4065                 }
4067                 if (annotated == view->lines)
4068                         kill_io(view->pipe);
4069         }
4070         return TRUE;
4073 static bool
4074 tree_read(struct view *view, char *text)
4076         static bool read_date = FALSE;
4077         struct tree_entry *data;
4078         struct line *entry, *line;
4079         enum line_type type;
4080         size_t textlen = text ? strlen(text) : 0;
4081         char *path = text + SIZEOF_TREE_ATTR;
4083         if (read_date || !text)
4084                 return tree_read_date(view, text, &read_date);
4086         if (textlen <= SIZEOF_TREE_ATTR)
4087                 return FALSE;
4088         if (view->lines == 0 &&
4089             !tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL))
4090                 return FALSE;
4092         /* Strip the path part ... */
4093         if (*opt_path) {
4094                 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
4095                 size_t striplen = strlen(opt_path);
4097                 if (pathlen > striplen)
4098                         memmove(path, path + striplen,
4099                                 pathlen - striplen + 1);
4101                 /* Insert "link" to parent directory. */
4102                 if (view->lines == 1 &&
4103                     !tree_entry(view, LINE_TREE_DIR, "..", "040000", view->ref))
4104                         return FALSE;
4105         }
4107         type = text[SIZEOF_TREE_MODE] == 't' ? LINE_TREE_DIR : LINE_TREE_FILE;
4108         entry = tree_entry(view, type, path, text, text + TREE_ID_OFFSET);
4109         if (!entry)
4110                 return FALSE;
4111         data = entry->data;
4113         /* Skip "Directory ..." and ".." line. */
4114         for (line = &view->line[1 + !!*opt_path]; line < entry; line++) {
4115                 if (tree_compare_entry(line, entry) <= 0)
4116                         continue;
4118                 memmove(line + 1, line, (entry - line) * sizeof(*entry));
4120                 line->data = data;
4121                 line->type = type;
4122                 for (; line <= entry; line++)
4123                         line->dirty = line->cleareol = 1;
4124                 return TRUE;
4125         }
4127         if (tree_lineno > view->lineno) {
4128                 view->lineno = tree_lineno;
4129                 tree_lineno = 0;
4130         }
4132         return TRUE;
4135 static bool
4136 tree_draw(struct view *view, struct line *line, unsigned int lineno)
4138         struct tree_entry *entry = line->data;
4140         if (line->type == LINE_TREE_HEAD) {
4141                 if (draw_text(view, line->type, "Directory path /", TRUE))
4142                         return TRUE;
4143         } else {
4144                 if (draw_mode(view, entry->mode))
4145                         return TRUE;
4147                 if (opt_author && draw_author(view, entry->author))
4148                         return TRUE;
4150                 if (opt_date && draw_date(view, entry->author ? &entry->time : NULL))
4151                         return TRUE;
4152         }
4153         if (draw_text(view, line->type, entry->name, TRUE))
4154                 return TRUE;
4155         return TRUE;
4158 static void
4159 open_blob_editor()
4161         char file[SIZEOF_STR] = "/tmp/tigblob.XXXXXX";
4162         int fd = mkstemp(file);
4164         if (fd == -1)
4165                 report("Failed to create temporary file");
4166         else if (!run_io_append(blob_ops.argv, FORMAT_ALL, fd))
4167                 report("Failed to save blob data to file");
4168         else
4169                 open_editor(FALSE, file);
4170         if (fd != -1)
4171                 unlink(file);
4174 static enum request
4175 tree_request(struct view *view, enum request request, struct line *line)
4177         enum open_flags flags;
4179         switch (request) {
4180         case REQ_VIEW_BLAME:
4181                 if (line->type != LINE_TREE_FILE) {
4182                         report("Blame only supported for files");
4183                         return REQ_NONE;
4184                 }
4186                 string_copy(opt_ref, view->vid);
4187                 return request;
4189         case REQ_EDIT:
4190                 if (line->type != LINE_TREE_FILE) {
4191                         report("Edit only supported for files");
4192                 } else if (!is_head_commit(view->vid)) {
4193                         open_blob_editor();
4194                 } else {
4195                         open_editor(TRUE, opt_file);
4196                 }
4197                 return REQ_NONE;
4199         case REQ_TOGGLE_SORT_FIELD:
4200         case REQ_TOGGLE_SORT_ORDER:
4201                 sort_view(view, request, &tree_sort_state, tree_compare);
4202                 return REQ_NONE;
4204         case REQ_PARENT:
4205                 if (!*opt_path) {
4206                         /* quit view if at top of tree */
4207                         return REQ_VIEW_CLOSE;
4208                 }
4209                 /* fake 'cd  ..' */
4210                 line = &view->line[1];
4211                 break;
4213         case REQ_ENTER:
4214                 break;
4216         default:
4217                 return request;
4218         }
4220         /* Cleanup the stack if the tree view is at a different tree. */
4221         while (!*opt_path && tree_stack)
4222                 pop_tree_stack_entry();
4224         switch (line->type) {
4225         case LINE_TREE_DIR:
4226                 /* Depending on whether it is a subdirectory or parent link
4227                  * mangle the path buffer. */
4228                 if (line == &view->line[1] && *opt_path) {
4229                         pop_tree_stack_entry();
4231                 } else {
4232                         const char *basename = tree_path(line);
4234                         push_tree_stack_entry(basename, view->lineno);
4235                 }
4237                 /* Trees and subtrees share the same ID, so they are not not
4238                  * unique like blobs. */
4239                 flags = OPEN_RELOAD;
4240                 request = REQ_VIEW_TREE;
4241                 break;
4243         case LINE_TREE_FILE:
4244                 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4245                 request = REQ_VIEW_BLOB;
4246                 break;
4248         default:
4249                 return REQ_NONE;
4250         }
4252         open_view(view, request, flags);
4253         if (request == REQ_VIEW_TREE)
4254                 view->lineno = tree_lineno;
4256         return REQ_NONE;
4259 static bool
4260 tree_grep(struct view *view, struct line *line)
4262         struct tree_entry *entry = line->data;
4263         const char *text[] = {
4264                 entry->name,
4265                 opt_author ? entry->author : "",
4266                 opt_date ? mkdate(&entry->time) : "",
4267                 NULL
4268         };
4270         return grep_text(view, text);
4273 static void
4274 tree_select(struct view *view, struct line *line)
4276         struct tree_entry *entry = line->data;
4278         if (line->type == LINE_TREE_FILE) {
4279                 string_copy_rev(ref_blob, entry->id);
4280                 string_format(opt_file, "%s%s", opt_path, tree_path(line));
4282         } else if (line->type != LINE_TREE_DIR) {
4283                 return;
4284         }
4286         string_copy_rev(view->ref, entry->id);
4289 static const char *tree_argv[SIZEOF_ARG] = {
4290         "git", "ls-tree", "%(commit)", "%(directory)", NULL
4291 };
4293 static struct view_ops tree_ops = {
4294         "file",
4295         tree_argv,
4296         NULL,
4297         tree_read,
4298         tree_draw,
4299         tree_request,
4300         tree_grep,
4301         tree_select,
4302 };
4304 static bool
4305 blob_read(struct view *view, char *line)
4307         if (!line)
4308                 return TRUE;
4309         return add_line_text(view, line, LINE_DEFAULT) != NULL;
4312 static enum request
4313 blob_request(struct view *view, enum request request, struct line *line)
4315         switch (request) {
4316         case REQ_EDIT:
4317                 open_blob_editor();
4318                 return REQ_NONE;
4319         default:
4320                 return pager_request(view, request, line);
4321         }
4324 static const char *blob_argv[SIZEOF_ARG] = {
4325         "git", "cat-file", "blob", "%(blob)", NULL
4326 };
4328 static struct view_ops blob_ops = {
4329         "line",
4330         blob_argv,
4331         NULL,
4332         blob_read,
4333         pager_draw,
4334         blob_request,
4335         pager_grep,
4336         pager_select,
4337 };
4339 /*
4340  * Blame backend
4341  *
4342  * Loading the blame view is a two phase job:
4343  *
4344  *  1. File content is read either using opt_file from the
4345  *     filesystem or using git-cat-file.
4346  *  2. Then blame information is incrementally added by
4347  *     reading output from git-blame.
4348  */
4350 static const char *blame_head_argv[] = {
4351         "git", "blame", "--incremental", "--", "%(file)", NULL
4352 };
4354 static const char *blame_ref_argv[] = {
4355         "git", "blame", "--incremental", "%(ref)", "--", "%(file)", NULL
4356 };
4358 static const char *blame_cat_file_argv[] = {
4359         "git", "cat-file", "blob", "%(ref):%(file)", NULL
4360 };
4362 struct blame_commit {
4363         char id[SIZEOF_REV];            /* SHA1 ID. */
4364         char title[128];                /* First line of the commit message. */
4365         const char *author;             /* Author of the commit. */
4366         time_t time;                    /* Date from the author ident. */
4367         char filename[128];             /* Name of file. */
4368         bool has_previous;              /* Was a "previous" line detected. */
4369 };
4371 struct blame {
4372         struct blame_commit *commit;
4373         unsigned long lineno;
4374         char text[1];
4375 };
4377 static bool
4378 blame_open(struct view *view)
4380         if (*opt_ref || !io_open(&view->io, opt_file)) {
4381                 if (!run_io_rd(&view->io, blame_cat_file_argv, FORMAT_ALL))
4382                         return FALSE;
4383         }
4385         setup_update(view, opt_file);
4386         string_format(view->ref, "%s ...", opt_file);
4388         return TRUE;
4391 static struct blame_commit *
4392 get_blame_commit(struct view *view, const char *id)
4394         size_t i;
4396         for (i = 0; i < view->lines; i++) {
4397                 struct blame *blame = view->line[i].data;
4399                 if (!blame->commit)
4400                         continue;
4402                 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
4403                         return blame->commit;
4404         }
4406         {
4407                 struct blame_commit *commit = calloc(1, sizeof(*commit));
4409                 if (commit)
4410                         string_ncopy(commit->id, id, SIZEOF_REV);
4411                 return commit;
4412         }
4415 static bool
4416 parse_number(const char **posref, size_t *number, size_t min, size_t max)
4418         const char *pos = *posref;
4420         *posref = NULL;
4421         pos = strchr(pos + 1, ' ');
4422         if (!pos || !isdigit(pos[1]))
4423                 return FALSE;
4424         *number = atoi(pos + 1);
4425         if (*number < min || *number > max)
4426                 return FALSE;
4428         *posref = pos;
4429         return TRUE;
4432 static struct blame_commit *
4433 parse_blame_commit(struct view *view, const char *text, int *blamed)
4435         struct blame_commit *commit;
4436         struct blame *blame;
4437         const char *pos = text + SIZEOF_REV - 2;
4438         size_t orig_lineno = 0;
4439         size_t lineno;
4440         size_t group;
4442         if (strlen(text) <= SIZEOF_REV || pos[1] != ' ')
4443                 return NULL;
4445         if (!parse_number(&pos, &orig_lineno, 1, 9999999) ||
4446             !parse_number(&pos, &lineno, 1, view->lines) ||
4447             !parse_number(&pos, &group, 1, view->lines - lineno + 1))
4448                 return NULL;
4450         commit = get_blame_commit(view, text);
4451         if (!commit)
4452                 return NULL;
4454         *blamed += group;
4455         while (group--) {
4456                 struct line *line = &view->line[lineno + group - 1];
4458                 blame = line->data;
4459                 blame->commit = commit;
4460                 blame->lineno = orig_lineno + group - 1;
4461                 line->dirty = 1;
4462         }
4464         return commit;
4467 static bool
4468 blame_read_file(struct view *view, const char *line, bool *read_file)
4470         if (!line) {
4471                 const char **argv = *opt_ref ? blame_ref_argv : blame_head_argv;
4472                 struct io io = {};
4474                 if (view->lines == 0 && !view->parent)
4475                         die("No blame exist for %s", view->vid);
4477                 if (view->lines == 0 || !run_io_rd(&io, argv, FORMAT_ALL)) {
4478                         report("Failed to load blame data");
4479                         return TRUE;
4480                 }
4482                 done_io(view->pipe);
4483                 view->io = io;
4484                 *read_file = FALSE;
4485                 return FALSE;
4487         } else {
4488                 size_t linelen = strlen(line);
4489                 struct blame *blame = malloc(sizeof(*blame) + linelen);
4491                 if (!blame)
4492                         return FALSE;
4494                 blame->commit = NULL;
4495                 strncpy(blame->text, line, linelen);
4496                 blame->text[linelen] = 0;
4497                 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
4498         }
4501 static bool
4502 match_blame_header(const char *name, char **line)
4504         size_t namelen = strlen(name);
4505         bool matched = !strncmp(name, *line, namelen);
4507         if (matched)
4508                 *line += namelen;
4510         return matched;
4513 static bool
4514 blame_read(struct view *view, char *line)
4516         static struct blame_commit *commit = NULL;
4517         static int blamed = 0;
4518         static bool read_file = TRUE;
4520         if (read_file)
4521                 return blame_read_file(view, line, &read_file);
4523         if (!line) {
4524                 /* Reset all! */
4525                 commit = NULL;
4526                 blamed = 0;
4527                 read_file = TRUE;
4528                 string_format(view->ref, "%s", view->vid);
4529                 if (view_is_displayed(view)) {
4530                         update_view_title(view);
4531                         redraw_view_from(view, 0);
4532                 }
4533                 return TRUE;
4534         }
4536         if (!commit) {
4537                 commit = parse_blame_commit(view, line, &blamed);
4538                 string_format(view->ref, "%s %2d%%", view->vid,
4539                               view->lines ? blamed * 100 / view->lines : 0);
4541         } else if (match_blame_header("author ", &line)) {
4542                 commit->author = get_author(line);
4544         } else if (match_blame_header("author-time ", &line)) {
4545                 commit->time = (time_t) atol(line);
4547         } else if (match_blame_header("author-tz ", &line)) {
4548                 parse_timezone(&commit->time, line);
4550         } else if (match_blame_header("summary ", &line)) {
4551                 string_ncopy(commit->title, line, strlen(line));
4553         } else if (match_blame_header("previous ", &line)) {
4554                 commit->has_previous = TRUE;
4556         } else if (match_blame_header("filename ", &line)) {
4557                 string_ncopy(commit->filename, line, strlen(line));
4558                 commit = NULL;
4559         }
4561         return TRUE;
4564 static bool
4565 blame_draw(struct view *view, struct line *line, unsigned int lineno)
4567         struct blame *blame = line->data;
4568         time_t *time = NULL;
4569         const char *id = NULL, *author = NULL;
4570         char text[SIZEOF_STR];
4572         if (blame->commit && *blame->commit->filename) {
4573                 id = blame->commit->id;
4574                 author = blame->commit->author;
4575                 time = &blame->commit->time;
4576         }
4578         if (opt_date && draw_date(view, time))
4579                 return TRUE;
4581         if (opt_author && draw_author(view, author))
4582                 return TRUE;
4584         if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
4585                 return TRUE;
4587         if (draw_lineno(view, lineno))
4588                 return TRUE;
4590         string_expand(text, sizeof(text), blame->text, opt_tab_size);
4591         draw_text(view, LINE_DEFAULT, text, TRUE);
4592         return TRUE;
4595 static bool
4596 check_blame_commit(struct blame *blame, bool check_null_id)
4598         if (!blame->commit)
4599                 report("Commit data not loaded yet");
4600         else if (check_null_id && !strcmp(blame->commit->id, NULL_ID))
4601                 report("No commit exist for the selected line");
4602         else
4603                 return TRUE;
4604         return FALSE;
4607 static void
4608 setup_blame_parent_line(struct view *view, struct blame *blame)
4610         const char *diff_tree_argv[] = {
4611                 "git", "diff-tree", "-U0", blame->commit->id,
4612                         "--", blame->commit->filename, NULL
4613         };
4614         struct io io = {};
4615         int parent_lineno = -1;
4616         int blamed_lineno = -1;
4617         char *line;
4619         if (!run_io(&io, diff_tree_argv, NULL, IO_RD))
4620                 return;
4622         while ((line = io_get(&io, '\n', TRUE))) {
4623                 if (*line == '@') {
4624                         char *pos = strchr(line, '+');
4626                         parent_lineno = atoi(line + 4);
4627                         if (pos)
4628                                 blamed_lineno = atoi(pos + 1);
4630                 } else if (*line == '+' && parent_lineno != -1) {
4631                         if (blame->lineno == blamed_lineno - 1 &&
4632                             !strcmp(blame->text, line + 1)) {
4633                                 view->lineno = parent_lineno ? parent_lineno - 1 : 0;
4634                                 break;
4635                         }
4636                         blamed_lineno++;
4637                 }
4638         }
4640         done_io(&io);
4643 static enum request
4644 blame_request(struct view *view, enum request request, struct line *line)
4646         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4647         struct blame *blame = line->data;
4649         switch (request) {
4650         case REQ_VIEW_BLAME:
4651                 if (check_blame_commit(blame, TRUE)) {
4652                         string_copy(opt_ref, blame->commit->id);
4653                         string_copy(opt_file, blame->commit->filename);
4654                         if (blame->lineno)
4655                                 view->lineno = blame->lineno;
4656                         open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4657                 }
4658                 break;
4660         case REQ_PARENT:
4661                 if (check_blame_commit(blame, TRUE) &&
4662                     select_commit_parent(blame->commit->id, opt_ref,
4663                                          blame->commit->filename)) {
4664                         string_copy(opt_file, blame->commit->filename);
4665                         setup_blame_parent_line(view, blame);
4666                         open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4667                 }
4668                 break;
4670         case REQ_ENTER:
4671                 if (!check_blame_commit(blame, FALSE))
4672                         break;
4674                 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
4675                     !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
4676                         break;
4678                 if (!strcmp(blame->commit->id, NULL_ID)) {
4679                         struct view *diff = VIEW(REQ_VIEW_DIFF);
4680                         const char *diff_index_argv[] = {
4681                                 "git", "diff-index", "--root", "--patch-with-stat",
4682                                         "-C", "-M", "HEAD", "--", view->vid, NULL
4683                         };
4685                         if (!blame->commit->has_previous) {
4686                                 diff_index_argv[1] = "diff";
4687                                 diff_index_argv[2] = "--no-color";
4688                                 diff_index_argv[6] = "--";
4689                                 diff_index_argv[7] = "/dev/null";
4690                         }
4692                         if (!prepare_update(diff, diff_index_argv, NULL, FORMAT_DASH)) {
4693                                 report("Failed to allocate diff command");
4694                                 break;
4695                         }
4696                         flags |= OPEN_PREPARED;
4697                 }
4699                 open_view(view, REQ_VIEW_DIFF, flags);
4700                 if (VIEW(REQ_VIEW_DIFF)->pipe && !strcmp(blame->commit->id, NULL_ID))
4701                         string_copy_rev(VIEW(REQ_VIEW_DIFF)->ref, NULL_ID);
4702                 break;
4704         default:
4705                 return request;
4706         }
4708         return REQ_NONE;
4711 static bool
4712 blame_grep(struct view *view, struct line *line)
4714         struct blame *blame = line->data;
4715         struct blame_commit *commit = blame->commit;
4716         const char *text[] = {
4717                 blame->text,
4718                 commit ? commit->title : "",
4719                 commit ? commit->id : "",
4720                 commit && opt_author ? commit->author : "",
4721                 commit && opt_date ? mkdate(&commit->time) : "",
4722                 NULL
4723         };
4725         return grep_text(view, text);
4728 static void
4729 blame_select(struct view *view, struct line *line)
4731         struct blame *blame = line->data;
4732         struct blame_commit *commit = blame->commit;
4734         if (!commit)
4735                 return;
4737         if (!strcmp(commit->id, NULL_ID))
4738                 string_ncopy(ref_commit, "HEAD", 4);
4739         else
4740                 string_copy_rev(ref_commit, commit->id);
4743 static struct view_ops blame_ops = {
4744         "line",
4745         NULL,
4746         blame_open,
4747         blame_read,
4748         blame_draw,
4749         blame_request,
4750         blame_grep,
4751         blame_select,
4752 };
4754 /*
4755  * Branch backend
4756  */
4758 struct branch {
4759         const char *author;             /* Author of the last commit. */
4760         time_t time;                    /* Date of the last activity. */
4761         struct ref *ref;                /* Name and commit ID information. */
4762 };
4764 static const enum sort_field branch_sort_fields[] = {
4765         ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
4766 };
4767 static struct sort_state branch_sort_state = SORT_STATE(branch_sort_fields);
4769 static int
4770 branch_compare(const void *l1, const void *l2)
4772         const struct branch *branch1 = ((const struct line *) l1)->data;
4773         const struct branch *branch2 = ((const struct line *) l2)->data;
4775         switch (get_sort_field(branch_sort_state)) {
4776         case ORDERBY_DATE:
4777                 return sort_order(branch_sort_state, branch1->time - branch2->time);
4779         case ORDERBY_AUTHOR:
4780                 return sort_order(branch_sort_state, strcmp(branch1->author, branch2->author));
4782         case ORDERBY_NAME:
4783         default:
4784                 return sort_order(branch_sort_state, strcmp(branch1->ref->name, branch2->ref->name));
4785         }
4788 static bool
4789 branch_draw(struct view *view, struct line *line, unsigned int lineno)
4791         struct branch *branch = line->data;
4792         enum line_type type = branch->ref->head ? LINE_MAIN_HEAD : LINE_DEFAULT;
4794         if (opt_date && draw_date(view, branch->author ? &branch->time : NULL))
4795                 return TRUE;
4797         if (opt_author && draw_author(view, branch->author))
4798                 return TRUE;
4800         draw_text(view, type, branch->ref->name, TRUE);
4801         return TRUE;
4804 static enum request
4805 branch_request(struct view *view, enum request request, struct line *line)
4807         switch (request) {
4808         case REQ_REFRESH:
4809                 load_refs();
4810                 open_view(view, REQ_VIEW_BRANCH, OPEN_REFRESH);
4811                 return REQ_NONE;
4813         case REQ_TOGGLE_SORT_FIELD:
4814         case REQ_TOGGLE_SORT_ORDER:
4815                 sort_view(view, request, &branch_sort_state, branch_compare);
4816                 return REQ_NONE;
4818         case REQ_ENTER:
4819                 open_view(view, REQ_VIEW_MAIN, OPEN_SPLIT);
4820                 return REQ_NONE;
4822         default:
4823                 return request;
4824         }
4827 static bool
4828 branch_read(struct view *view, char *line)
4830         static char id[SIZEOF_REV];
4831         struct branch *reference;
4832         size_t i;
4834         if (!line)
4835                 return TRUE;
4837         switch (get_line_type(line)) {
4838         case LINE_COMMIT:
4839                 string_copy_rev(id, line + STRING_SIZE("commit "));
4840                 return TRUE;
4842         case LINE_AUTHOR:
4843                 for (i = 0, reference = NULL; i < view->lines; i++) {
4844                         struct branch *branch = view->line[i].data;
4846                         if (strcmp(branch->ref->id, id))
4847                                 continue;
4849                         view->line[i].dirty = TRUE;
4850                         if (reference) {
4851                                 branch->author = reference->author;
4852                                 branch->time = reference->time;
4853                                 continue;
4854                         }
4856                         parse_author_line(line + STRING_SIZE("author "),
4857                                           &branch->author, &branch->time);
4858                         reference = branch;
4859                 }
4860                 return TRUE;
4862         default:
4863                 return TRUE;
4864         }
4868 static bool
4869 branch_open_visitor(void *data, struct ref *ref)
4871         struct view *view = data;
4872         struct branch *branch;
4874         if (ref->tag || ref->ltag || ref->remote)
4875                 return TRUE;
4877         branch = calloc(1, sizeof(*branch));
4878         if (!branch)
4879                 return FALSE;
4881         branch->ref = ref;
4882         return !!add_line_data(view, branch, LINE_DEFAULT);
4885 static bool
4886 branch_open(struct view *view)
4888         const char *branch_log[] = {
4889                 "git", "log", "--no-color", "--pretty=raw",
4890                         "--simplify-by-decoration", "--all", NULL
4891         };
4893         if (!run_io_rd(&view->io, branch_log, FORMAT_NONE)) {
4894                 report("Failed to load branch data");
4895                 return TRUE;
4896         }
4898         setup_update(view, view->id);
4899         foreach_ref(branch_open_visitor, view);
4900         view->p_restore = TRUE;
4902         return TRUE;
4905 static bool
4906 branch_grep(struct view *view, struct line *line)
4908         struct branch *branch = line->data;
4909         const char *text[] = {
4910                 branch->ref->name,
4911                 branch->author,
4912                 NULL
4913         };
4915         return grep_text(view, text);
4918 static void
4919 branch_select(struct view *view, struct line *line)
4921         struct branch *branch = line->data;
4923         string_copy_rev(view->ref, branch->ref->id);
4924         string_copy_rev(ref_commit, branch->ref->id);
4925         string_copy_rev(ref_head, branch->ref->id);
4928 static struct view_ops branch_ops = {
4929         "branch",
4930         NULL,
4931         branch_open,
4932         branch_read,
4933         branch_draw,
4934         branch_request,
4935         branch_grep,
4936         branch_select,
4937 };
4939 /*
4940  * Status backend
4941  */
4943 struct status {
4944         char status;
4945         struct {
4946                 mode_t mode;
4947                 char rev[SIZEOF_REV];
4948                 char name[SIZEOF_STR];
4949         } old;
4950         struct {
4951                 mode_t mode;
4952                 char rev[SIZEOF_REV];
4953                 char name[SIZEOF_STR];
4954         } new;
4955 };
4957 static char status_onbranch[SIZEOF_STR];
4958 static struct status stage_status;
4959 static enum line_type stage_line_type;
4960 static size_t stage_chunks;
4961 static int *stage_chunk;
4963 DEFINE_ALLOCATOR(realloc_ints, int, 32)
4965 /* This should work even for the "On branch" line. */
4966 static inline bool
4967 status_has_none(struct view *view, struct line *line)
4969         return line < view->line + view->lines && !line[1].data;
4972 /* Get fields from the diff line:
4973  * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
4974  */
4975 static inline bool
4976 status_get_diff(struct status *file, const char *buf, size_t bufsize)
4978         const char *old_mode = buf +  1;
4979         const char *new_mode = buf +  8;
4980         const char *old_rev  = buf + 15;
4981         const char *new_rev  = buf + 56;
4982         const char *status   = buf + 97;
4984         if (bufsize < 98 ||
4985             old_mode[-1] != ':' ||
4986             new_mode[-1] != ' ' ||
4987             old_rev[-1]  != ' ' ||
4988             new_rev[-1]  != ' ' ||
4989             status[-1]   != ' ')
4990                 return FALSE;
4992         file->status = *status;
4994         string_copy_rev(file->old.rev, old_rev);
4995         string_copy_rev(file->new.rev, new_rev);
4997         file->old.mode = strtoul(old_mode, NULL, 8);
4998         file->new.mode = strtoul(new_mode, NULL, 8);
5000         file->old.name[0] = file->new.name[0] = 0;
5002         return TRUE;
5005 static bool
5006 status_run(struct view *view, const char *argv[], char status, enum line_type type)
5008         struct status *unmerged = NULL;
5009         char *buf;
5010         struct io io = {};
5012         if (!run_io(&io, argv, NULL, IO_RD))
5013                 return FALSE;
5015         add_line_data(view, NULL, type);
5017         while ((buf = io_get(&io, 0, TRUE))) {
5018                 struct status *file = unmerged;
5020                 if (!file) {
5021                         file = calloc(1, sizeof(*file));
5022                         if (!file || !add_line_data(view, file, type))
5023                                 goto error_out;
5024                 }
5026                 /* Parse diff info part. */
5027                 if (status) {
5028                         file->status = status;
5029                         if (status == 'A')
5030                                 string_copy(file->old.rev, NULL_ID);
5032                 } else if (!file->status || file == unmerged) {
5033                         if (!status_get_diff(file, buf, strlen(buf)))
5034                                 goto error_out;
5036                         buf = io_get(&io, 0, TRUE);
5037                         if (!buf)
5038                                 break;
5040                         /* Collapse all modified entries that follow an
5041                          * associated unmerged entry. */
5042                         if (unmerged == file) {
5043                                 unmerged->status = 'U';
5044                                 unmerged = NULL;
5045                         } else if (file->status == 'U') {
5046                                 unmerged = file;
5047                         }
5048                 }
5050                 /* Grab the old name for rename/copy. */
5051                 if (!*file->old.name &&
5052                     (file->status == 'R' || file->status == 'C')) {
5053                         string_ncopy(file->old.name, buf, strlen(buf));
5055                         buf = io_get(&io, 0, TRUE);
5056                         if (!buf)
5057                                 break;
5058                 }
5060                 /* git-ls-files just delivers a NUL separated list of
5061                  * file names similar to the second half of the
5062                  * git-diff-* output. */
5063                 string_ncopy(file->new.name, buf, strlen(buf));
5064                 if (!*file->old.name)
5065                         string_copy(file->old.name, file->new.name);
5066                 file = NULL;
5067         }
5069         if (io_error(&io)) {
5070 error_out:
5071                 done_io(&io);
5072                 return FALSE;
5073         }
5075         if (!view->line[view->lines - 1].data)
5076                 add_line_data(view, NULL, LINE_STAT_NONE);
5078         done_io(&io);
5079         return TRUE;
5082 /* Don't show unmerged entries in the staged section. */
5083 static const char *status_diff_index_argv[] = {
5084         "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
5085                              "--cached", "-M", "HEAD", NULL
5086 };
5088 static const char *status_diff_files_argv[] = {
5089         "git", "diff-files", "-z", NULL
5090 };
5092 static const char *status_list_other_argv[] = {
5093         "git", "ls-files", "-z", "--others", "--exclude-standard", NULL
5094 };
5096 static const char *status_list_no_head_argv[] = {
5097         "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
5098 };
5100 static const char *update_index_argv[] = {
5101         "git", "update-index", "-q", "--unmerged", "--refresh", NULL
5102 };
5104 /* Restore the previous line number to stay in the context or select a
5105  * line with something that can be updated. */
5106 static void
5107 status_restore(struct view *view)
5109         if (view->p_lineno >= view->lines)
5110                 view->p_lineno = view->lines - 1;
5111         while (view->p_lineno < view->lines && !view->line[view->p_lineno].data)
5112                 view->p_lineno++;
5113         while (view->p_lineno > 0 && !view->line[view->p_lineno].data)
5114                 view->p_lineno--;
5116         /* If the above fails, always skip the "On branch" line. */
5117         if (view->p_lineno < view->lines)
5118                 view->lineno = view->p_lineno;
5119         else
5120                 view->lineno = 1;
5122         if (view->lineno < view->offset)
5123                 view->offset = view->lineno;
5124         else if (view->offset + view->height <= view->lineno)
5125                 view->offset = view->lineno - view->height + 1;
5127         view->p_restore = FALSE;
5130 static void
5131 status_update_onbranch(void)
5133         static const char *paths[][2] = {
5134                 { "rebase-apply/rebasing",      "Rebasing" },
5135                 { "rebase-apply/applying",      "Applying mailbox" },
5136                 { "rebase-apply/",              "Rebasing mailbox" },
5137                 { "rebase-merge/interactive",   "Interactive rebase" },
5138                 { "rebase-merge/",              "Rebase merge" },
5139                 { "MERGE_HEAD",                 "Merging" },
5140                 { "BISECT_LOG",                 "Bisecting" },
5141                 { "HEAD",                       "On branch" },
5142         };
5143         char buf[SIZEOF_STR];
5144         struct stat stat;
5145         int i;
5147         if (is_initial_commit()) {
5148                 string_copy(status_onbranch, "Initial commit");
5149                 return;
5150         }
5152         for (i = 0; i < ARRAY_SIZE(paths); i++) {
5153                 char *head = opt_head;
5155                 if (!string_format(buf, "%s/%s", opt_git_dir, paths[i][0]) ||
5156                     lstat(buf, &stat) < 0)
5157                         continue;
5159                 if (!*opt_head) {
5160                         struct io io = {};
5162                         if (string_format(buf, "%s/rebase-merge/head-name", opt_git_dir) &&
5163                             io_open(&io, buf) &&
5164                             io_read_buf(&io, buf, sizeof(buf))) {
5165                                 head = buf;
5166                                 if (!prefixcmp(head, "refs/heads/"))
5167                                         head += STRING_SIZE("refs/heads/");
5168                         }
5169                 }
5171                 if (!string_format(status_onbranch, "%s %s", paths[i][1], head))
5172                         string_copy(status_onbranch, opt_head);
5173                 return;
5174         }
5176         string_copy(status_onbranch, "Not currently on any branch");
5179 /* First parse staged info using git-diff-index(1), then parse unstaged
5180  * info using git-diff-files(1), and finally untracked files using
5181  * git-ls-files(1). */
5182 static bool
5183 status_open(struct view *view)
5185         reset_view(view);
5187         add_line_data(view, NULL, LINE_STAT_HEAD);
5188         status_update_onbranch();
5190         run_io_bg(update_index_argv);
5192         if (is_initial_commit()) {
5193                 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
5194                         return FALSE;
5195         } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
5196                 return FALSE;
5197         }
5199         if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
5200             !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
5201                 return FALSE;
5203         /* Restore the exact position or use the specialized restore
5204          * mode? */
5205         if (!view->p_restore)
5206                 status_restore(view);
5207         return TRUE;
5210 static bool
5211 status_draw(struct view *view, struct line *line, unsigned int lineno)
5213         struct status *status = line->data;
5214         enum line_type type;
5215         const char *text;
5217         if (!status) {
5218                 switch (line->type) {
5219                 case LINE_STAT_STAGED:
5220                         type = LINE_STAT_SECTION;
5221                         text = "Changes to be committed:";
5222                         break;
5224                 case LINE_STAT_UNSTAGED:
5225                         type = LINE_STAT_SECTION;
5226                         text = "Changed but not updated:";
5227                         break;
5229                 case LINE_STAT_UNTRACKED:
5230                         type = LINE_STAT_SECTION;
5231                         text = "Untracked files:";
5232                         break;
5234                 case LINE_STAT_NONE:
5235                         type = LINE_DEFAULT;
5236                         text = "  (no files)";
5237                         break;
5239                 case LINE_STAT_HEAD:
5240                         type = LINE_STAT_HEAD;
5241                         text = status_onbranch;
5242                         break;
5244                 default:
5245                         return FALSE;
5246                 }
5247         } else {
5248                 static char buf[] = { '?', ' ', ' ', ' ', 0 };
5250                 buf[0] = status->status;
5251                 if (draw_text(view, line->type, buf, TRUE))
5252                         return TRUE;
5253                 type = LINE_DEFAULT;
5254                 text = status->new.name;
5255         }
5257         draw_text(view, type, text, TRUE);
5258         return TRUE;
5261 static enum request
5262 status_load_error(struct view *view, struct view *stage, const char *path)
5264         if (displayed_views() == 2 || display[current_view] != view)
5265                 maximize_view(view);
5266         report("Failed to load '%s': %s", path, io_strerror(&stage->io));
5267         return REQ_NONE;
5270 static enum request
5271 status_enter(struct view *view, struct line *line)
5273         struct status *status = line->data;
5274         const char *oldpath = status ? status->old.name : NULL;
5275         /* Diffs for unmerged entries are empty when passing the new
5276          * path, so leave it empty. */
5277         const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
5278         const char *info;
5279         enum open_flags split;
5280         struct view *stage = VIEW(REQ_VIEW_STAGE);
5282         if (line->type == LINE_STAT_NONE ||
5283             (!status && line[1].type == LINE_STAT_NONE)) {
5284                 report("No file to diff");
5285                 return REQ_NONE;
5286         }
5288         switch (line->type) {
5289         case LINE_STAT_STAGED:
5290                 if (is_initial_commit()) {
5291                         const char *no_head_diff_argv[] = {
5292                                 "git", "diff", "--no-color", "--patch-with-stat",
5293                                         "--", "/dev/null", newpath, NULL
5294                         };
5296                         if (!prepare_update(stage, no_head_diff_argv, opt_cdup, FORMAT_DASH))
5297                                 return status_load_error(view, stage, newpath);
5298                 } else {
5299                         const char *index_show_argv[] = {
5300                                 "git", "diff-index", "--root", "--patch-with-stat",
5301                                         "-C", "-M", "--cached", "HEAD", "--",
5302                                         oldpath, newpath, NULL
5303                         };
5305                         if (!prepare_update(stage, index_show_argv, opt_cdup, FORMAT_DASH))
5306                                 return status_load_error(view, stage, newpath);
5307                 }
5309                 if (status)
5310                         info = "Staged changes to %s";
5311                 else
5312                         info = "Staged changes";
5313                 break;
5315         case LINE_STAT_UNSTAGED:
5316         {
5317                 const char *files_show_argv[] = {
5318                         "git", "diff-files", "--root", "--patch-with-stat",
5319                                 "-C", "-M", "--", oldpath, newpath, NULL
5320                 };
5322                 if (!prepare_update(stage, files_show_argv, opt_cdup, FORMAT_DASH))
5323                         return status_load_error(view, stage, newpath);
5324                 if (status)
5325                         info = "Unstaged changes to %s";
5326                 else
5327                         info = "Unstaged changes";
5328                 break;
5329         }
5330         case LINE_STAT_UNTRACKED:
5331                 if (!newpath) {
5332                         report("No file to show");
5333                         return REQ_NONE;
5334                 }
5336                 if (!suffixcmp(status->new.name, -1, "/")) {
5337                         report("Cannot display a directory");
5338                         return REQ_NONE;
5339                 }
5341                 if (!prepare_update_file(stage, newpath))
5342                         return status_load_error(view, stage, newpath);
5343                 info = "Untracked file %s";
5344                 break;
5346         case LINE_STAT_HEAD:
5347                 return REQ_NONE;
5349         default:
5350                 die("line type %d not handled in switch", line->type);
5351         }
5353         split = view_is_displayed(view) ? OPEN_SPLIT : 0;
5354         open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split);
5355         if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
5356                 if (status) {
5357                         stage_status = *status;
5358                 } else {
5359                         memset(&stage_status, 0, sizeof(stage_status));
5360                 }
5362                 stage_line_type = line->type;
5363                 stage_chunks = 0;
5364                 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
5365         }
5367         return REQ_NONE;
5370 static bool
5371 status_exists(struct status *status, enum line_type type)
5373         struct view *view = VIEW(REQ_VIEW_STATUS);
5374         unsigned long lineno;
5376         for (lineno = 0; lineno < view->lines; lineno++) {
5377                 struct line *line = &view->line[lineno];
5378                 struct status *pos = line->data;
5380                 if (line->type != type)
5381                         continue;
5382                 if (!pos && (!status || !status->status) && line[1].data) {
5383                         select_view_line(view, lineno);
5384                         return TRUE;
5385                 }
5386                 if (pos && !strcmp(status->new.name, pos->new.name)) {
5387                         select_view_line(view, lineno);
5388                         return TRUE;
5389                 }
5390         }
5392         return FALSE;
5396 static bool
5397 status_update_prepare(struct io *io, enum line_type type)
5399         const char *staged_argv[] = {
5400                 "git", "update-index", "-z", "--index-info", NULL
5401         };
5402         const char *others_argv[] = {
5403                 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
5404         };
5406         switch (type) {
5407         case LINE_STAT_STAGED:
5408                 return run_io(io, staged_argv, opt_cdup, IO_WR);
5410         case LINE_STAT_UNSTAGED:
5411                 return run_io(io, others_argv, opt_cdup, IO_WR);
5413         case LINE_STAT_UNTRACKED:
5414                 return run_io(io, others_argv, NULL, IO_WR);
5416         default:
5417                 die("line type %d not handled in switch", type);
5418                 return FALSE;
5419         }
5422 static bool
5423 status_update_write(struct io *io, struct status *status, enum line_type type)
5425         char buf[SIZEOF_STR];
5426         size_t bufsize = 0;
5428         switch (type) {
5429         case LINE_STAT_STAGED:
5430                 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
5431                                         status->old.mode,
5432                                         status->old.rev,
5433                                         status->old.name, 0))
5434                         return FALSE;
5435                 break;
5437         case LINE_STAT_UNSTAGED:
5438         case LINE_STAT_UNTRACKED:
5439                 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
5440                         return FALSE;
5441                 break;
5443         default:
5444                 die("line type %d not handled in switch", type);
5445         }
5447         return io_write(io, buf, bufsize);
5450 static bool
5451 status_update_file(struct status *status, enum line_type type)
5453         struct io io = {};
5454         bool result;
5456         if (!status_update_prepare(&io, type))
5457                 return FALSE;
5459         result = status_update_write(&io, status, type);
5460         return done_io(&io) && result;
5463 static bool
5464 status_update_files(struct view *view, struct line *line)
5466         char buf[sizeof(view->ref)];
5467         struct io io = {};
5468         bool result = TRUE;
5469         struct line *pos = view->line + view->lines;
5470         int files = 0;
5471         int file, done;
5472         int cursor_y, cursor_x;
5474         if (!status_update_prepare(&io, line->type))
5475                 return FALSE;
5477         for (pos = line; pos < view->line + view->lines && pos->data; pos++)
5478                 files++;
5480         string_copy(buf, view->ref);
5481         getsyx(cursor_y, cursor_x);
5482         for (file = 0, done = 5; result && file < files; line++, file++) {
5483                 int almost_done = file * 100 / files;
5485                 if (almost_done > done) {
5486                         done = almost_done;
5487                         string_format(view->ref, "updating file %u of %u (%d%% done)",
5488                                       file, files, done);
5489                         update_view_title(view);
5490                         setsyx(cursor_y, cursor_x);
5491                         doupdate();
5492                 }
5493                 result = status_update_write(&io, line->data, line->type);
5494         }
5495         string_copy(view->ref, buf);
5497         return done_io(&io) && result;
5500 static bool
5501 status_update(struct view *view)
5503         struct line *line = &view->line[view->lineno];
5505         assert(view->lines);
5507         if (!line->data) {
5508                 /* This should work even for the "On branch" line. */
5509                 if (line < view->line + view->lines && !line[1].data) {
5510                         report("Nothing to update");
5511                         return FALSE;
5512                 }
5514                 if (!status_update_files(view, line + 1)) {
5515                         report("Failed to update file status");
5516                         return FALSE;
5517                 }
5519         } else if (!status_update_file(line->data, line->type)) {
5520                 report("Failed to update file status");
5521                 return FALSE;
5522         }
5524         return TRUE;
5527 static bool
5528 status_revert(struct status *status, enum line_type type, bool has_none)
5530         if (!status || type != LINE_STAT_UNSTAGED) {
5531                 if (type == LINE_STAT_STAGED) {
5532                         report("Cannot revert changes to staged files");
5533                 } else if (type == LINE_STAT_UNTRACKED) {
5534                         report("Cannot revert changes to untracked files");
5535                 } else if (has_none) {
5536                         report("Nothing to revert");
5537                 } else {
5538                         report("Cannot revert changes to multiple files");
5539                 }
5540                 return FALSE;
5542         } else {
5543                 char mode[10] = "100644";
5544                 const char *reset_argv[] = {
5545                         "git", "update-index", "--cacheinfo", mode,
5546                                 status->old.rev, status->old.name, NULL
5547                 };
5548                 const char *checkout_argv[] = {
5549                         "git", "checkout", "--", status->old.name, NULL
5550                 };
5552                 if (!prompt_yesno("Are you sure you want to overwrite any changes?"))
5553                         return FALSE;
5554                 string_format(mode, "%o", status->old.mode);
5555                 return (status->status != 'U' || run_io_fg(reset_argv, opt_cdup)) &&
5556                         run_io_fg(checkout_argv, opt_cdup);
5557         }
5560 static enum request
5561 status_request(struct view *view, enum request request, struct line *line)
5563         struct status *status = line->data;
5565         switch (request) {
5566         case REQ_STATUS_UPDATE:
5567                 if (!status_update(view))
5568                         return REQ_NONE;
5569                 break;
5571         case REQ_STATUS_REVERT:
5572                 if (!status_revert(status, line->type, status_has_none(view, line)))
5573                         return REQ_NONE;
5574                 break;
5576         case REQ_STATUS_MERGE:
5577                 if (!status || status->status != 'U') {
5578                         report("Merging only possible for files with unmerged status ('U').");
5579                         return REQ_NONE;
5580                 }
5581                 open_mergetool(status->new.name);
5582                 break;
5584         case REQ_EDIT:
5585                 if (!status)
5586                         return request;
5587                 if (status->status == 'D') {
5588                         report("File has been deleted.");
5589                         return REQ_NONE;
5590                 }
5592                 open_editor(status->status != '?', status->new.name);
5593                 break;
5595         case REQ_VIEW_BLAME:
5596                 if (status) {
5597                         string_copy(opt_file, status->new.name);
5598                         opt_ref[0] = 0;
5599                 }
5600                 return request;
5602         case REQ_ENTER:
5603                 /* After returning the status view has been split to
5604                  * show the stage view. No further reloading is
5605                  * necessary. */
5606                 return status_enter(view, line);
5608         case REQ_REFRESH:
5609                 /* Simply reload the view. */
5610                 break;
5612         default:
5613                 return request;
5614         }
5616         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
5618         return REQ_NONE;
5621 static void
5622 status_select(struct view *view, struct line *line)
5624         struct status *status = line->data;
5625         char file[SIZEOF_STR] = "all files";
5626         const char *text;
5627         const char *key;
5629         if (status && !string_format(file, "'%s'", status->new.name))
5630                 return;
5632         if (!status && line[1].type == LINE_STAT_NONE)
5633                 line++;
5635         switch (line->type) {
5636         case LINE_STAT_STAGED:
5637                 text = "Press %s to unstage %s for commit";
5638                 break;
5640         case LINE_STAT_UNSTAGED:
5641                 text = "Press %s to stage %s for commit";
5642                 break;
5644         case LINE_STAT_UNTRACKED:
5645                 text = "Press %s to stage %s for addition";
5646                 break;
5648         case LINE_STAT_HEAD:
5649         case LINE_STAT_NONE:
5650                 text = "Nothing to update";
5651                 break;
5653         default:
5654                 die("line type %d not handled in switch", line->type);
5655         }
5657         if (status && status->status == 'U') {
5658                 text = "Press %s to resolve conflict in %s";
5659                 key = get_key(REQ_STATUS_MERGE);
5661         } else {
5662                 key = get_key(REQ_STATUS_UPDATE);
5663         }
5665         string_format(view->ref, text, key, file);
5668 static bool
5669 status_grep(struct view *view, struct line *line)
5671         struct status *status = line->data;
5673         if (status) {
5674                 const char buf[2] = { status->status, 0 };
5675                 const char *text[] = { status->new.name, buf, NULL };
5677                 return grep_text(view, text);
5678         }
5680         return FALSE;
5683 static struct view_ops status_ops = {
5684         "file",
5685         NULL,
5686         status_open,
5687         NULL,
5688         status_draw,
5689         status_request,
5690         status_grep,
5691         status_select,
5692 };
5695 static bool
5696 stage_diff_write(struct io *io, struct line *line, struct line *end)
5698         while (line < end) {
5699                 if (!io_write(io, line->data, strlen(line->data)) ||
5700                     !io_write(io, "\n", 1))
5701                         return FALSE;
5702                 line++;
5703                 if (line->type == LINE_DIFF_CHUNK ||
5704                     line->type == LINE_DIFF_HEADER)
5705                         break;
5706         }
5708         return TRUE;
5711 static struct line *
5712 stage_diff_find(struct view *view, struct line *line, enum line_type type)
5714         for (; view->line < line; line--)
5715                 if (line->type == type)
5716                         return line;
5718         return NULL;
5721 static bool
5722 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
5724         const char *apply_argv[SIZEOF_ARG] = {
5725                 "git", "apply", "--whitespace=nowarn", NULL
5726         };
5727         struct line *diff_hdr;
5728         struct io io = {};
5729         int argc = 3;
5731         diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
5732         if (!diff_hdr)
5733                 return FALSE;
5735         if (!revert)
5736                 apply_argv[argc++] = "--cached";
5737         if (revert || stage_line_type == LINE_STAT_STAGED)
5738                 apply_argv[argc++] = "-R";
5739         apply_argv[argc++] = "-";
5740         apply_argv[argc++] = NULL;
5741         if (!run_io(&io, apply_argv, opt_cdup, IO_WR))
5742                 return FALSE;
5744         if (!stage_diff_write(&io, diff_hdr, chunk) ||
5745             !stage_diff_write(&io, chunk, view->line + view->lines))
5746                 chunk = NULL;
5748         done_io(&io);
5749         run_io_bg(update_index_argv);
5751         return chunk ? TRUE : FALSE;
5754 static bool
5755 stage_update(struct view *view, struct line *line)
5757         struct line *chunk = NULL;
5759         if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
5760                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
5762         if (chunk) {
5763                 if (!stage_apply_chunk(view, chunk, FALSE)) {
5764                         report("Failed to apply chunk");
5765                         return FALSE;
5766                 }
5768         } else if (!stage_status.status) {
5769                 view = VIEW(REQ_VIEW_STATUS);
5771                 for (line = view->line; line < view->line + view->lines; line++)
5772                         if (line->type == stage_line_type)
5773                                 break;
5775                 if (!status_update_files(view, line + 1)) {
5776                         report("Failed to update files");
5777                         return FALSE;
5778                 }
5780         } else if (!status_update_file(&stage_status, stage_line_type)) {
5781                 report("Failed to update file");
5782                 return FALSE;
5783         }
5785         return TRUE;
5788 static bool
5789 stage_revert(struct view *view, struct line *line)
5791         struct line *chunk = NULL;
5793         if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
5794                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
5796         if (chunk) {
5797                 if (!prompt_yesno("Are you sure you want to revert changes?"))
5798                         return FALSE;
5800                 if (!stage_apply_chunk(view, chunk, TRUE)) {
5801                         report("Failed to revert chunk");
5802                         return FALSE;
5803                 }
5804                 return TRUE;
5806         } else {
5807                 return status_revert(stage_status.status ? &stage_status : NULL,
5808                                      stage_line_type, FALSE);
5809         }
5813 static void
5814 stage_next(struct view *view, struct line *line)
5816         int i;
5818         if (!stage_chunks) {
5819                 for (line = view->line; line < view->line + view->lines; line++) {
5820                         if (line->type != LINE_DIFF_CHUNK)
5821                                 continue;
5823                         if (!realloc_ints(&stage_chunk, stage_chunks, 1)) {
5824                                 report("Allocation failure");
5825                                 return;
5826                         }
5828                         stage_chunk[stage_chunks++] = line - view->line;
5829                 }
5830         }
5832         for (i = 0; i < stage_chunks; i++) {
5833                 if (stage_chunk[i] > view->lineno) {
5834                         do_scroll_view(view, stage_chunk[i] - view->lineno);
5835                         report("Chunk %d of %d", i + 1, stage_chunks);
5836                         return;
5837                 }
5838         }
5840         report("No next chunk found");
5843 static enum request
5844 stage_request(struct view *view, enum request request, struct line *line)
5846         switch (request) {
5847         case REQ_STATUS_UPDATE:
5848                 if (!stage_update(view, line))
5849                         return REQ_NONE;
5850                 break;
5852         case REQ_STATUS_REVERT:
5853                 if (!stage_revert(view, line))
5854                         return REQ_NONE;
5855                 break;
5857         case REQ_STAGE_NEXT:
5858                 if (stage_line_type == LINE_STAT_UNTRACKED) {
5859                         report("File is untracked; press %s to add",
5860                                get_key(REQ_STATUS_UPDATE));
5861                         return REQ_NONE;
5862                 }
5863                 stage_next(view, line);
5864                 return REQ_NONE;
5866         case REQ_EDIT:
5867                 if (!stage_status.new.name[0])
5868                         return request;
5869                 if (stage_status.status == 'D') {
5870                         report("File has been deleted.");
5871                         return REQ_NONE;
5872                 }
5874                 open_editor(stage_status.status != '?', stage_status.new.name);
5875                 break;
5877         case REQ_REFRESH:
5878                 /* Reload everything ... */
5879                 break;
5881         case REQ_VIEW_BLAME:
5882                 if (stage_status.new.name[0]) {
5883                         string_copy(opt_file, stage_status.new.name);
5884                         opt_ref[0] = 0;
5885                 }
5886                 return request;
5888         case REQ_ENTER:
5889                 return pager_request(view, request, line);
5891         default:
5892                 return request;
5893         }
5895         VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
5896         open_view(view, REQ_VIEW_STATUS, OPEN_REFRESH);
5898         /* Check whether the staged entry still exists, and close the
5899          * stage view if it doesn't. */
5900         if (!status_exists(&stage_status, stage_line_type)) {
5901                 status_restore(VIEW(REQ_VIEW_STATUS));
5902                 return REQ_VIEW_CLOSE;
5903         }
5905         if (stage_line_type == LINE_STAT_UNTRACKED) {
5906                 if (!suffixcmp(stage_status.new.name, -1, "/")) {
5907                         report("Cannot display a directory");
5908                         return REQ_NONE;
5909                 }
5911                 if (!prepare_update_file(view, stage_status.new.name)) {
5912                         report("Failed to open file: %s", strerror(errno));
5913                         return REQ_NONE;
5914                 }
5915         }
5916         open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
5918         return REQ_NONE;
5921 static struct view_ops stage_ops = {
5922         "line",
5923         NULL,
5924         NULL,
5925         pager_read,
5926         pager_draw,
5927         stage_request,
5928         pager_grep,
5929         pager_select,
5930 };
5933 /*
5934  * Revision graph
5935  */
5937 struct commit {
5938         char id[SIZEOF_REV];            /* SHA1 ID. */
5939         char title[128];                /* First line of the commit message. */
5940         const char *author;             /* Author of the commit. */
5941         time_t time;                    /* Date from the author ident. */
5942         struct ref_list *refs;          /* Repository references. */
5943         chtype graph[SIZEOF_REVGRAPH];  /* Ancestry chain graphics. */
5944         size_t graph_size;              /* The width of the graph array. */
5945         bool has_parents;               /* Rewritten --parents seen. */
5946 };
5948 /* Size of rev graph with no  "padding" columns */
5949 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
5951 struct rev_graph {
5952         struct rev_graph *prev, *next, *parents;
5953         char rev[SIZEOF_REVITEMS][SIZEOF_REV];
5954         size_t size;
5955         struct commit *commit;
5956         size_t pos;
5957         unsigned int boundary:1;
5958 };
5960 /* Parents of the commit being visualized. */
5961 static struct rev_graph graph_parents[4];
5963 /* The current stack of revisions on the graph. */
5964 static struct rev_graph graph_stacks[4] = {
5965         { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
5966         { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
5967         { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
5968         { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
5969 };
5971 static inline bool
5972 graph_parent_is_merge(struct rev_graph *graph)
5974         return graph->parents->size > 1;
5977 static inline void
5978 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
5980         struct commit *commit = graph->commit;
5982         if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
5983                 commit->graph[commit->graph_size++] = symbol;
5986 static void
5987 clear_rev_graph(struct rev_graph *graph)
5989         graph->boundary = 0;
5990         graph->size = graph->pos = 0;
5991         graph->commit = NULL;
5992         memset(graph->parents, 0, sizeof(*graph->parents));
5995 static void
5996 done_rev_graph(struct rev_graph *graph)
5998         if (graph_parent_is_merge(graph) &&
5999             graph->pos < graph->size - 1 &&
6000             graph->next->size == graph->size + graph->parents->size - 1) {
6001                 size_t i = graph->pos + graph->parents->size - 1;
6003                 graph->commit->graph_size = i * 2;
6004                 while (i < graph->next->size - 1) {
6005                         append_to_rev_graph(graph, ' ');
6006                         append_to_rev_graph(graph, '\\');
6007                         i++;
6008                 }
6009         }
6011         clear_rev_graph(graph);
6014 static void
6015 push_rev_graph(struct rev_graph *graph, const char *parent)
6017         int i;
6019         /* "Collapse" duplicate parents lines.
6020          *
6021          * FIXME: This needs to also update update the drawn graph but
6022          * for now it just serves as a method for pruning graph lines. */
6023         for (i = 0; i < graph->size; i++)
6024                 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
6025                         return;
6027         if (graph->size < SIZEOF_REVITEMS) {
6028                 string_copy_rev(graph->rev[graph->size++], parent);
6029         }
6032 static chtype
6033 get_rev_graph_symbol(struct rev_graph *graph)
6035         chtype symbol;
6037         if (graph->boundary)
6038                 symbol = REVGRAPH_BOUND;
6039         else if (graph->parents->size == 0)
6040                 symbol = REVGRAPH_INIT;
6041         else if (graph_parent_is_merge(graph))
6042                 symbol = REVGRAPH_MERGE;
6043         else if (graph->pos >= graph->size)
6044                 symbol = REVGRAPH_BRANCH;
6045         else
6046                 symbol = REVGRAPH_COMMIT;
6048         return symbol;
6051 static void
6052 draw_rev_graph(struct rev_graph *graph)
6054         struct rev_filler {
6055                 chtype separator, line;
6056         };
6057         enum { DEFAULT, RSHARP, RDIAG, LDIAG };
6058         static struct rev_filler fillers[] = {
6059                 { ' ',  '|' },
6060                 { '`',  '.' },
6061                 { '\'', ' ' },
6062                 { '/',  ' ' },
6063         };
6064         chtype symbol = get_rev_graph_symbol(graph);
6065         struct rev_filler *filler;
6066         size_t i;
6068         if (opt_line_graphics)
6069                 fillers[DEFAULT].line = line_graphics[LINE_GRAPHIC_VLINE];
6071         filler = &fillers[DEFAULT];
6073         for (i = 0; i < graph->pos; i++) {
6074                 append_to_rev_graph(graph, filler->line);
6075                 if (graph_parent_is_merge(graph->prev) &&
6076                     graph->prev->pos == i)
6077                         filler = &fillers[RSHARP];
6079                 append_to_rev_graph(graph, filler->separator);
6080         }
6082         /* Place the symbol for this revision. */
6083         append_to_rev_graph(graph, symbol);
6085         if (graph->prev->size > graph->size)
6086                 filler = &fillers[RDIAG];
6087         else
6088                 filler = &fillers[DEFAULT];
6090         i++;
6092         for (; i < graph->size; i++) {
6093                 append_to_rev_graph(graph, filler->separator);
6094                 append_to_rev_graph(graph, filler->line);
6095                 if (graph_parent_is_merge(graph->prev) &&
6096                     i < graph->prev->pos + graph->parents->size)
6097                         filler = &fillers[RSHARP];
6098                 if (graph->prev->size > graph->size)
6099                         filler = &fillers[LDIAG];
6100         }
6102         if (graph->prev->size > graph->size) {
6103                 append_to_rev_graph(graph, filler->separator);
6104                 if (filler->line != ' ')
6105                         append_to_rev_graph(graph, filler->line);
6106         }
6109 /* Prepare the next rev graph */
6110 static void
6111 prepare_rev_graph(struct rev_graph *graph)
6113         size_t i;
6115         /* First, traverse all lines of revisions up to the active one. */
6116         for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
6117                 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
6118                         break;
6120                 push_rev_graph(graph->next, graph->rev[graph->pos]);
6121         }
6123         /* Interleave the new revision parent(s). */
6124         for (i = 0; !graph->boundary && i < graph->parents->size; i++)
6125                 push_rev_graph(graph->next, graph->parents->rev[i]);
6127         /* Lastly, put any remaining revisions. */
6128         for (i = graph->pos + 1; i < graph->size; i++)
6129                 push_rev_graph(graph->next, graph->rev[i]);
6132 static void
6133 update_rev_graph(struct view *view, struct rev_graph *graph)
6135         /* If this is the finalizing update ... */
6136         if (graph->commit)
6137                 prepare_rev_graph(graph);
6139         /* Graph visualization needs a one rev look-ahead,
6140          * so the first update doesn't visualize anything. */
6141         if (!graph->prev->commit)
6142                 return;
6144         if (view->lines > 2)
6145                 view->line[view->lines - 3].dirty = 1;
6146         if (view->lines > 1)
6147                 view->line[view->lines - 2].dirty = 1;
6148         draw_rev_graph(graph->prev);
6149         done_rev_graph(graph->prev->prev);
6153 /*
6154  * Main view backend
6155  */
6157 static const char *main_argv[SIZEOF_ARG] = {
6158         "git", "log", "--no-color", "--pretty=raw", "--parents",
6159                       "--topo-order", "%(head)", NULL
6160 };
6162 static bool
6163 main_draw(struct view *view, struct line *line, unsigned int lineno)
6165         struct commit *commit = line->data;
6167         if (!commit->author)
6168                 return FALSE;
6170         if (opt_date && draw_date(view, &commit->time))
6171                 return TRUE;
6173         if (opt_author && draw_author(view, commit->author))
6174                 return TRUE;
6176         if (opt_rev_graph && commit->graph_size &&
6177             draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
6178                 return TRUE;
6180         if (opt_show_refs && commit->refs) {
6181                 size_t i;
6183                 for (i = 0; i < commit->refs->size; i++) {
6184                         struct ref *ref = commit->refs->refs[i];
6185                         enum line_type type;
6187                         if (ref->head)
6188                                 type = LINE_MAIN_HEAD;
6189                         else if (ref->ltag)
6190                                 type = LINE_MAIN_LOCAL_TAG;
6191                         else if (ref->tag)
6192                                 type = LINE_MAIN_TAG;
6193                         else if (ref->tracked)
6194                                 type = LINE_MAIN_TRACKED;
6195                         else if (ref->remote)
6196                                 type = LINE_MAIN_REMOTE;
6197                         else
6198                                 type = LINE_MAIN_REF;
6200                         if (draw_text(view, type, "[", TRUE) ||
6201                             draw_text(view, type, ref->name, TRUE) ||
6202                             draw_text(view, type, "]", TRUE))
6203                                 return TRUE;
6205                         if (draw_text(view, LINE_DEFAULT, " ", TRUE))
6206                                 return TRUE;
6207                 }
6208         }
6210         draw_text(view, LINE_DEFAULT, commit->title, TRUE);
6211         return TRUE;
6214 /* Reads git log --pretty=raw output and parses it into the commit struct. */
6215 static bool
6216 main_read(struct view *view, char *line)
6218         static struct rev_graph *graph = graph_stacks;
6219         enum line_type type;
6220         struct commit *commit;
6222         if (!line) {
6223                 int i;
6225                 if (!view->lines && !view->parent)
6226                         die("No revisions match the given arguments.");
6227                 if (view->lines > 0) {
6228                         commit = view->line[view->lines - 1].data;
6229                         view->line[view->lines - 1].dirty = 1;
6230                         if (!commit->author) {
6231                                 view->lines--;
6232                                 free(commit);
6233                                 graph->commit = NULL;
6234                         }
6235                 }
6236                 update_rev_graph(view, graph);
6238                 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
6239                         clear_rev_graph(&graph_stacks[i]);
6240                 return TRUE;
6241         }
6243         type = get_line_type(line);
6244         if (type == LINE_COMMIT) {
6245                 commit = calloc(1, sizeof(struct commit));
6246                 if (!commit)
6247                         return FALSE;
6249                 line += STRING_SIZE("commit ");
6250                 if (*line == '-') {
6251                         graph->boundary = 1;
6252                         line++;
6253                 }
6255                 string_copy_rev(commit->id, line);
6256                 commit->refs = get_ref_list(commit->id);
6257                 graph->commit = commit;
6258                 add_line_data(view, commit, LINE_MAIN_COMMIT);
6260                 while ((line = strchr(line, ' '))) {
6261                         line++;
6262                         push_rev_graph(graph->parents, line);
6263                         commit->has_parents = TRUE;
6264                 }
6265                 return TRUE;
6266         }
6268         if (!view->lines)
6269                 return TRUE;
6270         commit = view->line[view->lines - 1].data;
6272         switch (type) {
6273         case LINE_PARENT:
6274                 if (commit->has_parents)
6275                         break;
6276                 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
6277                 break;
6279         case LINE_AUTHOR:
6280                 parse_author_line(line + STRING_SIZE("author "),
6281                                   &commit->author, &commit->time);
6282                 update_rev_graph(view, graph);
6283                 graph = graph->next;
6284                 break;
6286         default:
6287                 /* Fill in the commit title if it has not already been set. */
6288                 if (commit->title[0])
6289                         break;
6291                 /* Require titles to start with a non-space character at the
6292                  * offset used by git log. */
6293                 if (strncmp(line, "    ", 4))
6294                         break;
6295                 line += 4;
6296                 /* Well, if the title starts with a whitespace character,
6297                  * try to be forgiving.  Otherwise we end up with no title. */
6298                 while (isspace(*line))
6299                         line++;
6300                 if (*line == '\0')
6301                         break;
6302                 /* FIXME: More graceful handling of titles; append "..." to
6303                  * shortened titles, etc. */
6305                 string_expand(commit->title, sizeof(commit->title), line, 1);
6306                 view->line[view->lines - 1].dirty = 1;
6307         }
6309         return TRUE;
6312 static enum request
6313 main_request(struct view *view, enum request request, struct line *line)
6315         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
6317         switch (request) {
6318         case REQ_ENTER:
6319                 open_view(view, REQ_VIEW_DIFF, flags);
6320                 break;
6321         case REQ_REFRESH:
6322                 load_refs();
6323                 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
6324                 break;
6325         default:
6326                 return request;
6327         }
6329         return REQ_NONE;
6332 static bool
6333 grep_refs(struct ref_list *list, regex_t *regex)
6335         regmatch_t pmatch;
6336         size_t i;
6338         if (!opt_show_refs || !list)
6339                 return FALSE;
6341         for (i = 0; i < list->size; i++) {
6342                 if (regexec(regex, list->refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
6343                         return TRUE;
6344         }
6346         return FALSE;
6349 static bool
6350 main_grep(struct view *view, struct line *line)
6352         struct commit *commit = line->data;
6353         const char *text[] = {
6354                 commit->title,
6355                 opt_author ? commit->author : "",
6356                 opt_date ? mkdate(&commit->time) : "",
6357                 NULL
6358         };
6360         return grep_text(view, text) || grep_refs(commit->refs, view->regex);
6363 static void
6364 main_select(struct view *view, struct line *line)
6366         struct commit *commit = line->data;
6368         string_copy_rev(view->ref, commit->id);
6369         string_copy_rev(ref_commit, view->ref);
6372 static struct view_ops main_ops = {
6373         "commit",
6374         main_argv,
6375         NULL,
6376         main_read,
6377         main_draw,
6378         main_request,
6379         main_grep,
6380         main_select,
6381 };
6384 /*
6385  * Unicode / UTF-8 handling
6386  *
6387  * NOTE: Much of the following code for dealing with Unicode is derived from
6388  * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
6389  * src/intl/charset.c from the UTF-8 branch commit elinks-0.11.0-g31f2c28.
6390  */
6392 static inline int
6393 unicode_width(unsigned long c)
6395         if (c >= 0x1100 &&
6396            (c <= 0x115f                         /* Hangul Jamo */
6397             || c == 0x2329
6398             || c == 0x232a
6399             || (c >= 0x2e80  && c <= 0xa4cf && c != 0x303f)
6400                                                 /* CJK ... Yi */
6401             || (c >= 0xac00  && c <= 0xd7a3)    /* Hangul Syllables */
6402             || (c >= 0xf900  && c <= 0xfaff)    /* CJK Compatibility Ideographs */
6403             || (c >= 0xfe30  && c <= 0xfe6f)    /* CJK Compatibility Forms */
6404             || (c >= 0xff00  && c <= 0xff60)    /* Fullwidth Forms */
6405             || (c >= 0xffe0  && c <= 0xffe6)
6406             || (c >= 0x20000 && c <= 0x2fffd)
6407             || (c >= 0x30000 && c <= 0x3fffd)))
6408                 return 2;
6410         if (c == '\t')
6411                 return opt_tab_size;
6413         return 1;
6416 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
6417  * Illegal bytes are set one. */
6418 static const unsigned char utf8_bytes[256] = {
6419         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,
6420         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,
6421         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,
6422         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,
6423         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,
6424         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,
6425         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,
6426         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,
6427 };
6429 /* Decode UTF-8 multi-byte representation into a Unicode character. */
6430 static inline unsigned long
6431 utf8_to_unicode(const char *string, size_t length)
6433         unsigned long unicode;
6435         switch (length) {
6436         case 1:
6437                 unicode  =   string[0];
6438                 break;
6439         case 2:
6440                 unicode  =  (string[0] & 0x1f) << 6;
6441                 unicode +=  (string[1] & 0x3f);
6442                 break;
6443         case 3:
6444                 unicode  =  (string[0] & 0x0f) << 12;
6445                 unicode += ((string[1] & 0x3f) << 6);
6446                 unicode +=  (string[2] & 0x3f);
6447                 break;
6448         case 4:
6449                 unicode  =  (string[0] & 0x0f) << 18;
6450                 unicode += ((string[1] & 0x3f) << 12);
6451                 unicode += ((string[2] & 0x3f) << 6);
6452                 unicode +=  (string[3] & 0x3f);
6453                 break;
6454         case 5:
6455                 unicode  =  (string[0] & 0x0f) << 24;
6456                 unicode += ((string[1] & 0x3f) << 18);
6457                 unicode += ((string[2] & 0x3f) << 12);
6458                 unicode += ((string[3] & 0x3f) << 6);
6459                 unicode +=  (string[4] & 0x3f);
6460                 break;
6461         case 6:
6462                 unicode  =  (string[0] & 0x01) << 30;
6463                 unicode += ((string[1] & 0x3f) << 24);
6464                 unicode += ((string[2] & 0x3f) << 18);
6465                 unicode += ((string[3] & 0x3f) << 12);
6466                 unicode += ((string[4] & 0x3f) << 6);
6467                 unicode +=  (string[5] & 0x3f);
6468                 break;
6469         default:
6470                 die("Invalid Unicode length");
6471         }
6473         /* Invalid characters could return the special 0xfffd value but NUL
6474          * should be just as good. */
6475         return unicode > 0xffff ? 0 : unicode;
6478 /* Calculates how much of string can be shown within the given maximum width
6479  * and sets trimmed parameter to non-zero value if all of string could not be
6480  * shown. If the reserve flag is TRUE, it will reserve at least one
6481  * trailing character, which can be useful when drawing a delimiter.
6482  *
6483  * Returns the number of bytes to output from string to satisfy max_width. */
6484 static size_t
6485 utf8_length(const char **start, size_t skip, int *width, size_t max_width, int *trimmed, bool reserve)
6487         const char *string = *start;
6488         const char *end = strchr(string, '\0');
6489         unsigned char last_bytes = 0;
6490         size_t last_ucwidth = 0;
6492         *width = 0;
6493         *trimmed = 0;
6495         while (string < end) {
6496                 int c = *(unsigned char *) string;
6497                 unsigned char bytes = utf8_bytes[c];
6498                 size_t ucwidth;
6499                 unsigned long unicode;
6501                 if (string + bytes > end)
6502                         break;
6504                 /* Change representation to figure out whether
6505                  * it is a single- or double-width character. */
6507                 unicode = utf8_to_unicode(string, bytes);
6508                 /* FIXME: Graceful handling of invalid Unicode character. */
6509                 if (!unicode)
6510                         break;
6512                 ucwidth = unicode_width(unicode);
6513                 if (skip > 0) {
6514                         skip -= ucwidth <= skip ? ucwidth : skip;
6515                         *start += bytes;
6516                 }
6517                 *width  += ucwidth;
6518                 if (*width > max_width) {
6519                         *trimmed = 1;
6520                         *width -= ucwidth;
6521                         if (reserve && *width == max_width) {
6522                                 string -= last_bytes;
6523                                 *width -= last_ucwidth;
6524                         }
6525                         break;
6526                 }
6528                 string  += bytes;
6529                 last_bytes = ucwidth ? bytes : 0;
6530                 last_ucwidth = ucwidth;
6531         }
6533         return string - *start;
6537 /*
6538  * Status management
6539  */
6541 /* Whether or not the curses interface has been initialized. */
6542 static bool cursed = FALSE;
6544 /* Terminal hacks and workarounds. */
6545 static bool use_scroll_redrawwin;
6546 static bool use_scroll_status_wclear;
6548 /* The status window is used for polling keystrokes. */
6549 static WINDOW *status_win;
6551 /* Reading from the prompt? */
6552 static bool input_mode = FALSE;
6554 static bool status_empty = FALSE;
6556 /* Update status and title window. */
6557 static void
6558 report(const char *msg, ...)
6560         struct view *view = display[current_view];
6562         if (input_mode)
6563                 return;
6565         if (!view) {
6566                 char buf[SIZEOF_STR];
6567                 va_list args;
6569                 va_start(args, msg);
6570                 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
6571                         buf[sizeof(buf) - 1] = 0;
6572                         buf[sizeof(buf) - 2] = '.';
6573                         buf[sizeof(buf) - 3] = '.';
6574                         buf[sizeof(buf) - 4] = '.';
6575                 }
6576                 va_end(args);
6577                 die("%s", buf);
6578         }
6580         if (!status_empty || *msg) {
6581                 va_list args;
6583                 va_start(args, msg);
6585                 wmove(status_win, 0, 0);
6586                 if (view->has_scrolled && use_scroll_status_wclear)
6587                         wclear(status_win);
6588                 if (*msg) {
6589                         vwprintw(status_win, msg, args);
6590                         status_empty = FALSE;
6591                 } else {
6592                         status_empty = TRUE;
6593                 }
6594                 wclrtoeol(status_win);
6595                 wnoutrefresh(status_win);
6597                 va_end(args);
6598         }
6600         update_view_title(view);
6603 /* Controls when nodelay should be in effect when polling user input. */
6604 static void
6605 set_nonblocking_input(bool loading)
6607         static unsigned int loading_views;
6609         if ((loading == FALSE && loading_views-- == 1) ||
6610             (loading == TRUE  && loading_views++ == 0))
6611                 nodelay(status_win, loading);
6614 static void
6615 init_display(void)
6617         const char *term;
6618         int x, y;
6620         /* Initialize the curses library */
6621         if (isatty(STDIN_FILENO)) {
6622                 cursed = !!initscr();
6623                 opt_tty = stdin;
6624         } else {
6625                 /* Leave stdin and stdout alone when acting as a pager. */
6626                 opt_tty = fopen("/dev/tty", "r+");
6627                 if (!opt_tty)
6628                         die("Failed to open /dev/tty");
6629                 cursed = !!newterm(NULL, opt_tty, opt_tty);
6630         }
6632         if (!cursed)
6633                 die("Failed to initialize curses");
6635         nonl();         /* Disable conversion and detect newlines from input. */
6636         cbreak();       /* Take input chars one at a time, no wait for \n */
6637         noecho();       /* Don't echo input */
6638         leaveok(stdscr, FALSE);
6640         if (has_colors())
6641                 init_colors();
6643         getmaxyx(stdscr, y, x);
6644         status_win = newwin(1, 0, y - 1, 0);
6645         if (!status_win)
6646                 die("Failed to create status window");
6648         /* Enable keyboard mapping */
6649         keypad(status_win, TRUE);
6650         wbkgdset(status_win, get_line_attr(LINE_STATUS));
6652         TABSIZE = opt_tab_size;
6653         if (opt_line_graphics) {
6654                 line_graphics[LINE_GRAPHIC_VLINE] = ACS_VLINE;
6655         }
6657         term = getenv("XTERM_VERSION") ? NULL : getenv("COLORTERM");
6658         if (term && !strcmp(term, "gnome-terminal")) {
6659                 /* In the gnome-terminal-emulator, the message from
6660                  * scrolling up one line when impossible followed by
6661                  * scrolling down one line causes corruption of the
6662                  * status line. This is fixed by calling wclear. */
6663                 use_scroll_status_wclear = TRUE;
6664                 use_scroll_redrawwin = FALSE;
6666         } else if (term && !strcmp(term, "xrvt-xpm")) {
6667                 /* No problems with full optimizations in xrvt-(unicode)
6668                  * and aterm. */
6669                 use_scroll_status_wclear = use_scroll_redrawwin = FALSE;
6671         } else {
6672                 /* When scrolling in (u)xterm the last line in the
6673                  * scrolling direction will update slowly. */
6674                 use_scroll_redrawwin = TRUE;
6675                 use_scroll_status_wclear = FALSE;
6676         }
6679 static int
6680 get_input(int prompt_position)
6682         struct view *view;
6683         int i, key, cursor_y, cursor_x;
6685         if (prompt_position)
6686                 input_mode = TRUE;
6688         while (TRUE) {
6689                 foreach_view (view, i) {
6690                         update_view(view);
6691                         if (view_is_displayed(view) && view->has_scrolled &&
6692                             use_scroll_redrawwin)
6693                                 redrawwin(view->win);
6694                         view->has_scrolled = FALSE;
6695                 }
6697                 /* Update the cursor position. */
6698                 if (prompt_position) {
6699                         getbegyx(status_win, cursor_y, cursor_x);
6700                         cursor_x = prompt_position;
6701                 } else {
6702                         view = display[current_view];
6703                         getbegyx(view->win, cursor_y, cursor_x);
6704                         cursor_x = view->width - 1;
6705                         cursor_y += view->lineno - view->offset;
6706                 }
6707                 setsyx(cursor_y, cursor_x);
6709                 /* Refresh, accept single keystroke of input */
6710                 doupdate();
6711                 key = wgetch(status_win);
6713                 /* wgetch() with nodelay() enabled returns ERR when
6714                  * there's no input. */
6715                 if (key == ERR) {
6717                 } else if (key == KEY_RESIZE) {
6718                         int height, width;
6720                         getmaxyx(stdscr, height, width);
6722                         wresize(status_win, 1, width);
6723                         mvwin(status_win, height - 1, 0);
6724                         wnoutrefresh(status_win);
6725                         resize_display();
6726                         redraw_display(TRUE);
6728                 } else {
6729                         input_mode = FALSE;
6730                         return key;
6731                 }
6732         }
6735 static char *
6736 prompt_input(const char *prompt, input_handler handler, void *data)
6738         enum input_status status = INPUT_OK;
6739         static char buf[SIZEOF_STR];
6740         size_t pos = 0;
6742         buf[pos] = 0;
6744         while (status == INPUT_OK || status == INPUT_SKIP) {
6745                 int key;
6747                 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
6748                 wclrtoeol(status_win);
6750                 key = get_input(pos + 1);
6751                 switch (key) {
6752                 case KEY_RETURN:
6753                 case KEY_ENTER:
6754                 case '\n':
6755                         status = pos ? INPUT_STOP : INPUT_CANCEL;
6756                         break;
6758                 case KEY_BACKSPACE:
6759                         if (pos > 0)
6760                                 buf[--pos] = 0;
6761                         else
6762                                 status = INPUT_CANCEL;
6763                         break;
6765                 case KEY_ESC:
6766                         status = INPUT_CANCEL;
6767                         break;
6769                 default:
6770                         if (pos >= sizeof(buf)) {
6771                                 report("Input string too long");
6772                                 return NULL;
6773                         }
6775                         status = handler(data, buf, key);
6776                         if (status == INPUT_OK)
6777                                 buf[pos++] = (char) key;
6778                 }
6779         }
6781         /* Clear the status window */
6782         status_empty = FALSE;
6783         report("");
6785         if (status == INPUT_CANCEL)
6786                 return NULL;
6788         buf[pos++] = 0;
6790         return buf;
6793 static enum input_status
6794 prompt_yesno_handler(void *data, char *buf, int c)
6796         if (c == 'y' || c == 'Y')
6797                 return INPUT_STOP;
6798         if (c == 'n' || c == 'N')
6799                 return INPUT_CANCEL;
6800         return INPUT_SKIP;
6803 static bool
6804 prompt_yesno(const char *prompt)
6806         char prompt2[SIZEOF_STR];
6808         if (!string_format(prompt2, "%s [Yy/Nn]", prompt))
6809                 return FALSE;
6811         return !!prompt_input(prompt2, prompt_yesno_handler, NULL);
6814 static enum input_status
6815 read_prompt_handler(void *data, char *buf, int c)
6817         return isprint(c) ? INPUT_OK : INPUT_SKIP;
6820 static char *
6821 read_prompt(const char *prompt)
6823         return prompt_input(prompt, read_prompt_handler, NULL);
6826 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected)
6828         enum input_status status = INPUT_OK;
6829         int size = 0;
6831         while (items[size].text)
6832                 size++;
6834         while (status == INPUT_OK) {
6835                 const struct menu_item *item = &items[*selected];
6836                 int key;
6837                 int i;
6839                 mvwprintw(status_win, 0, 0, "%s (%d of %d) ",
6840                           prompt, *selected + 1, size);
6841                 if (item->hotkey)
6842                         wprintw(status_win, "[%c] ", (char) item->hotkey);
6843                 wprintw(status_win, "%s", item->text);
6844                 wclrtoeol(status_win);
6846                 key = get_input(COLS - 1);
6847                 switch (key) {
6848                 case KEY_RETURN:
6849                 case KEY_ENTER:
6850                 case '\n':
6851                         status = INPUT_STOP;
6852                         break;
6854                 case KEY_LEFT:
6855                 case KEY_UP:
6856                         *selected = *selected - 1;
6857                         if (*selected < 0)
6858                                 *selected = size - 1;
6859                         break;
6861                 case KEY_RIGHT:
6862                 case KEY_DOWN:
6863                         *selected = (*selected + 1) % size;
6864                         break;
6866                 case KEY_ESC:
6867                         status = INPUT_CANCEL;
6868                         break;
6870                 default:
6871                         for (i = 0; items[i].text; i++)
6872                                 if (items[i].hotkey == key) {
6873                                         *selected = i;
6874                                         status = INPUT_STOP;
6875                                         break;
6876                                 }
6877                 }
6878         }
6880         /* Clear the status window */
6881         status_empty = FALSE;
6882         report("");
6884         return status != INPUT_CANCEL;
6887 /*
6888  * Repository properties
6889  */
6891 static struct ref **refs = NULL;
6892 static size_t refs_size = 0;
6894 static struct ref_list **ref_lists = NULL;
6895 static size_t ref_lists_size = 0;
6897 DEFINE_ALLOCATOR(realloc_refs, struct ref *, 256)
6898 DEFINE_ALLOCATOR(realloc_refs_list, struct ref *, 8)
6899 DEFINE_ALLOCATOR(realloc_ref_lists, struct ref_list *, 8)
6901 static int
6902 compare_refs(const void *ref1_, const void *ref2_)
6904         const struct ref *ref1 = *(const struct ref **)ref1_;
6905         const struct ref *ref2 = *(const struct ref **)ref2_;
6907         if (ref1->tag != ref2->tag)
6908                 return ref2->tag - ref1->tag;
6909         if (ref1->ltag != ref2->ltag)
6910                 return ref2->ltag - ref2->ltag;
6911         if (ref1->head != ref2->head)
6912                 return ref2->head - ref1->head;
6913         if (ref1->tracked != ref2->tracked)
6914                 return ref2->tracked - ref1->tracked;
6915         if (ref1->remote != ref2->remote)
6916                 return ref2->remote - ref1->remote;
6917         return strcmp(ref1->name, ref2->name);
6920 static void
6921 foreach_ref(bool (*visitor)(void *data, struct ref *ref), void *data)
6923         size_t i;
6925         for (i = 0; i < refs_size; i++)
6926                 if (!visitor(data, refs[i]))
6927                         break;
6930 static struct ref_list *
6931 get_ref_list(const char *id)
6933         struct ref_list *list;
6934         size_t i;
6936         for (i = 0; i < ref_lists_size; i++)
6937                 if (!strcmp(id, ref_lists[i]->id))
6938                         return ref_lists[i];
6940         if (!realloc_ref_lists(&ref_lists, ref_lists_size, 1))
6941                 return NULL;
6942         list = calloc(1, sizeof(*list));
6943         if (!list)
6944                 return NULL;
6946         for (i = 0; i < refs_size; i++) {
6947                 if (!strcmp(id, refs[i]->id) &&
6948                     realloc_refs_list(&list->refs, list->size, 1))
6949                         list->refs[list->size++] = refs[i];
6950         }
6952         if (!list->refs) {
6953                 free(list);
6954                 return NULL;
6955         }
6957         qsort(list->refs, list->size, sizeof(*list->refs), compare_refs);
6958         ref_lists[ref_lists_size++] = list;
6959         return list;
6962 static int
6963 read_ref(char *id, size_t idlen, char *name, size_t namelen)
6965         struct ref *ref = NULL;
6966         bool tag = FALSE;
6967         bool ltag = FALSE;
6968         bool remote = FALSE;
6969         bool tracked = FALSE;
6970         bool head = FALSE;
6971         int from = 0, to = refs_size - 1;
6973         if (!prefixcmp(name, "refs/tags/")) {
6974                 if (!suffixcmp(name, namelen, "^{}")) {
6975                         namelen -= 3;
6976                         name[namelen] = 0;
6977                 } else {
6978                         ltag = TRUE;
6979                 }
6981                 tag = TRUE;
6982                 namelen -= STRING_SIZE("refs/tags/");
6983                 name    += STRING_SIZE("refs/tags/");
6985         } else if (!prefixcmp(name, "refs/remotes/")) {
6986                 remote = TRUE;
6987                 namelen -= STRING_SIZE("refs/remotes/");
6988                 name    += STRING_SIZE("refs/remotes/");
6989                 tracked  = !strcmp(opt_remote, name);
6991         } else if (!prefixcmp(name, "refs/heads/")) {
6992                 namelen -= STRING_SIZE("refs/heads/");
6993                 name    += STRING_SIZE("refs/heads/");
6994                 head     = !strncmp(opt_head, name, namelen);
6996         } else if (!strcmp(name, "HEAD")) {
6997                 string_ncopy(opt_head_rev, id, idlen);
6998                 return OK;
6999         }
7001         /* If we are reloading or it's an annotated tag, replace the
7002          * previous SHA1 with the resolved commit id; relies on the fact
7003          * git-ls-remote lists the commit id of an annotated tag right
7004          * before the commit id it points to. */
7005         while (from <= to) {
7006                 size_t pos = (to + from) / 2;
7007                 int cmp = strcmp(name, refs[pos]->name);
7009                 if (!cmp) {
7010                         ref = refs[pos];
7011                         break;
7012                 }
7014                 if (cmp < 0)
7015                         to = pos - 1;
7016                 else
7017                         from = pos + 1;
7018         }
7020         if (!ref) {
7021                 if (!realloc_refs(&refs, refs_size, 1))
7022                         return ERR;
7023                 ref = calloc(1, sizeof(*ref) + namelen);
7024                 if (!ref)
7025                         return ERR;
7026                 memmove(refs + from + 1, refs + from,
7027                         (refs_size - from) * sizeof(*refs));
7028                 refs[from] = ref;
7029                 strncpy(ref->name, name, namelen);
7030                 refs_size++;
7031         }
7033         ref->head = head;
7034         ref->tag = tag;
7035         ref->ltag = ltag;
7036         ref->remote = remote;
7037         ref->tracked = tracked;
7038         string_copy_rev(ref->id, id);
7040         return OK;
7043 static int
7044 load_refs(void)
7046         const char *head_argv[] = {
7047                 "git", "symbolic-ref", "HEAD", NULL
7048         };
7049         static const char *ls_remote_argv[SIZEOF_ARG] = {
7050                 "git", "ls-remote", opt_git_dir, NULL
7051         };
7052         static bool init = FALSE;
7053         size_t i;
7055         if (!init) {
7056                 argv_from_env(ls_remote_argv, "TIG_LS_REMOTE");
7057                 init = TRUE;
7058         }
7060         if (!*opt_git_dir)
7061                 return OK;
7063         if (run_io_buf(head_argv, opt_head, sizeof(opt_head)) &&
7064             !prefixcmp(opt_head, "refs/heads/")) {
7065                 char *offset = opt_head + STRING_SIZE("refs/heads/");
7067                 memmove(opt_head, offset, strlen(offset) + 1);
7068         }
7070         for (i = 0; i < refs_size; i++)
7071                 refs[i]->id[0] = 0;
7073         if (run_io_load(ls_remote_argv, "\t", read_ref) == ERR)
7074                 return ERR;
7076         /* Update the ref lists to reflect changes. */
7077         for (i = 0; i < ref_lists_size; i++) {
7078                 struct ref_list *list = ref_lists[i];
7079                 size_t old, new;
7081                 for (old = new = 0; old < list->size; old++)
7082                         if (!strcmp(list->id, list->refs[old]->id))
7083                                 list->refs[new++] = list->refs[old];
7084                 list->size = new;
7085         }
7087         return OK;
7090 static void
7091 set_remote_branch(const char *name, const char *value, size_t valuelen)
7093         if (!strcmp(name, ".remote")) {
7094                 string_ncopy(opt_remote, value, valuelen);
7096         } else if (*opt_remote && !strcmp(name, ".merge")) {
7097                 size_t from = strlen(opt_remote);
7099                 if (!prefixcmp(value, "refs/heads/"))
7100                         value += STRING_SIZE("refs/heads/");
7102                 if (!string_format_from(opt_remote, &from, "/%s", value))
7103                         opt_remote[0] = 0;
7104         }
7107 static void
7108 set_repo_config_option(char *name, char *value, int (*cmd)(int, const char **))
7110         const char *argv[SIZEOF_ARG] = { name, "=" };
7111         int argc = 1 + (cmd == option_set_command);
7112         int error = ERR;
7114         if (!argv_from_string(argv, &argc, value))
7115                 config_msg = "Too many option arguments";
7116         else
7117                 error = cmd(argc, argv);
7119         if (error == ERR)
7120                 warn("Option 'tig.%s': %s", name, config_msg);
7123 static bool
7124 set_environment_variable(const char *name, const char *value)
7126         size_t len = strlen(name) + 1 + strlen(value) + 1;
7127         char *env = malloc(len);
7129         if (env &&
7130             string_nformat(env, len, NULL, "%s=%s", name, value) &&
7131             putenv(env) == 0)
7132                 return TRUE;
7133         free(env);
7134         return FALSE;
7137 static void
7138 set_work_tree(const char *value)
7140         char cwd[SIZEOF_STR];
7142         if (!getcwd(cwd, sizeof(cwd)))
7143                 die("Failed to get cwd path: %s", strerror(errno));
7144         if (chdir(opt_git_dir) < 0)
7145                 die("Failed to chdir(%s): %s", strerror(errno));
7146         if (!getcwd(opt_git_dir, sizeof(opt_git_dir)))
7147                 die("Failed to get git path: %s", strerror(errno));
7148         if (chdir(cwd) < 0)
7149                 die("Failed to chdir(%s): %s", cwd, strerror(errno));
7150         if (chdir(value) < 0)
7151                 die("Failed to chdir(%s): %s", value, strerror(errno));
7152         if (!getcwd(cwd, sizeof(cwd)))
7153                 die("Failed to get cwd path: %s", strerror(errno));
7154         if (!set_environment_variable("GIT_WORK_TREE", cwd))
7155                 die("Failed to set GIT_WORK_TREE to '%s'", cwd);
7156         if (!set_environment_variable("GIT_DIR", opt_git_dir))
7157                 die("Failed to set GIT_DIR to '%s'", opt_git_dir);
7158         opt_is_inside_work_tree = TRUE;
7161 static int
7162 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
7164         if (!strcmp(name, "i18n.commitencoding"))
7165                 string_ncopy(opt_encoding, value, valuelen);
7167         else if (!strcmp(name, "core.editor"))
7168                 string_ncopy(opt_editor, value, valuelen);
7170         else if (!strcmp(name, "core.worktree"))
7171                 set_work_tree(value);
7173         else if (!prefixcmp(name, "tig.color."))
7174                 set_repo_config_option(name + 10, value, option_color_command);
7176         else if (!prefixcmp(name, "tig.bind."))
7177                 set_repo_config_option(name + 9, value, option_bind_command);
7179         else if (!prefixcmp(name, "tig."))
7180                 set_repo_config_option(name + 4, value, option_set_command);
7182         else if (*opt_head && !prefixcmp(name, "branch.") &&
7183                  !strncmp(name + 7, opt_head, strlen(opt_head)))
7184                 set_remote_branch(name + 7 + strlen(opt_head), value, valuelen);
7186         return OK;
7189 static int
7190 load_git_config(void)
7192         const char *config_list_argv[] = { "git", GIT_CONFIG, "--list", NULL };
7194         return run_io_load(config_list_argv, "=", read_repo_config_option);
7197 static int
7198 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
7200         if (!opt_git_dir[0]) {
7201                 string_ncopy(opt_git_dir, name, namelen);
7203         } else if (opt_is_inside_work_tree == -1) {
7204                 /* This can be 3 different values depending on the
7205                  * version of git being used. If git-rev-parse does not
7206                  * understand --is-inside-work-tree it will simply echo
7207                  * the option else either "true" or "false" is printed.
7208                  * Default to true for the unknown case. */
7209                 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
7211         } else if (*name == '.') {
7212                 string_ncopy(opt_cdup, name, namelen);
7214         } else {
7215                 string_ncopy(opt_prefix, name, namelen);
7216         }
7218         return OK;
7221 static int
7222 load_repo_info(void)
7224         const char *rev_parse_argv[] = {
7225                 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
7226                         "--show-cdup", "--show-prefix", NULL
7227         };
7229         return run_io_load(rev_parse_argv, "=", read_repo_info);
7233 /*
7234  * Main
7235  */
7237 static const char usage[] =
7238 "tig " TIG_VERSION " (" __DATE__ ")\n"
7239 "\n"
7240 "Usage: tig        [options] [revs] [--] [paths]\n"
7241 "   or: tig show   [options] [revs] [--] [paths]\n"
7242 "   or: tig blame  [rev] path\n"
7243 "   or: tig status\n"
7244 "   or: tig <      [git command output]\n"
7245 "\n"
7246 "Options:\n"
7247 "  -v, --version   Show version and exit\n"
7248 "  -h, --help      Show help message and exit";
7250 static void __NORETURN
7251 quit(int sig)
7253         /* XXX: Restore tty modes and let the OS cleanup the rest! */
7254         if (cursed)
7255                 endwin();
7256         exit(0);
7259 static void __NORETURN
7260 die(const char *err, ...)
7262         va_list args;
7264         endwin();
7266         va_start(args, err);
7267         fputs("tig: ", stderr);
7268         vfprintf(stderr, err, args);
7269         fputs("\n", stderr);
7270         va_end(args);
7272         exit(1);
7275 static void
7276 warn(const char *msg, ...)
7278         va_list args;
7280         va_start(args, msg);
7281         fputs("tig warning: ", stderr);
7282         vfprintf(stderr, msg, args);
7283         fputs("\n", stderr);
7284         va_end(args);
7287 static enum request
7288 parse_options(int argc, const char *argv[])
7290         enum request request = REQ_VIEW_MAIN;
7291         const char *subcommand;
7292         bool seen_dashdash = FALSE;
7293         /* XXX: This is vulnerable to the user overriding options
7294          * required for the main view parser. */
7295         const char *custom_argv[SIZEOF_ARG] = {
7296                 "git", "log", "--no-color", "--pretty=raw", "--parents",
7297                         "--topo-order", NULL
7298         };
7299         int i, j = 6;
7301         if (!isatty(STDIN_FILENO)) {
7302                 io_open(&VIEW(REQ_VIEW_PAGER)->io, "");
7303                 return REQ_VIEW_PAGER;
7304         }
7306         if (argc <= 1)
7307                 return REQ_NONE;
7309         subcommand = argv[1];
7310         if (!strcmp(subcommand, "status")) {
7311                 if (argc > 2)
7312                         warn("ignoring arguments after `%s'", subcommand);
7313                 return REQ_VIEW_STATUS;
7315         } else if (!strcmp(subcommand, "blame")) {
7316                 if (argc <= 2 || argc > 4)
7317                         die("invalid number of options to blame\n\n%s", usage);
7319                 i = 2;
7320                 if (argc == 4) {
7321                         string_ncopy(opt_ref, argv[i], strlen(argv[i]));
7322                         i++;
7323                 }
7325                 string_ncopy(opt_file, argv[i], strlen(argv[i]));
7326                 return REQ_VIEW_BLAME;
7328         } else if (!strcmp(subcommand, "show")) {
7329                 request = REQ_VIEW_DIFF;
7331         } else {
7332                 subcommand = NULL;
7333         }
7335         if (subcommand) {
7336                 custom_argv[1] = subcommand;
7337                 j = 2;
7338         }
7340         for (i = 1 + !!subcommand; i < argc; i++) {
7341                 const char *opt = argv[i];
7343                 if (seen_dashdash || !strcmp(opt, "--")) {
7344                         seen_dashdash = TRUE;
7346                 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
7347                         printf("tig version %s\n", TIG_VERSION);
7348                         quit(0);
7350                 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
7351                         printf("%s\n", usage);
7352                         quit(0);
7353                 }
7355                 custom_argv[j++] = opt;
7356                 if (j >= ARRAY_SIZE(custom_argv))
7357                         die("command too long");
7358         }
7360         if (!prepare_update(VIEW(request), custom_argv, NULL, FORMAT_NONE))                                                                        
7361                 die("Failed to format arguments"); 
7363         return request;
7366 int
7367 main(int argc, const char *argv[])
7369         enum request request = parse_options(argc, argv);
7370         struct view *view;
7371         size_t i;
7373         signal(SIGINT, quit);
7374         signal(SIGPIPE, SIG_IGN);
7376         if (setlocale(LC_ALL, "")) {
7377                 char *codeset = nl_langinfo(CODESET);
7379                 string_ncopy(opt_codeset, codeset, strlen(codeset));
7380         }
7382         if (load_repo_info() == ERR)
7383                 die("Failed to load repo info.");
7385         if (load_options() == ERR)
7386                 die("Failed to load user config.");
7388         if (load_git_config() == ERR)
7389                 die("Failed to load repo config.");
7391         /* Require a git repository unless when running in pager mode. */
7392         if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
7393                 die("Not a git repository");
7395         if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
7396                 opt_utf8 = FALSE;
7398         if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
7399                 opt_iconv = iconv_open(opt_codeset, opt_encoding);
7400                 if (opt_iconv == ICONV_NONE)
7401                         die("Failed to initialize character set conversion");
7402         }
7404         if (load_refs() == ERR)
7405                 die("Failed to load refs.");
7407         foreach_view (view, i)
7408                 argv_from_env(view->ops->argv, view->cmd_env);
7410         init_display();
7412         if (request != REQ_NONE)
7413                 open_view(NULL, request, OPEN_PREPARED);
7414         request = request == REQ_NONE ? REQ_VIEW_MAIN : REQ_NONE;
7416         while (view_driver(display[current_view], request)) {
7417                 int key = get_input(0);
7419                 view = display[current_view];
7420                 request = get_keybinding(view->keymap, key);
7422                 /* Some low-level request handling. This keeps access to
7423                  * status_win restricted. */
7424                 switch (request) {
7425                 case REQ_PROMPT:
7426                 {
7427                         char *cmd = read_prompt(":");
7429                         if (cmd && isdigit(*cmd)) {
7430                                 int lineno = view->lineno + 1;
7432                                 if (parse_int(&lineno, cmd, 1, view->lines + 1) == OK) {
7433                                         select_view_line(view, lineno - 1);
7434                                         report("");
7435                                 } else {
7436                                         report("Unable to parse '%s' as a line number", cmd);
7437                                 }
7439                         } else if (cmd) {
7440                                 struct view *next = VIEW(REQ_VIEW_PAGER);
7441                                 const char *argv[SIZEOF_ARG] = { "git" };
7442                                 int argc = 1;
7444                                 /* When running random commands, initially show the
7445                                  * command in the title. However, it maybe later be
7446                                  * overwritten if a commit line is selected. */
7447                                 string_ncopy(next->ref, cmd, strlen(cmd));
7449                                 if (!argv_from_string(argv, &argc, cmd)) {
7450                                         report("Too many arguments");
7451                                 } else if (!prepare_update(next, argv, NULL, FORMAT_DASH)) {
7452                                         report("Failed to format command");
7453                                 } else {
7454                                         open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
7455                                 }
7456                         }
7458                         request = REQ_NONE;
7459                         break;
7460                 }
7461                 case REQ_SEARCH:
7462                 case REQ_SEARCH_BACK:
7463                 {
7464                         const char *prompt = request == REQ_SEARCH ? "/" : "?";
7465                         char *search = read_prompt(prompt);
7467                         if (search)
7468                                 string_ncopy(opt_search, search, strlen(search));
7469                         else if (*opt_search)
7470                                 request = request == REQ_SEARCH ?
7471                                         REQ_FIND_NEXT :
7472                                         REQ_FIND_PREV;
7473                         else
7474                                 request = REQ_NONE;
7475                         break;
7476                 }
7477                 default:
7478                         break;
7479                 }
7480         }
7482         quit(0);
7484         return 0;