Code

Make height of split view configurable
[tig.git] / tig.c
1 /* Copyright (c) 2006-2009 Jonas Fonseca <fonseca@diku.dk>
2  *
3  * This program is free software; you can redistribute it and/or
4  * modify it under the terms of the GNU General Public License as
5  * published by the Free Software Foundation; either version 2 of
6  * the License, or (at your option) any later version.
7  *
8  * This program is distributed in the hope that it will be useful,
9  * but WITHOUT ANY WARRANTY; without even the implied warranty of
10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11  * GNU General Public License for more details.
12  */
14 #ifdef HAVE_CONFIG_H
15 #include "config.h"
16 #endif
18 #ifndef TIG_VERSION
19 #define TIG_VERSION "unknown-version"
20 #endif
22 #ifndef DEBUG
23 #define NDEBUG
24 #endif
26 #include <assert.h>
27 #include <errno.h>
28 #include <ctype.h>
29 #include <signal.h>
30 #include <stdarg.h>
31 #include <stdio.h>
32 #include <stdlib.h>
33 #include <string.h>
34 #include <sys/types.h>
35 #include <sys/wait.h>
36 #include <sys/stat.h>
37 #include <sys/select.h>
38 #include <unistd.h>
39 #include <time.h>
40 #include <fcntl.h>
42 #include <regex.h>
44 #include <locale.h>
45 #include <langinfo.h>
46 #include <iconv.h>
48 /* ncurses(3): Must be defined to have extended wide-character functions. */
49 #define _XOPEN_SOURCE_EXTENDED
51 #ifdef HAVE_NCURSESW_NCURSES_H
52 #include <ncursesw/ncurses.h>
53 #else
54 #ifdef HAVE_NCURSES_NCURSES_H
55 #include <ncurses/ncurses.h>
56 #else
57 #include <ncurses.h>
58 #endif
59 #endif
61 #if __GNUC__ >= 3
62 #define __NORETURN __attribute__((__noreturn__))
63 #else
64 #define __NORETURN
65 #endif
67 static void __NORETURN die(const char *err, ...);
68 static void warn(const char *msg, ...);
69 static void report(const char *msg, ...);
70 static void set_nonblocking_input(bool loading);
71 static size_t utf8_length(const char **string, size_t col, int *width, size_t max_width, int *trimmed, bool reserve);
73 #define ABS(x)          ((x) >= 0  ? (x) : -(x))
74 #define MIN(x, y)       ((x) < (y) ? (x) :  (y))
75 #define MAX(x, y)       ((x) > (y) ? (x) :  (y))
77 #define ARRAY_SIZE(x)   (sizeof(x) / sizeof(x[0]))
78 #define STRING_SIZE(x)  (sizeof(x) - 1)
80 #define SIZEOF_STR      1024    /* Default string size. */
81 #define SIZEOF_REF      256     /* Size of symbolic or SHA1 ID. */
82 #define SIZEOF_REV      41      /* Holds a SHA-1 and an ending NUL. */
83 #define SIZEOF_ARG      32      /* Default argument array size. */
85 /* Revision graph */
87 #define REVGRAPH_INIT   'I'
88 #define REVGRAPH_MERGE  'M'
89 #define REVGRAPH_BRANCH '+'
90 #define REVGRAPH_COMMIT '*'
91 #define REVGRAPH_BOUND  '^'
93 #define SIZEOF_REVGRAPH 19      /* Size of revision ancestry graphics. */
95 /* This color name can be used to refer to the default term colors. */
96 #define COLOR_DEFAULT   (-1)
98 #define ICONV_NONE      ((iconv_t) -1)
99 #ifndef ICONV_CONST
100 #define ICONV_CONST     /* nothing */
101 #endif
103 /* The format and size of the date column in the main view. */
104 #define DATE_FORMAT     "%Y-%m-%d %H:%M"
105 #define DATE_COLS       STRING_SIZE("2006-04-29 14:21 ")
107 #define AUTHOR_COLS     20
108 #define ID_COLS         8
110 /* The default interval between line numbers. */
111 #define NUMBER_INTERVAL 5
113 #define TAB_SIZE        8
115 #define SCALE_SPLIT_VIEW (2.0 / 3.0)
116 #define MIN_VIEW_HEIGHT 4
118 #define NULL_ID         "0000000000000000000000000000000000000000"
120 #define S_ISGITLINK(mode) (((mode) & S_IFMT) == 0160000)
122 #ifndef GIT_CONFIG
123 #define GIT_CONFIG "config"
124 #endif
126 /* Some ASCII-shorthands fitted into the ncurses namespace. */
127 #define KEY_TAB         '\t'
128 #define KEY_RETURN      '\r'
129 #define KEY_ESC         27
132 struct ref {
133         char id[SIZEOF_REV];    /* Commit SHA1 ID */
134         unsigned int head:1;    /* Is it the current HEAD? */
135         unsigned int tag:1;     /* Is it a tag? */
136         unsigned int ltag:1;    /* If so, is the tag local? */
137         unsigned int remote:1;  /* Is it a remote ref? */
138         unsigned int tracked:1; /* Is it the remote for the current HEAD? */
139         char name[1];           /* Ref name; tag or head names are shortened. */
140 };
142 struct ref_list {
143         char id[SIZEOF_REV];    /* Commit SHA1 ID */
144         size_t size;            /* Number of refs. */
145         struct ref **refs;      /* References for this ID. */
146 };
148 static struct ref_list *get_ref_list(const char *id);
149 static void foreach_ref(bool (*visitor)(void *data, struct ref *ref), void *data);
150 static int load_refs(void);
152 enum format_flags {
153         FORMAT_ALL,             /* Perform replacement in all arguments. */
154         FORMAT_DASH,            /* Perform replacement up until "--". */
155         FORMAT_NONE             /* No replacement should be performed. */
156 };
158 static bool format_argv(const char *dst[], const char *src[], enum format_flags flags);
160 enum input_status {
161         INPUT_OK,
162         INPUT_SKIP,
163         INPUT_STOP,
164         INPUT_CANCEL
165 };
167 typedef enum input_status (*input_handler)(void *data, char *buf, int c);
169 static char *prompt_input(const char *prompt, input_handler handler, void *data);
170 static bool prompt_yesno(const char *prompt);
172 struct menu_item {
173         int hotkey;
174         const char *text;
175         void *data;
176 };
178 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected);
180 /*
181  * Allocation helpers ... Entering macro hell to never be seen again.
182  */
184 #define DEFINE_ALLOCATOR(name, type, chunk_size)                                \
185 static type *                                                                   \
186 name(type **mem, size_t size, size_t increase)                                  \
187 {                                                                               \
188         size_t num_chunks = (size + chunk_size - 1) / chunk_size;               \
189         size_t num_chunks_new = (size + increase + chunk_size - 1) / chunk_size;\
190         type *tmp = *mem;                                                       \
191                                                                                 \
192         if (mem == NULL || num_chunks != num_chunks_new) {                      \
193                 tmp = realloc(tmp, num_chunks_new * chunk_size * sizeof(type)); \
194                 if (tmp)                                                        \
195                         *mem = tmp;                                             \
196         }                                                                       \
197                                                                                 \
198         return tmp;                                                             \
201 /*
202  * String helpers
203  */
205 static inline void
206 string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t srclen)
208         if (srclen > dstlen - 1)
209                 srclen = dstlen - 1;
211         strncpy(dst, src, srclen);
212         dst[srclen] = 0;
215 /* Shorthands for safely copying into a fixed buffer. */
217 #define string_copy(dst, src) \
218         string_ncopy_do(dst, sizeof(dst), src, sizeof(src))
220 #define string_ncopy(dst, src, srclen) \
221         string_ncopy_do(dst, sizeof(dst), src, srclen)
223 #define string_copy_rev(dst, src) \
224         string_ncopy_do(dst, SIZEOF_REV, src, SIZEOF_REV - 1)
226 #define string_add(dst, from, src) \
227         string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src))
229 static void
230 string_expand(char *dst, size_t dstlen, const char *src, int tabsize)
232         size_t size, pos;
234         for (size = pos = 0; size < dstlen - 1 && src[pos]; pos++) {
235                 if (src[pos] == '\t') {
236                         size_t expanded = tabsize - (size % tabsize);
238                         if (expanded + size >= dstlen - 1)
239                                 expanded = dstlen - size - 1;
240                         memcpy(dst + size, "        ", expanded);
241                         size += expanded;
242                 } else {
243                         dst[size++] = src[pos];
244                 }
245         }
247         dst[size] = 0;
250 static char *
251 chomp_string(char *name)
253         int namelen;
255         while (isspace(*name))
256                 name++;
258         namelen = strlen(name) - 1;
259         while (namelen > 0 && isspace(name[namelen]))
260                 name[namelen--] = 0;
262         return name;
265 static bool
266 string_nformat(char *buf, size_t bufsize, size_t *bufpos, const char *fmt, ...)
268         va_list args;
269         size_t pos = bufpos ? *bufpos : 0;
271         va_start(args, fmt);
272         pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
273         va_end(args);
275         if (bufpos)
276                 *bufpos = pos;
278         return pos >= bufsize ? FALSE : TRUE;
281 #define string_format(buf, fmt, args...) \
282         string_nformat(buf, sizeof(buf), NULL, fmt, args)
284 #define string_format_from(buf, from, fmt, args...) \
285         string_nformat(buf, sizeof(buf), from, fmt, args)
287 static int
288 string_enum_compare(const char *str1, const char *str2, int len)
290         size_t i;
292 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
294         /* Diff-Header == DIFF_HEADER */
295         for (i = 0; i < len; i++) {
296                 if (toupper(str1[i]) == toupper(str2[i]))
297                         continue;
299                 if (string_enum_sep(str1[i]) &&
300                     string_enum_sep(str2[i]))
301                         continue;
303                 return str1[i] - str2[i];
304         }
306         return 0;
309 struct enum_map {
310         const char *name;
311         int namelen;
312         int value;
313 };
315 #define ENUM_MAP(name, value) { name, STRING_SIZE(name), value }
317 static bool
318 map_enum_do(const struct enum_map *map, size_t map_size, int *value, const char *name)
320         size_t namelen = strlen(name);
321         int i;
323         for (i = 0; i < map_size; i++)
324                 if (namelen == map[i].namelen &&
325                     !string_enum_compare(name, map[i].name, namelen)) {
326                         *value = map[i].value;
327                         return TRUE;
328                 }
330         return FALSE;
333 #define map_enum(attr, map, name) \
334         map_enum_do(map, ARRAY_SIZE(map), attr, name)
336 #define prefixcmp(str1, str2) \
337         strncmp(str1, str2, STRING_SIZE(str2))
339 static inline int
340 suffixcmp(const char *str, int slen, const char *suffix)
342         size_t len = slen >= 0 ? slen : strlen(str);
343         size_t suffixlen = strlen(suffix);
345         return suffixlen < len ? strcmp(str + len - suffixlen, suffix) : -1;
349 static const char *
350 mkdate(const time_t *time)
352         static char buf[DATE_COLS + 1];
353         struct tm tm;
355         gmtime_r(time, &tm);
356         return strftime(buf, sizeof(buf), DATE_FORMAT, &tm) ? buf : NULL;
360 static bool
361 argv_from_string(const char *argv[SIZEOF_ARG], int *argc, char *cmd)
363         int valuelen;
365         while (*cmd && *argc < SIZEOF_ARG && (valuelen = strcspn(cmd, " \t"))) {
366                 bool advance = cmd[valuelen] != 0;
368                 cmd[valuelen] = 0;
369                 argv[(*argc)++] = chomp_string(cmd);
370                 cmd = chomp_string(cmd + valuelen + advance);
371         }
373         if (*argc < SIZEOF_ARG)
374                 argv[*argc] = NULL;
375         return *argc < SIZEOF_ARG;
378 static void
379 argv_from_env(const char **argv, const char *name)
381         char *env = argv ? getenv(name) : NULL;
382         int argc = 0;
384         if (env && *env)
385                 env = strdup(env);
386         if (env && !argv_from_string(argv, &argc, env))
387                 die("Too many arguments in the `%s` environment variable", name);
391 /*
392  * Executing external commands.
393  */
395 enum io_type {
396         IO_FD,                  /* File descriptor based IO. */
397         IO_BG,                  /* Execute command in the background. */
398         IO_FG,                  /* Execute command with same std{in,out,err}. */
399         IO_RD,                  /* Read only fork+exec IO. */
400         IO_WR,                  /* Write only fork+exec IO. */
401         IO_AP,                  /* Append fork+exec output to file. */
402 };
404 struct io {
405         enum io_type type;      /* The requested type of pipe. */
406         const char *dir;        /* Directory from which to execute. */
407         pid_t pid;              /* Pipe for reading or writing. */
408         int pipe;               /* Pipe end for reading or writing. */
409         int error;              /* Error status. */
410         const char *argv[SIZEOF_ARG];   /* Shell command arguments. */
411         char *buf;              /* Read buffer. */
412         size_t bufalloc;        /* Allocated buffer size. */
413         size_t bufsize;         /* Buffer content size. */
414         char *bufpos;           /* Current buffer position. */
415         unsigned int eof:1;     /* Has end of file been reached. */
416 };
418 static void
419 reset_io(struct io *io)
421         io->pipe = -1;
422         io->pid = 0;
423         io->buf = io->bufpos = NULL;
424         io->bufalloc = io->bufsize = 0;
425         io->error = 0;
426         io->eof = 0;
429 static void
430 init_io(struct io *io, const char *dir, enum io_type type)
432         reset_io(io);
433         io->type = type;
434         io->dir = dir;
437 static bool
438 init_io_rd(struct io *io, const char *argv[], const char *dir,
439                 enum format_flags flags)
441         init_io(io, dir, IO_RD);
442         return format_argv(io->argv, argv, flags);
445 static bool
446 io_open(struct io *io, const char *name)
448         init_io(io, NULL, IO_FD);
449         io->pipe = *name ? open(name, O_RDONLY) : STDIN_FILENO;
450         if (io->pipe == -1)
451                 io->error = errno;
452         return io->pipe != -1;
455 static bool
456 kill_io(struct io *io)
458         return io->pid == 0 || kill(io->pid, SIGKILL) != -1;
461 static bool
462 done_io(struct io *io)
464         pid_t pid = io->pid;
466         if (io->pipe != -1)
467                 close(io->pipe);
468         free(io->buf);
469         reset_io(io);
471         while (pid > 0) {
472                 int status;
473                 pid_t waiting = waitpid(pid, &status, 0);
475                 if (waiting < 0) {
476                         if (errno == EINTR)
477                                 continue;
478                         report("waitpid failed (%s)", strerror(errno));
479                         return FALSE;
480                 }
482                 return waiting == pid &&
483                        !WIFSIGNALED(status) &&
484                        WIFEXITED(status) &&
485                        !WEXITSTATUS(status);
486         }
488         return TRUE;
491 static bool
492 start_io(struct io *io)
494         int pipefds[2] = { -1, -1 };
496         if (io->type == IO_FD)
497                 return TRUE;
499         if ((io->type == IO_RD || io->type == IO_WR) &&
500             pipe(pipefds) < 0)
501                 return FALSE;
502         else if (io->type == IO_AP)
503                 pipefds[1] = io->pipe;
505         if ((io->pid = fork())) {
506                 if (pipefds[!(io->type == IO_WR)] != -1)
507                         close(pipefds[!(io->type == IO_WR)]);
508                 if (io->pid != -1) {
509                         io->pipe = pipefds[!!(io->type == IO_WR)];
510                         return TRUE;
511                 }
513         } else {
514                 if (io->type != IO_FG) {
515                         int devnull = open("/dev/null", O_RDWR);
516                         int readfd  = io->type == IO_WR ? pipefds[0] : devnull;
517                         int writefd = (io->type == IO_RD || io->type == IO_AP)
518                                                         ? pipefds[1] : devnull;
520                         dup2(readfd,  STDIN_FILENO);
521                         dup2(writefd, STDOUT_FILENO);
522                         dup2(devnull, STDERR_FILENO);
524                         close(devnull);
525                         if (pipefds[0] != -1)
526                                 close(pipefds[0]);
527                         if (pipefds[1] != -1)
528                                 close(pipefds[1]);
529                 }
531                 if (io->dir && *io->dir && chdir(io->dir) == -1)
532                         die("Failed to change directory: %s", strerror(errno));
534                 execvp(io->argv[0], (char *const*) io->argv);
535                 die("Failed to execute program: %s", strerror(errno));
536         }
538         if (pipefds[!!(io->type == IO_WR)] != -1)
539                 close(pipefds[!!(io->type == IO_WR)]);
540         return FALSE;
543 static bool
544 run_io(struct io *io, const char **argv, const char *dir, enum io_type type)
546         init_io(io, dir, type);
547         if (!format_argv(io->argv, argv, FORMAT_NONE))
548                 return FALSE;
549         return start_io(io);
552 static int
553 run_io_do(struct io *io)
555         return start_io(io) && done_io(io);
558 static int
559 run_io_bg(const char **argv)
561         struct io io = {};
563         init_io(&io, NULL, IO_BG);
564         if (!format_argv(io.argv, argv, FORMAT_NONE))
565                 return FALSE;
566         return run_io_do(&io);
569 static bool
570 run_io_fg(const char **argv, const char *dir)
572         struct io io = {};
574         init_io(&io, dir, IO_FG);
575         if (!format_argv(io.argv, argv, FORMAT_NONE))
576                 return FALSE;
577         return run_io_do(&io);
580 static bool
581 run_io_append(const char **argv, enum format_flags flags, int fd)
583         struct io io = {};
585         init_io(&io, NULL, IO_AP);
586         io.pipe = fd;
587         if (format_argv(io.argv, argv, flags))
588                 return run_io_do(&io);
589         close(fd);
590         return FALSE;
593 static bool
594 run_io_rd(struct io *io, const char **argv, enum format_flags flags)
596         return init_io_rd(io, argv, NULL, flags) && start_io(io);
599 static bool
600 io_eof(struct io *io)
602         return io->eof;
605 static int
606 io_error(struct io *io)
608         return io->error;
611 static char *
612 io_strerror(struct io *io)
614         return strerror(io->error);
617 static bool
618 io_can_read(struct io *io)
620         struct timeval tv = { 0, 500 };
621         fd_set fds;
623         FD_ZERO(&fds);
624         FD_SET(io->pipe, &fds);
626         return select(io->pipe + 1, &fds, NULL, NULL, &tv) > 0;
629 static ssize_t
630 io_read(struct io *io, void *buf, size_t bufsize)
632         do {
633                 ssize_t readsize = read(io->pipe, buf, bufsize);
635                 if (readsize < 0 && (errno == EAGAIN || errno == EINTR))
636                         continue;
637                 else if (readsize == -1)
638                         io->error = errno;
639                 else if (readsize == 0)
640                         io->eof = 1;
641                 return readsize;
642         } while (1);
645 DEFINE_ALLOCATOR(realloc_io_buf, char, BUFSIZ)
647 static char *
648 io_get(struct io *io, int c, bool can_read)
650         char *eol;
651         ssize_t readsize;
653         while (TRUE) {
654                 if (io->bufsize > 0) {
655                         eol = memchr(io->bufpos, c, io->bufsize);
656                         if (eol) {
657                                 char *line = io->bufpos;
659                                 *eol = 0;
660                                 io->bufpos = eol + 1;
661                                 io->bufsize -= io->bufpos - line;
662                                 return line;
663                         }
664                 }
666                 if (io_eof(io)) {
667                         if (io->bufsize) {
668                                 io->bufpos[io->bufsize] = 0;
669                                 io->bufsize = 0;
670                                 return io->bufpos;
671                         }
672                         return NULL;
673                 }
675                 if (!can_read)
676                         return NULL;
678                 if (io->bufsize > 0 && io->bufpos > io->buf)
679                         memmove(io->buf, io->bufpos, io->bufsize);
681                 if (io->bufalloc == io->bufsize) {
682                         if (!realloc_io_buf(&io->buf, io->bufalloc, BUFSIZ))
683                                 return NULL;
684                         io->bufalloc += BUFSIZ;
685                 }
687                 io->bufpos = io->buf;
688                 readsize = io_read(io, io->buf + io->bufsize, io->bufalloc - io->bufsize);
689                 if (io_error(io))
690                         return NULL;
691                 io->bufsize += readsize;
692         }
695 static bool
696 io_write(struct io *io, const void *buf, size_t bufsize)
698         size_t written = 0;
700         while (!io_error(io) && written < bufsize) {
701                 ssize_t size;
703                 size = write(io->pipe, buf + written, bufsize - written);
704                 if (size < 0 && (errno == EAGAIN || errno == EINTR))
705                         continue;
706                 else if (size == -1)
707                         io->error = errno;
708                 else
709                         written += size;
710         }
712         return written == bufsize;
715 static bool
716 io_read_buf(struct io *io, char buf[], size_t bufsize)
718         char *result = io_get(io, '\n', TRUE);
720         if (result) {
721                 result = chomp_string(result);
722                 string_ncopy_do(buf, bufsize, result, strlen(result));
723         }
725         return done_io(io) && result;
728 static bool
729 run_io_buf(const char **argv, char buf[], size_t bufsize)
731         struct io io = {};
733         return run_io_rd(&io, argv, FORMAT_NONE) && io_read_buf(&io, buf, bufsize);
736 static int
737 io_load(struct io *io, const char *separators,
738         int (*read_property)(char *, size_t, char *, size_t))
740         char *name;
741         int state = OK;
743         if (!start_io(io))
744                 return ERR;
746         while (state == OK && (name = io_get(io, '\n', TRUE))) {
747                 char *value;
748                 size_t namelen;
749                 size_t valuelen;
751                 name = chomp_string(name);
752                 namelen = strcspn(name, separators);
754                 if (name[namelen]) {
755                         name[namelen] = 0;
756                         value = chomp_string(name + namelen + 1);
757                         valuelen = strlen(value);
759                 } else {
760                         value = "";
761                         valuelen = 0;
762                 }
764                 state = read_property(name, namelen, value, valuelen);
765         }
767         if (state != ERR && io_error(io))
768                 state = ERR;
769         done_io(io);
771         return state;
774 static int
775 run_io_load(const char **argv, const char *separators,
776             int (*read_property)(char *, size_t, char *, size_t))
778         struct io io = {};
780         return init_io_rd(&io, argv, NULL, FORMAT_NONE)
781                 ? io_load(&io, separators, read_property) : ERR;
785 /*
786  * User requests
787  */
789 #define REQ_INFO \
790         /* XXX: Keep the view request first and in sync with views[]. */ \
791         REQ_GROUP("View switching") \
792         REQ_(VIEW_MAIN,         "Show main view"), \
793         REQ_(VIEW_DIFF,         "Show diff view"), \
794         REQ_(VIEW_LOG,          "Show log view"), \
795         REQ_(VIEW_TREE,         "Show tree view"), \
796         REQ_(VIEW_BLOB,         "Show blob view"), \
797         REQ_(VIEW_BLAME,        "Show blame view"), \
798         REQ_(VIEW_BRANCH,       "Show branch view"), \
799         REQ_(VIEW_HELP,         "Show help page"), \
800         REQ_(VIEW_PAGER,        "Show pager view"), \
801         REQ_(VIEW_STATUS,       "Show status view"), \
802         REQ_(VIEW_STAGE,        "Show stage view"), \
803         \
804         REQ_GROUP("View manipulation") \
805         REQ_(ENTER,             "Enter current line and scroll"), \
806         REQ_(NEXT,              "Move to next"), \
807         REQ_(PREVIOUS,          "Move to previous"), \
808         REQ_(PARENT,            "Move to parent"), \
809         REQ_(VIEW_NEXT,         "Move focus to next view"), \
810         REQ_(REFRESH,           "Reload and refresh"), \
811         REQ_(MAXIMIZE,          "Maximize the current view"), \
812         REQ_(VIEW_CLOSE,        "Close the current view"), \
813         REQ_(QUIT,              "Close all views and quit"), \
814         \
815         REQ_GROUP("View specific requests") \
816         REQ_(STATUS_UPDATE,     "Update file status"), \
817         REQ_(STATUS_REVERT,     "Revert file changes"), \
818         REQ_(STATUS_MERGE,      "Merge file using external tool"), \
819         REQ_(STAGE_NEXT,        "Find next chunk to stage"), \
820         \
821         REQ_GROUP("Cursor navigation") \
822         REQ_(MOVE_UP,           "Move cursor one line up"), \
823         REQ_(MOVE_DOWN,         "Move cursor one line down"), \
824         REQ_(MOVE_PAGE_DOWN,    "Move cursor one page down"), \
825         REQ_(MOVE_PAGE_UP,      "Move cursor one page up"), \
826         REQ_(MOVE_FIRST_LINE,   "Move cursor to first line"), \
827         REQ_(MOVE_LAST_LINE,    "Move cursor to last line"), \
828         \
829         REQ_GROUP("Scrolling") \
830         REQ_(SCROLL_LEFT,       "Scroll two columns left"), \
831         REQ_(SCROLL_RIGHT,      "Scroll two columns right"), \
832         REQ_(SCROLL_LINE_UP,    "Scroll one line up"), \
833         REQ_(SCROLL_LINE_DOWN,  "Scroll one line down"), \
834         REQ_(SCROLL_PAGE_UP,    "Scroll one page up"), \
835         REQ_(SCROLL_PAGE_DOWN,  "Scroll one page down"), \
836         \
837         REQ_GROUP("Searching") \
838         REQ_(SEARCH,            "Search the view"), \
839         REQ_(SEARCH_BACK,       "Search backwards in the view"), \
840         REQ_(FIND_NEXT,         "Find next search match"), \
841         REQ_(FIND_PREV,         "Find previous search match"), \
842         \
843         REQ_GROUP("Option manipulation") \
844         REQ_(OPTIONS,           "Open option menu"), \
845         REQ_(TOGGLE_LINENO,     "Toggle line numbers"), \
846         REQ_(TOGGLE_DATE,       "Toggle date display"), \
847         REQ_(TOGGLE_AUTHOR,     "Toggle author display"), \
848         REQ_(TOGGLE_REV_GRAPH,  "Toggle revision graph visualization"), \
849         REQ_(TOGGLE_REFS,       "Toggle reference display (tags/branches)"), \
850         REQ_(TOGGLE_SORT_ORDER, "Toggle ascending/descending sort order"), \
851         REQ_(TOGGLE_SORT_FIELD, "Toggle field to sort by"), \
852         \
853         REQ_GROUP("Misc") \
854         REQ_(PROMPT,            "Bring up the prompt"), \
855         REQ_(SCREEN_REDRAW,     "Redraw the screen"), \
856         REQ_(SHOW_VERSION,      "Show version information"), \
857         REQ_(STOP_LOADING,      "Stop all loading views"), \
858         REQ_(EDIT,              "Open in editor"), \
859         REQ_(NONE,              "Do nothing")
862 /* User action requests. */
863 enum request {
864 #define REQ_GROUP(help)
865 #define REQ_(req, help) REQ_##req
867         /* Offset all requests to avoid conflicts with ncurses getch values. */
868         REQ_OFFSET = KEY_MAX + 1,
869         REQ_INFO
871 #undef  REQ_GROUP
872 #undef  REQ_
873 };
875 struct request_info {
876         enum request request;
877         const char *name;
878         int namelen;
879         const char *help;
880 };
882 static const struct request_info req_info[] = {
883 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
884 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
885         REQ_INFO
886 #undef  REQ_GROUP
887 #undef  REQ_
888 };
890 static enum request
891 get_request(const char *name)
893         int namelen = strlen(name);
894         int i;
896         for (i = 0; i < ARRAY_SIZE(req_info); i++)
897                 if (req_info[i].namelen == namelen &&
898                     !string_enum_compare(req_info[i].name, name, namelen))
899                         return req_info[i].request;
901         return REQ_NONE;
905 /*
906  * Options
907  */
909 /* Option and state variables. */
910 static bool opt_date                    = TRUE;
911 static bool opt_author                  = TRUE;
912 static bool opt_line_number             = FALSE;
913 static bool opt_line_graphics           = TRUE;
914 static bool opt_rev_graph               = FALSE;
915 static bool opt_show_refs               = TRUE;
916 static int opt_num_interval             = NUMBER_INTERVAL;
917 static double opt_hscroll               = 0.50;
918 static double opt_scale_split_view      = SCALE_SPLIT_VIEW;
919 static int opt_tab_size                 = TAB_SIZE;
920 static int opt_author_cols              = AUTHOR_COLS-1;
921 static char opt_path[SIZEOF_STR]        = "";
922 static char opt_file[SIZEOF_STR]        = "";
923 static char opt_ref[SIZEOF_REF]         = "";
924 static char opt_head[SIZEOF_REF]        = "";
925 static char opt_head_rev[SIZEOF_REV]    = "";
926 static char opt_remote[SIZEOF_REF]      = "";
927 static char opt_encoding[20]            = "UTF-8";
928 static bool opt_utf8                    = TRUE;
929 static char opt_codeset[20]             = "UTF-8";
930 static iconv_t opt_iconv                = ICONV_NONE;
931 static char opt_search[SIZEOF_STR]      = "";
932 static char opt_cdup[SIZEOF_STR]        = "";
933 static char opt_prefix[SIZEOF_STR]      = "";
934 static char opt_git_dir[SIZEOF_STR]     = "";
935 static signed char opt_is_inside_work_tree      = -1; /* set to TRUE or FALSE */
936 static char opt_editor[SIZEOF_STR]      = "";
937 static FILE *opt_tty                    = NULL;
939 #define is_initial_commit()     (!*opt_head_rev)
940 #define is_head_commit(rev)     (!strcmp((rev), "HEAD") || !strcmp(opt_head_rev, (rev)))
943 /*
944  * Line-oriented content detection.
945  */
947 #define LINE_INFO \
948 LINE(DIFF_HEADER,  "diff --git ",       COLOR_YELLOW,   COLOR_DEFAULT,  0), \
949 LINE(DIFF_CHUNK,   "@@",                COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
950 LINE(DIFF_ADD,     "+",                 COLOR_GREEN,    COLOR_DEFAULT,  0), \
951 LINE(DIFF_DEL,     "-",                 COLOR_RED,      COLOR_DEFAULT,  0), \
952 LINE(DIFF_INDEX,        "index ",         COLOR_BLUE,   COLOR_DEFAULT,  0), \
953 LINE(DIFF_OLDMODE,      "old file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
954 LINE(DIFF_NEWMODE,      "new file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
955 LINE(DIFF_COPY_FROM,    "copy from",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
956 LINE(DIFF_COPY_TO,      "copy to",        COLOR_YELLOW, COLOR_DEFAULT,  0), \
957 LINE(DIFF_RENAME_FROM,  "rename from",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
958 LINE(DIFF_RENAME_TO,    "rename to",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
959 LINE(DIFF_SIMILARITY,   "similarity ",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
960 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
961 LINE(DIFF_TREE,         "diff-tree ",     COLOR_BLUE,   COLOR_DEFAULT,  0), \
962 LINE(PP_AUTHOR,    "Author: ",          COLOR_CYAN,     COLOR_DEFAULT,  0), \
963 LINE(PP_COMMIT,    "Commit: ",          COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
964 LINE(PP_MERGE,     "Merge: ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
965 LINE(PP_DATE,      "Date:   ",          COLOR_YELLOW,   COLOR_DEFAULT,  0), \
966 LINE(PP_ADATE,     "AuthorDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
967 LINE(PP_CDATE,     "CommitDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
968 LINE(PP_REFS,      "Refs: ",            COLOR_RED,      COLOR_DEFAULT,  0), \
969 LINE(COMMIT,       "commit ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
970 LINE(PARENT,       "parent ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
971 LINE(TREE,         "tree ",             COLOR_BLUE,     COLOR_DEFAULT,  0), \
972 LINE(AUTHOR,       "author ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
973 LINE(COMMITTER,    "committer ",        COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
974 LINE(SIGNOFF,      "    Signed-off-by", COLOR_YELLOW,   COLOR_DEFAULT,  0), \
975 LINE(ACKED,        "    Acked-by",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
976 LINE(DEFAULT,      "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
977 LINE(CURSOR,       "",                  COLOR_WHITE,    COLOR_GREEN,    A_BOLD), \
978 LINE(STATUS,       "",                  COLOR_GREEN,    COLOR_DEFAULT,  0), \
979 LINE(DELIMITER,    "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
980 LINE(DATE,         "",                  COLOR_BLUE,     COLOR_DEFAULT,  0), \
981 LINE(MODE,         "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
982 LINE(LINE_NUMBER,  "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
983 LINE(TITLE_BLUR,   "",                  COLOR_WHITE,    COLOR_BLUE,     0), \
984 LINE(TITLE_FOCUS,  "",                  COLOR_WHITE,    COLOR_BLUE,     A_BOLD), \
985 LINE(MAIN_COMMIT,  "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
986 LINE(MAIN_TAG,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  A_BOLD), \
987 LINE(MAIN_LOCAL_TAG,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
988 LINE(MAIN_REMOTE,  "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
989 LINE(MAIN_TRACKED, "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_BOLD), \
990 LINE(MAIN_REF,     "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
991 LINE(MAIN_HEAD,    "",                  COLOR_CYAN,     COLOR_DEFAULT,  A_BOLD), \
992 LINE(MAIN_REVGRAPH,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
993 LINE(TREE_HEAD,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_BOLD), \
994 LINE(TREE_DIR,     "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_NORMAL), \
995 LINE(TREE_FILE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
996 LINE(STAT_HEAD,    "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
997 LINE(STAT_SECTION, "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
998 LINE(STAT_NONE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
999 LINE(STAT_STAGED,  "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1000 LINE(STAT_UNSTAGED,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1001 LINE(STAT_UNTRACKED,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1002 LINE(BLAME_ID,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0)
1004 enum line_type {
1005 #define LINE(type, line, fg, bg, attr) \
1006         LINE_##type
1007         LINE_INFO,
1008         LINE_NONE
1009 #undef  LINE
1010 };
1012 struct line_info {
1013         const char *name;       /* Option name. */
1014         int namelen;            /* Size of option name. */
1015         const char *line;       /* The start of line to match. */
1016         int linelen;            /* Size of string to match. */
1017         int fg, bg, attr;       /* Color and text attributes for the lines. */
1018 };
1020 static struct line_info line_info[] = {
1021 #define LINE(type, line, fg, bg, attr) \
1022         { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
1023         LINE_INFO
1024 #undef  LINE
1025 };
1027 static enum line_type
1028 get_line_type(const char *line)
1030         int linelen = strlen(line);
1031         enum line_type type;
1033         for (type = 0; type < ARRAY_SIZE(line_info); type++)
1034                 /* Case insensitive search matches Signed-off-by lines better. */
1035                 if (linelen >= line_info[type].linelen &&
1036                     !strncasecmp(line_info[type].line, line, line_info[type].linelen))
1037                         return type;
1039         return LINE_DEFAULT;
1042 static inline int
1043 get_line_attr(enum line_type type)
1045         assert(type < ARRAY_SIZE(line_info));
1046         return COLOR_PAIR(type) | line_info[type].attr;
1049 static struct line_info *
1050 get_line_info(const char *name)
1052         size_t namelen = strlen(name);
1053         enum line_type type;
1055         for (type = 0; type < ARRAY_SIZE(line_info); type++)
1056                 if (namelen == line_info[type].namelen &&
1057                     !string_enum_compare(line_info[type].name, name, namelen))
1058                         return &line_info[type];
1060         return NULL;
1063 static void
1064 init_colors(void)
1066         int default_bg = line_info[LINE_DEFAULT].bg;
1067         int default_fg = line_info[LINE_DEFAULT].fg;
1068         enum line_type type;
1070         start_color();
1072         if (assume_default_colors(default_fg, default_bg) == ERR) {
1073                 default_bg = COLOR_BLACK;
1074                 default_fg = COLOR_WHITE;
1075         }
1077         for (type = 0; type < ARRAY_SIZE(line_info); type++) {
1078                 struct line_info *info = &line_info[type];
1079                 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
1080                 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
1082                 init_pair(type, fg, bg);
1083         }
1086 struct line {
1087         enum line_type type;
1089         /* State flags */
1090         unsigned int selected:1;
1091         unsigned int dirty:1;
1092         unsigned int cleareol:1;
1094         void *data;             /* User data */
1095 };
1098 /*
1099  * Keys
1100  */
1102 struct keybinding {
1103         int alias;
1104         enum request request;
1105 };
1107 static const struct keybinding default_keybindings[] = {
1108         /* View switching */
1109         { 'm',          REQ_VIEW_MAIN },
1110         { 'd',          REQ_VIEW_DIFF },
1111         { 'l',          REQ_VIEW_LOG },
1112         { 't',          REQ_VIEW_TREE },
1113         { 'f',          REQ_VIEW_BLOB },
1114         { 'B',          REQ_VIEW_BLAME },
1115         { 'H',          REQ_VIEW_BRANCH },
1116         { 'p',          REQ_VIEW_PAGER },
1117         { 'h',          REQ_VIEW_HELP },
1118         { 'S',          REQ_VIEW_STATUS },
1119         { 'c',          REQ_VIEW_STAGE },
1121         /* View manipulation */
1122         { 'q',          REQ_VIEW_CLOSE },
1123         { KEY_TAB,      REQ_VIEW_NEXT },
1124         { KEY_RETURN,   REQ_ENTER },
1125         { KEY_UP,       REQ_PREVIOUS },
1126         { KEY_DOWN,     REQ_NEXT },
1127         { 'R',          REQ_REFRESH },
1128         { KEY_F(5),     REQ_REFRESH },
1129         { 'O',          REQ_MAXIMIZE },
1131         /* Cursor navigation */
1132         { 'k',          REQ_MOVE_UP },
1133         { 'j',          REQ_MOVE_DOWN },
1134         { KEY_HOME,     REQ_MOVE_FIRST_LINE },
1135         { KEY_END,      REQ_MOVE_LAST_LINE },
1136         { KEY_NPAGE,    REQ_MOVE_PAGE_DOWN },
1137         { ' ',          REQ_MOVE_PAGE_DOWN },
1138         { KEY_PPAGE,    REQ_MOVE_PAGE_UP },
1139         { 'b',          REQ_MOVE_PAGE_UP },
1140         { '-',          REQ_MOVE_PAGE_UP },
1142         /* Scrolling */
1143         { KEY_LEFT,     REQ_SCROLL_LEFT },
1144         { KEY_RIGHT,    REQ_SCROLL_RIGHT },
1145         { KEY_IC,       REQ_SCROLL_LINE_UP },
1146         { KEY_DC,       REQ_SCROLL_LINE_DOWN },
1147         { 'w',          REQ_SCROLL_PAGE_UP },
1148         { 's',          REQ_SCROLL_PAGE_DOWN },
1150         /* Searching */
1151         { '/',          REQ_SEARCH },
1152         { '?',          REQ_SEARCH_BACK },
1153         { 'n',          REQ_FIND_NEXT },
1154         { 'N',          REQ_FIND_PREV },
1156         /* Misc */
1157         { 'Q',          REQ_QUIT },
1158         { 'z',          REQ_STOP_LOADING },
1159         { 'v',          REQ_SHOW_VERSION },
1160         { 'r',          REQ_SCREEN_REDRAW },
1161         { 'o',          REQ_OPTIONS },
1162         { '.',          REQ_TOGGLE_LINENO },
1163         { 'D',          REQ_TOGGLE_DATE },
1164         { 'A',          REQ_TOGGLE_AUTHOR },
1165         { 'g',          REQ_TOGGLE_REV_GRAPH },
1166         { 'F',          REQ_TOGGLE_REFS },
1167         { 'I',          REQ_TOGGLE_SORT_ORDER },
1168         { 'i',          REQ_TOGGLE_SORT_FIELD },
1169         { ':',          REQ_PROMPT },
1170         { 'u',          REQ_STATUS_UPDATE },
1171         { '!',          REQ_STATUS_REVERT },
1172         { 'M',          REQ_STATUS_MERGE },
1173         { '@',          REQ_STAGE_NEXT },
1174         { ',',          REQ_PARENT },
1175         { 'e',          REQ_EDIT },
1176 };
1178 #define KEYMAP_INFO \
1179         KEYMAP_(GENERIC), \
1180         KEYMAP_(MAIN), \
1181         KEYMAP_(DIFF), \
1182         KEYMAP_(LOG), \
1183         KEYMAP_(TREE), \
1184         KEYMAP_(BLOB), \
1185         KEYMAP_(BLAME), \
1186         KEYMAP_(BRANCH), \
1187         KEYMAP_(PAGER), \
1188         KEYMAP_(HELP), \
1189         KEYMAP_(STATUS), \
1190         KEYMAP_(STAGE)
1192 enum keymap {
1193 #define KEYMAP_(name) KEYMAP_##name
1194         KEYMAP_INFO
1195 #undef  KEYMAP_
1196 };
1198 static const struct enum_map keymap_table[] = {
1199 #define KEYMAP_(name) ENUM_MAP(#name, KEYMAP_##name)
1200         KEYMAP_INFO
1201 #undef  KEYMAP_
1202 };
1204 #define set_keymap(map, name) map_enum(map, keymap_table, name)
1206 struct keybinding_table {
1207         struct keybinding *data;
1208         size_t size;
1209 };
1211 static struct keybinding_table keybindings[ARRAY_SIZE(keymap_table)];
1213 static void
1214 add_keybinding(enum keymap keymap, enum request request, int key)
1216         struct keybinding_table *table = &keybindings[keymap];
1218         table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data));
1219         if (!table->data)
1220                 die("Failed to allocate keybinding");
1221         table->data[table->size].alias = key;
1222         table->data[table->size++].request = request;
1225 /* Looks for a key binding first in the given map, then in the generic map, and
1226  * lastly in the default keybindings. */
1227 static enum request
1228 get_keybinding(enum keymap keymap, int key)
1230         size_t i;
1232         for (i = 0; i < keybindings[keymap].size; i++)
1233                 if (keybindings[keymap].data[i].alias == key)
1234                         return keybindings[keymap].data[i].request;
1236         for (i = 0; i < keybindings[KEYMAP_GENERIC].size; i++)
1237                 if (keybindings[KEYMAP_GENERIC].data[i].alias == key)
1238                         return keybindings[KEYMAP_GENERIC].data[i].request;
1240         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1241                 if (default_keybindings[i].alias == key)
1242                         return default_keybindings[i].request;
1244         return (enum request) key;
1248 struct key {
1249         const char *name;
1250         int value;
1251 };
1253 static const struct key key_table[] = {
1254         { "Enter",      KEY_RETURN },
1255         { "Space",      ' ' },
1256         { "Backspace",  KEY_BACKSPACE },
1257         { "Tab",        KEY_TAB },
1258         { "Escape",     KEY_ESC },
1259         { "Left",       KEY_LEFT },
1260         { "Right",      KEY_RIGHT },
1261         { "Up",         KEY_UP },
1262         { "Down",       KEY_DOWN },
1263         { "Insert",     KEY_IC },
1264         { "Delete",     KEY_DC },
1265         { "Hash",       '#' },
1266         { "Home",       KEY_HOME },
1267         { "End",        KEY_END },
1268         { "PageUp",     KEY_PPAGE },
1269         { "PageDown",   KEY_NPAGE },
1270         { "F1",         KEY_F(1) },
1271         { "F2",         KEY_F(2) },
1272         { "F3",         KEY_F(3) },
1273         { "F4",         KEY_F(4) },
1274         { "F5",         KEY_F(5) },
1275         { "F6",         KEY_F(6) },
1276         { "F7",         KEY_F(7) },
1277         { "F8",         KEY_F(8) },
1278         { "F9",         KEY_F(9) },
1279         { "F10",        KEY_F(10) },
1280         { "F11",        KEY_F(11) },
1281         { "F12",        KEY_F(12) },
1282 };
1284 static int
1285 get_key_value(const char *name)
1287         int i;
1289         for (i = 0; i < ARRAY_SIZE(key_table); i++)
1290                 if (!strcasecmp(key_table[i].name, name))
1291                         return key_table[i].value;
1293         if (strlen(name) == 1 && isprint(*name))
1294                 return (int) *name;
1296         return ERR;
1299 static const char *
1300 get_key_name(int key_value)
1302         static char key_char[] = "'X'";
1303         const char *seq = NULL;
1304         int key;
1306         for (key = 0; key < ARRAY_SIZE(key_table); key++)
1307                 if (key_table[key].value == key_value)
1308                         seq = key_table[key].name;
1310         if (seq == NULL &&
1311             key_value < 127 &&
1312             isprint(key_value)) {
1313                 key_char[1] = (char) key_value;
1314                 seq = key_char;
1315         }
1317         return seq ? seq : "(no key)";
1320 static const char *
1321 get_key(enum request request)
1323         static char buf[BUFSIZ];
1324         size_t pos = 0;
1325         char *sep = "";
1326         int i;
1328         buf[pos] = 0;
1330         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1331                 const struct keybinding *keybinding = &default_keybindings[i];
1333                 if (keybinding->request != request)
1334                         continue;
1336                 if (!string_format_from(buf, &pos, "%s%s", sep,
1337                                         get_key_name(keybinding->alias)))
1338                         return "Too many keybindings!";
1339                 sep = ", ";
1340         }
1342         return buf;
1345 struct run_request {
1346         enum keymap keymap;
1347         int key;
1348         const char *argv[SIZEOF_ARG];
1349 };
1351 static struct run_request *run_request;
1352 static size_t run_requests;
1354 DEFINE_ALLOCATOR(realloc_run_requests, struct run_request, 8)
1356 static enum request
1357 add_run_request(enum keymap keymap, int key, int argc, const char **argv)
1359         struct run_request *req;
1361         if (argc >= ARRAY_SIZE(req->argv) - 1)
1362                 return REQ_NONE;
1364         if (!realloc_run_requests(&run_request, run_requests, 1))
1365                 return REQ_NONE;
1367         req = &run_request[run_requests];
1368         req->keymap = keymap;
1369         req->key = key;
1370         req->argv[0] = NULL;
1372         if (!format_argv(req->argv, argv, FORMAT_NONE))
1373                 return REQ_NONE;
1375         return REQ_NONE + ++run_requests;
1378 static struct run_request *
1379 get_run_request(enum request request)
1381         if (request <= REQ_NONE)
1382                 return NULL;
1383         return &run_request[request - REQ_NONE - 1];
1386 static void
1387 add_builtin_run_requests(void)
1389         const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1390         const char *commit[] = { "git", "commit", NULL };
1391         const char *gc[] = { "git", "gc", NULL };
1392         struct {
1393                 enum keymap keymap;
1394                 int key;
1395                 int argc;
1396                 const char **argv;
1397         } reqs[] = {
1398                 { KEYMAP_MAIN,    'C', ARRAY_SIZE(cherry_pick) - 1, cherry_pick },
1399                 { KEYMAP_STATUS,  'C', ARRAY_SIZE(commit) - 1, commit },
1400                 { KEYMAP_GENERIC, 'G', ARRAY_SIZE(gc) - 1, gc },
1401         };
1402         int i;
1404         for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1405                 enum request req;
1407                 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argc, reqs[i].argv);
1408                 if (req != REQ_NONE)
1409                         add_keybinding(reqs[i].keymap, req, reqs[i].key);
1410         }
1413 /*
1414  * User config file handling.
1415  */
1417 static int   config_lineno;
1418 static bool  config_errors;
1419 static const char *config_msg;
1421 static const struct enum_map color_map[] = {
1422 #define COLOR_MAP(name) ENUM_MAP(#name, COLOR_##name)
1423         COLOR_MAP(DEFAULT),
1424         COLOR_MAP(BLACK),
1425         COLOR_MAP(BLUE),
1426         COLOR_MAP(CYAN),
1427         COLOR_MAP(GREEN),
1428         COLOR_MAP(MAGENTA),
1429         COLOR_MAP(RED),
1430         COLOR_MAP(WHITE),
1431         COLOR_MAP(YELLOW),
1432 };
1434 static const struct enum_map attr_map[] = {
1435 #define ATTR_MAP(name) ENUM_MAP(#name, A_##name)
1436         ATTR_MAP(NORMAL),
1437         ATTR_MAP(BLINK),
1438         ATTR_MAP(BOLD),
1439         ATTR_MAP(DIM),
1440         ATTR_MAP(REVERSE),
1441         ATTR_MAP(STANDOUT),
1442         ATTR_MAP(UNDERLINE),
1443 };
1445 #define set_attribute(attr, name)       map_enum(attr, attr_map, name)
1447 static int parse_step(double *opt, const char *arg)
1449         *opt = atoi(arg);
1450         if (!strchr(arg, '%'))
1451                 return OK;
1453         /* "Shift down" so 100% and 1 does not conflict. */
1454         *opt = (*opt - 1) / 100;
1455         if (*opt >= 1.0) {
1456                 *opt = 0.99;
1457                 config_msg = "Step value larger than 100%";
1458                 return ERR;
1459         }
1460         if (*opt < 0.0) {
1461                 *opt = 1;
1462                 config_msg = "Invalid step value";
1463                 return ERR;
1464         }
1465         return OK;
1468 static int
1469 parse_int(int *opt, const char *arg, int min, int max)
1471         int value = atoi(arg);
1473         if (min <= value && value <= max) {
1474                 *opt = value;
1475                 return OK;
1476         }
1478         config_msg = "Integer value out of bound";
1479         return ERR;
1482 static bool
1483 set_color(int *color, const char *name)
1485         if (map_enum(color, color_map, name))
1486                 return TRUE;
1487         if (!prefixcmp(name, "color"))
1488                 return parse_int(color, name + 5, 0, 255) == OK;
1489         return FALSE;
1492 /* Wants: object fgcolor bgcolor [attribute] */
1493 static int
1494 option_color_command(int argc, const char *argv[])
1496         struct line_info *info;
1498         if (argc != 3 && argc != 4) {
1499                 config_msg = "Wrong number of arguments given to color command";
1500                 return ERR;
1501         }
1503         info = get_line_info(argv[0]);
1504         if (!info) {
1505                 static const struct enum_map obsolete[] = {
1506                         ENUM_MAP("main-delim",  LINE_DELIMITER),
1507                         ENUM_MAP("main-date",   LINE_DATE),
1508                         ENUM_MAP("main-author", LINE_AUTHOR),
1509                 };
1510                 int index;
1512                 if (!map_enum(&index, obsolete, argv[0])) {
1513                         config_msg = "Unknown color name";
1514                         return ERR;
1515                 }
1516                 info = &line_info[index];
1517         }
1519         if (!set_color(&info->fg, argv[1]) ||
1520             !set_color(&info->bg, argv[2])) {
1521                 config_msg = "Unknown color";
1522                 return ERR;
1523         }
1525         if (argc == 4 && !set_attribute(&info->attr, argv[3])) {
1526                 config_msg = "Unknown attribute";
1527                 return ERR;
1528         }
1530         return OK;
1533 static int parse_bool(bool *opt, const char *arg)
1535         *opt = (!strcmp(arg, "1") || !strcmp(arg, "true") || !strcmp(arg, "yes"))
1536                 ? TRUE : FALSE;
1537         return OK;
1540 static int
1541 parse_string(char *opt, const char *arg, size_t optsize)
1543         int arglen = strlen(arg);
1545         switch (arg[0]) {
1546         case '\"':
1547         case '\'':
1548                 if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1549                         config_msg = "Unmatched quotation";
1550                         return ERR;
1551                 }
1552                 arg += 1; arglen -= 2;
1553         default:
1554                 string_ncopy_do(opt, optsize, arg, arglen);
1555                 return OK;
1556         }
1559 /* Wants: name = value */
1560 static int
1561 option_set_command(int argc, const char *argv[])
1563         if (argc != 3) {
1564                 config_msg = "Wrong number of arguments given to set command";
1565                 return ERR;
1566         }
1568         if (strcmp(argv[1], "=")) {
1569                 config_msg = "No value assigned";
1570                 return ERR;
1571         }
1573         if (!strcmp(argv[0], "show-author"))
1574                 return parse_bool(&opt_author, argv[2]);
1576         if (!strcmp(argv[0], "show-date"))
1577                 return parse_bool(&opt_date, argv[2]);
1579         if (!strcmp(argv[0], "show-rev-graph"))
1580                 return parse_bool(&opt_rev_graph, argv[2]);
1582         if (!strcmp(argv[0], "show-refs"))
1583                 return parse_bool(&opt_show_refs, argv[2]);
1585         if (!strcmp(argv[0], "show-line-numbers"))
1586                 return parse_bool(&opt_line_number, argv[2]);
1588         if (!strcmp(argv[0], "line-graphics"))
1589                 return parse_bool(&opt_line_graphics, argv[2]);
1591         if (!strcmp(argv[0], "line-number-interval"))
1592                 return parse_int(&opt_num_interval, argv[2], 1, 1024);
1594         if (!strcmp(argv[0], "author-width"))
1595                 return parse_int(&opt_author_cols, argv[2], 0, 1024);
1597         if (!strcmp(argv[0], "horizontal-scroll"))
1598                 return parse_step(&opt_hscroll, argv[2]);
1600         if (!strcmp(argv[0], "split-view-height"))
1601                 return parse_step(&opt_scale_split_view, argv[2]);
1603         if (!strcmp(argv[0], "tab-size"))
1604                 return parse_int(&opt_tab_size, argv[2], 1, 1024);
1606         if (!strcmp(argv[0], "commit-encoding"))
1607                 return parse_string(opt_encoding, argv[2], sizeof(opt_encoding));
1609         config_msg = "Unknown variable name";
1610         return ERR;
1613 /* Wants: mode request key */
1614 static int
1615 option_bind_command(int argc, const char *argv[])
1617         enum request request;
1618         int keymap;
1619         int key;
1621         if (argc < 3) {
1622                 config_msg = "Wrong number of arguments given to bind command";
1623                 return ERR;
1624         }
1626         if (set_keymap(&keymap, argv[0]) == ERR) {
1627                 config_msg = "Unknown key map";
1628                 return ERR;
1629         }
1631         key = get_key_value(argv[1]);
1632         if (key == ERR) {
1633                 config_msg = "Unknown key";
1634                 return ERR;
1635         }
1637         request = get_request(argv[2]);
1638         if (request == REQ_NONE) {
1639                 static const struct enum_map obsolete[] = {
1640                         ENUM_MAP("cherry-pick",         REQ_NONE),
1641                         ENUM_MAP("screen-resize",       REQ_NONE),
1642                         ENUM_MAP("tree-parent",         REQ_PARENT),
1643                 };
1644                 int alias;
1646                 if (map_enum(&alias, obsolete, argv[2])) {
1647                         if (alias != REQ_NONE)
1648                                 add_keybinding(keymap, alias, key);
1649                         config_msg = "Obsolete request name";
1650                         return ERR;
1651                 }
1652         }
1653         if (request == REQ_NONE && *argv[2]++ == '!')
1654                 request = add_run_request(keymap, key, argc - 2, argv + 2);
1655         if (request == REQ_NONE) {
1656                 config_msg = "Unknown request name";
1657                 return ERR;
1658         }
1660         add_keybinding(keymap, request, key);
1662         return OK;
1665 static int
1666 set_option(const char *opt, char *value)
1668         const char *argv[SIZEOF_ARG];
1669         int argc = 0;
1671         if (!argv_from_string(argv, &argc, value)) {
1672                 config_msg = "Too many option arguments";
1673                 return ERR;
1674         }
1676         if (!strcmp(opt, "color"))
1677                 return option_color_command(argc, argv);
1679         if (!strcmp(opt, "set"))
1680                 return option_set_command(argc, argv);
1682         if (!strcmp(opt, "bind"))
1683                 return option_bind_command(argc, argv);
1685         config_msg = "Unknown option command";
1686         return ERR;
1689 static int
1690 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
1692         int status = OK;
1694         config_lineno++;
1695         config_msg = "Internal error";
1697         /* Check for comment markers, since read_properties() will
1698          * only ensure opt and value are split at first " \t". */
1699         optlen = strcspn(opt, "#");
1700         if (optlen == 0)
1701                 return OK;
1703         if (opt[optlen] != 0) {
1704                 config_msg = "No option value";
1705                 status = ERR;
1707         }  else {
1708                 /* Look for comment endings in the value. */
1709                 size_t len = strcspn(value, "#");
1711                 if (len < valuelen) {
1712                         valuelen = len;
1713                         value[valuelen] = 0;
1714                 }
1716                 status = set_option(opt, value);
1717         }
1719         if (status == ERR) {
1720                 warn("Error on line %d, near '%.*s': %s",
1721                      config_lineno, (int) optlen, opt, config_msg);
1722                 config_errors = TRUE;
1723         }
1725         /* Always keep going if errors are encountered. */
1726         return OK;
1729 static void
1730 load_option_file(const char *path)
1732         struct io io = {};
1734         /* It's OK that the file doesn't exist. */
1735         if (!io_open(&io, path))
1736                 return;
1738         config_lineno = 0;
1739         config_errors = FALSE;
1741         if (io_load(&io, " \t", read_option) == ERR ||
1742             config_errors == TRUE)
1743                 warn("Errors while loading %s.", path);
1746 static int
1747 load_options(void)
1749         const char *home = getenv("HOME");
1750         const char *tigrc_user = getenv("TIGRC_USER");
1751         const char *tigrc_system = getenv("TIGRC_SYSTEM");
1752         char buf[SIZEOF_STR];
1754         add_builtin_run_requests();
1756         if (!tigrc_system)
1757                 tigrc_system = SYSCONFDIR "/tigrc";
1758         load_option_file(tigrc_system);
1760         if (!tigrc_user) {
1761                 if (!home || !string_format(buf, "%s/.tigrc", home))
1762                         return ERR;
1763                 tigrc_user = buf;
1764         }
1765         load_option_file(tigrc_user);
1767         return OK;
1771 /*
1772  * The viewer
1773  */
1775 struct view;
1776 struct view_ops;
1778 /* The display array of active views and the index of the current view. */
1779 static struct view *display[2];
1780 static unsigned int current_view;
1782 #define foreach_displayed_view(view, i) \
1783         for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1785 #define displayed_views()       (display[1] != NULL ? 2 : 1)
1787 /* Current head and commit ID */
1788 static char ref_blob[SIZEOF_REF]        = "";
1789 static char ref_commit[SIZEOF_REF]      = "HEAD";
1790 static char ref_head[SIZEOF_REF]        = "HEAD";
1792 struct view {
1793         const char *name;       /* View name */
1794         const char *cmd_env;    /* Command line set via environment */
1795         const char *id;         /* Points to either of ref_{head,commit,blob} */
1797         struct view_ops *ops;   /* View operations */
1799         enum keymap keymap;     /* What keymap does this view have */
1800         bool git_dir;           /* Whether the view requires a git directory. */
1802         char ref[SIZEOF_REF];   /* Hovered commit reference */
1803         char vid[SIZEOF_REF];   /* View ID. Set to id member when updating. */
1805         int height, width;      /* The width and height of the main window */
1806         WINDOW *win;            /* The main window */
1807         WINDOW *title;          /* The title window living below the main window */
1809         /* Navigation */
1810         unsigned long offset;   /* Offset of the window top */
1811         unsigned long yoffset;  /* Offset from the window side. */
1812         unsigned long lineno;   /* Current line number */
1813         unsigned long p_offset; /* Previous offset of the window top */
1814         unsigned long p_yoffset;/* Previous offset from the window side */
1815         unsigned long p_lineno; /* Previous current line number */
1816         bool p_restore;         /* Should the previous position be restored. */
1818         /* Searching */
1819         char grep[SIZEOF_STR];  /* Search string */
1820         regex_t *regex;         /* Pre-compiled regexp */
1822         /* If non-NULL, points to the view that opened this view. If this view
1823          * is closed tig will switch back to the parent view. */
1824         struct view *parent;
1826         /* Buffering */
1827         size_t lines;           /* Total number of lines */
1828         struct line *line;      /* Line index */
1829         unsigned int digits;    /* Number of digits in the lines member. */
1831         /* Drawing */
1832         struct line *curline;   /* Line currently being drawn. */
1833         enum line_type curtype; /* Attribute currently used for drawing. */
1834         unsigned long col;      /* Column when drawing. */
1835         bool has_scrolled;      /* View was scrolled. */
1837         /* Loading */
1838         struct io io;
1839         struct io *pipe;
1840         time_t start_time;
1841         time_t update_secs;
1842 };
1844 struct view_ops {
1845         /* What type of content being displayed. Used in the title bar. */
1846         const char *type;
1847         /* Default command arguments. */
1848         const char **argv;
1849         /* Open and reads in all view content. */
1850         bool (*open)(struct view *view);
1851         /* Read one line; updates view->line. */
1852         bool (*read)(struct view *view, char *data);
1853         /* Draw one line; @lineno must be < view->height. */
1854         bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
1855         /* Depending on view handle a special requests. */
1856         enum request (*request)(struct view *view, enum request request, struct line *line);
1857         /* Search for regexp in a line. */
1858         bool (*grep)(struct view *view, struct line *line);
1859         /* Select line */
1860         void (*select)(struct view *view, struct line *line);
1861 };
1863 static struct view_ops blame_ops;
1864 static struct view_ops blob_ops;
1865 static struct view_ops diff_ops;
1866 static struct view_ops help_ops;
1867 static struct view_ops log_ops;
1868 static struct view_ops main_ops;
1869 static struct view_ops pager_ops;
1870 static struct view_ops stage_ops;
1871 static struct view_ops status_ops;
1872 static struct view_ops tree_ops;
1873 static struct view_ops branch_ops;
1875 #define VIEW_STR(name, env, ref, ops, map, git) \
1876         { name, #env, ref, ops, map, git }
1878 #define VIEW_(id, name, ops, git, ref) \
1879         VIEW_STR(name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
1882 static struct view views[] = {
1883         VIEW_(MAIN,   "main",   &main_ops,   TRUE,  ref_head),
1884         VIEW_(DIFF,   "diff",   &diff_ops,   TRUE,  ref_commit),
1885         VIEW_(LOG,    "log",    &log_ops,    TRUE,  ref_head),
1886         VIEW_(TREE,   "tree",   &tree_ops,   TRUE,  ref_commit),
1887         VIEW_(BLOB,   "blob",   &blob_ops,   TRUE,  ref_blob),
1888         VIEW_(BLAME,  "blame",  &blame_ops,  TRUE,  ref_commit),
1889         VIEW_(BRANCH, "branch", &branch_ops, TRUE,  ref_head),
1890         VIEW_(HELP,   "help",   &help_ops,   FALSE, ""),
1891         VIEW_(PAGER,  "pager",  &pager_ops,  FALSE, "stdin"),
1892         VIEW_(STATUS, "status", &status_ops, TRUE,  ""),
1893         VIEW_(STAGE,  "stage",  &stage_ops,  TRUE,  ""),
1894 };
1896 #define VIEW(req)       (&views[(req) - REQ_OFFSET - 1])
1897 #define VIEW_REQ(view)  ((view) - views + REQ_OFFSET + 1)
1899 #define foreach_view(view, i) \
1900         for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
1902 #define view_is_displayed(view) \
1903         (view == display[0] || view == display[1])
1906 enum line_graphic {
1907         LINE_GRAPHIC_VLINE
1908 };
1910 static chtype line_graphics[] = {
1911         /* LINE_GRAPHIC_VLINE: */ '|'
1912 };
1914 static inline void
1915 set_view_attr(struct view *view, enum line_type type)
1917         if (!view->curline->selected && view->curtype != type) {
1918                 wattrset(view->win, get_line_attr(type));
1919                 wchgat(view->win, -1, 0, type, NULL);
1920                 view->curtype = type;
1921         }
1924 static int
1925 draw_chars(struct view *view, enum line_type type, const char *string,
1926            int max_len, bool use_tilde)
1928         int len = 0;
1929         int col = 0;
1930         int trimmed = FALSE;
1931         size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
1933         if (max_len <= 0)
1934                 return 0;
1936         if (opt_utf8) {
1937                 len = utf8_length(&string, skip, &col, max_len, &trimmed, use_tilde);
1938         } else {
1939                 col = len = strlen(string);
1940                 if (len > max_len) {
1941                         if (use_tilde) {
1942                                 max_len -= 1;
1943                         }
1944                         col = len = max_len;
1945                         trimmed = TRUE;
1946                 }
1947         }
1949         set_view_attr(view, type);
1950         if (len > 0)
1951                 waddnstr(view->win, string, len);
1952         if (trimmed && use_tilde) {
1953                 set_view_attr(view, LINE_DELIMITER);
1954                 waddch(view->win, '~');
1955                 col++;
1956         }
1958         return col;
1961 static int
1962 draw_space(struct view *view, enum line_type type, int max, int spaces)
1964         static char space[] = "                    ";
1965         int col = 0;
1967         spaces = MIN(max, spaces);
1969         while (spaces > 0) {
1970                 int len = MIN(spaces, sizeof(space) - 1);
1972                 col += draw_chars(view, type, space, len, FALSE);
1973                 spaces -= len;
1974         }
1976         return col;
1979 static bool
1980 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
1982         view->col += draw_chars(view, type, string, view->width + view->yoffset - view->col, trim);
1983         return view->width + view->yoffset <= view->col;
1986 static bool
1987 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
1989         size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
1990         int max = view->width + view->yoffset - view->col;
1991         int i;
1993         if (max < size)
1994                 size = max;
1996         set_view_attr(view, type);
1997         /* Using waddch() instead of waddnstr() ensures that
1998          * they'll be rendered correctly for the cursor line. */
1999         for (i = skip; i < size; i++)
2000                 waddch(view->win, graphic[i]);
2002         view->col += size;
2003         if (size < max && skip <= size)
2004                 waddch(view->win, ' ');
2005         view->col++;
2007         return view->width + view->yoffset <= view->col;
2010 static bool
2011 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
2013         int max = MIN(view->width + view->yoffset - view->col, len);
2014         int col;
2016         if (text)
2017                 col = draw_chars(view, type, text, max - 1, trim);
2018         else
2019                 col = draw_space(view, type, max - 1, max - 1);
2021         view->col += col;
2022         view->col += draw_space(view, LINE_DEFAULT, max - col, max - col);
2023         return view->width + view->yoffset <= view->col;
2026 static bool
2027 draw_date(struct view *view, time_t *time)
2029         const char *date = mkdate(time);
2031         return draw_field(view, LINE_DATE, date, DATE_COLS, FALSE);
2034 static bool
2035 draw_author(struct view *view, const char *author)
2037         bool trim = opt_author_cols == 0 || opt_author_cols > 5 || !author;
2039         if (!trim) {
2040                 static char initials[10];
2041                 size_t pos;
2043 #define is_initial_sep(c) (isspace(c) || ispunct(c) || (c) == '@')
2045                 memset(initials, 0, sizeof(initials));
2046                 for (pos = 0; *author && pos < opt_author_cols - 1; author++, pos++) {
2047                         while (is_initial_sep(*author))
2048                                 author++;
2049                         strncpy(&initials[pos], author, sizeof(initials) - 1 - pos);
2050                         while (*author && !is_initial_sep(author[1]))
2051                                 author++;
2052                 }
2054                 author = initials;
2055         }
2057         return draw_field(view, LINE_AUTHOR, author, opt_author_cols, trim);
2060 static bool
2061 draw_mode(struct view *view, mode_t mode)
2063         const char *str;
2065         if (S_ISDIR(mode))
2066                 str = "drwxr-xr-x";
2067         else if (S_ISLNK(mode))
2068                 str = "lrwxrwxrwx";
2069         else if (S_ISGITLINK(mode))
2070                 str = "m---------";
2071         else if (S_ISREG(mode) && mode & S_IXUSR)
2072                 str = "-rwxr-xr-x";
2073         else if (S_ISREG(mode))
2074                 str = "-rw-r--r--";
2075         else
2076                 str = "----------";
2078         return draw_field(view, LINE_MODE, str, STRING_SIZE("-rw-r--r-- "), FALSE);
2081 static bool
2082 draw_lineno(struct view *view, unsigned int lineno)
2084         char number[10];
2085         int digits3 = view->digits < 3 ? 3 : view->digits;
2086         int max = MIN(view->width + view->yoffset - view->col, digits3);
2087         char *text = NULL;
2089         lineno += view->offset + 1;
2090         if (lineno == 1 || (lineno % opt_num_interval) == 0) {
2091                 static char fmt[] = "%1ld";
2093                 fmt[1] = '0' + (view->digits <= 9 ? digits3 : 1);
2094                 if (string_format(number, fmt, lineno))
2095                         text = number;
2096         }
2097         if (text)
2098                 view->col += draw_chars(view, LINE_LINE_NUMBER, text, max, TRUE);
2099         else
2100                 view->col += draw_space(view, LINE_LINE_NUMBER, max, digits3);
2101         return draw_graphic(view, LINE_DEFAULT, &line_graphics[LINE_GRAPHIC_VLINE], 1);
2104 static bool
2105 draw_view_line(struct view *view, unsigned int lineno)
2107         struct line *line;
2108         bool selected = (view->offset + lineno == view->lineno);
2110         assert(view_is_displayed(view));
2112         if (view->offset + lineno >= view->lines)
2113                 return FALSE;
2115         line = &view->line[view->offset + lineno];
2117         wmove(view->win, lineno, 0);
2118         if (line->cleareol)
2119                 wclrtoeol(view->win);
2120         view->col = 0;
2121         view->curline = line;
2122         view->curtype = LINE_NONE;
2123         line->selected = FALSE;
2124         line->dirty = line->cleareol = 0;
2126         if (selected) {
2127                 set_view_attr(view, LINE_CURSOR);
2128                 line->selected = TRUE;
2129                 view->ops->select(view, line);
2130         }
2132         return view->ops->draw(view, line, lineno);
2135 static void
2136 redraw_view_dirty(struct view *view)
2138         bool dirty = FALSE;
2139         int lineno;
2141         for (lineno = 0; lineno < view->height; lineno++) {
2142                 if (view->offset + lineno >= view->lines)
2143                         break;
2144                 if (!view->line[view->offset + lineno].dirty)
2145                         continue;
2146                 dirty = TRUE;
2147                 if (!draw_view_line(view, lineno))
2148                         break;
2149         }
2151         if (!dirty)
2152                 return;
2153         wnoutrefresh(view->win);
2156 static void
2157 redraw_view_from(struct view *view, int lineno)
2159         assert(0 <= lineno && lineno < view->height);
2161         for (; lineno < view->height; lineno++) {
2162                 if (!draw_view_line(view, lineno))
2163                         break;
2164         }
2166         wnoutrefresh(view->win);
2169 static void
2170 redraw_view(struct view *view)
2172         werase(view->win);
2173         redraw_view_from(view, 0);
2177 static void
2178 update_view_title(struct view *view)
2180         char buf[SIZEOF_STR];
2181         char state[SIZEOF_STR];
2182         size_t bufpos = 0, statelen = 0;
2184         assert(view_is_displayed(view));
2186         if (view != VIEW(REQ_VIEW_STATUS) && view->lines) {
2187                 unsigned int view_lines = view->offset + view->height;
2188                 unsigned int lines = view->lines
2189                                    ? MIN(view_lines, view->lines) * 100 / view->lines
2190                                    : 0;
2192                 string_format_from(state, &statelen, " - %s %d of %d (%d%%)",
2193                                    view->ops->type,
2194                                    view->lineno + 1,
2195                                    view->lines,
2196                                    lines);
2198         }
2200         if (view->pipe) {
2201                 time_t secs = time(NULL) - view->start_time;
2203                 /* Three git seconds are a long time ... */
2204                 if (secs > 2)
2205                         string_format_from(state, &statelen, " loading %lds", secs);
2206         }
2208         string_format_from(buf, &bufpos, "[%s]", view->name);
2209         if (*view->ref && bufpos < view->width) {
2210                 size_t refsize = strlen(view->ref);
2211                 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2213                 if (minsize < view->width)
2214                         refsize = view->width - minsize + 7;
2215                 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2216         }
2218         if (statelen && bufpos < view->width) {
2219                 string_format_from(buf, &bufpos, "%s", state);
2220         }
2222         if (view == display[current_view])
2223                 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2224         else
2225                 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2227         mvwaddnstr(view->title, 0, 0, buf, bufpos);
2228         wclrtoeol(view->title);
2229         wnoutrefresh(view->title);
2232 static int
2233 apply_step(double step, int value)
2235         if (step >= 1)
2236                 return (int) step;
2237         value *= step + 0.01;
2238         return value ? value : 1;
2241 static void
2242 resize_display(void)
2244         int offset, i;
2245         struct view *base = display[0];
2246         struct view *view = display[1] ? display[1] : display[0];
2248         /* Setup window dimensions */
2250         getmaxyx(stdscr, base->height, base->width);
2252         /* Make room for the status window. */
2253         base->height -= 1;
2255         if (view != base) {
2256                 /* Horizontal split. */
2257                 view->width   = base->width;
2258                 view->height  = apply_step(opt_scale_split_view, base->height);
2259                 view->height  = MAX(view->height, MIN_VIEW_HEIGHT);
2260                 view->height  = MIN(view->height, base->height - MIN_VIEW_HEIGHT);
2261                 base->height -= view->height;
2263                 /* Make room for the title bar. */
2264                 view->height -= 1;
2265         }
2267         /* Make room for the title bar. */
2268         base->height -= 1;
2270         offset = 0;
2272         foreach_displayed_view (view, i) {
2273                 if (!view->win) {
2274                         view->win = newwin(view->height, 0, offset, 0);
2275                         if (!view->win)
2276                                 die("Failed to create %s view", view->name);
2278                         scrollok(view->win, FALSE);
2280                         view->title = newwin(1, 0, offset + view->height, 0);
2281                         if (!view->title)
2282                                 die("Failed to create title window");
2284                 } else {
2285                         wresize(view->win, view->height, view->width);
2286                         mvwin(view->win,   offset, 0);
2287                         mvwin(view->title, offset + view->height, 0);
2288                 }
2290                 offset += view->height + 1;
2291         }
2294 static void
2295 redraw_display(bool clear)
2297         struct view *view;
2298         int i;
2300         foreach_displayed_view (view, i) {
2301                 if (clear)
2302                         wclear(view->win);
2303                 redraw_view(view);
2304                 update_view_title(view);
2305         }
2308 static void
2309 toggle_view_option(bool *option, const char *help)
2311         *option = !*option;
2312         redraw_display(FALSE);
2313         report("%sabling %s", *option ? "En" : "Dis", help);
2316 static void
2317 open_option_menu(void)
2319         const struct menu_item menu[] = {
2320                 { '.', "line numbers", &opt_line_number },
2321                 { 'D', "date display", &opt_date },
2322                 { 'A', "author display", &opt_author },
2323                 { 'g', "revision graph display", &opt_rev_graph },
2324                 { 'F', "reference display", &opt_show_refs },
2325                 { 0 }
2326         };
2327         int selected = 0;
2329         if (prompt_menu("Toggle option", menu, &selected))
2330                 toggle_view_option(menu[selected].data, menu[selected].text);
2333 static void
2334 maximize_view(struct view *view)
2336         memset(display, 0, sizeof(display));
2337         current_view = 0;
2338         display[current_view] = view;
2339         resize_display();
2340         redraw_display(FALSE);
2341         report("");
2345 /*
2346  * Navigation
2347  */
2349 static bool
2350 goto_view_line(struct view *view, unsigned long offset, unsigned long lineno)
2352         if (lineno >= view->lines)
2353                 lineno = view->lines > 0 ? view->lines - 1 : 0;
2355         if (offset > lineno || offset + view->height <= lineno) {
2356                 unsigned long half = view->height / 2;
2358                 if (lineno > half)
2359                         offset = lineno - half;
2360                 else
2361                         offset = 0;
2362         }
2364         if (offset != view->offset || lineno != view->lineno) {
2365                 view->offset = offset;
2366                 view->lineno = lineno;
2367                 return TRUE;
2368         }
2370         return FALSE;
2373 /* Scrolling backend */
2374 static void
2375 do_scroll_view(struct view *view, int lines)
2377         bool redraw_current_line = FALSE;
2379         /* The rendering expects the new offset. */
2380         view->offset += lines;
2382         assert(0 <= view->offset && view->offset < view->lines);
2383         assert(lines);
2385         /* Move current line into the view. */
2386         if (view->lineno < view->offset) {
2387                 view->lineno = view->offset;
2388                 redraw_current_line = TRUE;
2389         } else if (view->lineno >= view->offset + view->height) {
2390                 view->lineno = view->offset + view->height - 1;
2391                 redraw_current_line = TRUE;
2392         }
2394         assert(view->offset <= view->lineno && view->lineno < view->lines);
2396         /* Redraw the whole screen if scrolling is pointless. */
2397         if (view->height < ABS(lines)) {
2398                 redraw_view(view);
2400         } else {
2401                 int line = lines > 0 ? view->height - lines : 0;
2402                 int end = line + ABS(lines);
2404                 scrollok(view->win, TRUE);
2405                 wscrl(view->win, lines);
2406                 scrollok(view->win, FALSE);
2408                 while (line < end && draw_view_line(view, line))
2409                         line++;
2411                 if (redraw_current_line)
2412                         draw_view_line(view, view->lineno - view->offset);
2413                 wnoutrefresh(view->win);
2414         }
2416         view->has_scrolled = TRUE;
2417         report("");
2420 /* Scroll frontend */
2421 static void
2422 scroll_view(struct view *view, enum request request)
2424         int lines = 1;
2426         assert(view_is_displayed(view));
2428         switch (request) {
2429         case REQ_SCROLL_LEFT:
2430                 if (view->yoffset == 0) {
2431                         report("Cannot scroll beyond the first column");
2432                         return;
2433                 }
2434                 if (view->yoffset <= apply_step(opt_hscroll, view->width))
2435                         view->yoffset = 0;
2436                 else
2437                         view->yoffset -= apply_step(opt_hscroll, view->width);
2438                 redraw_view_from(view, 0);
2439                 report("");
2440                 return;
2441         case REQ_SCROLL_RIGHT:
2442                 view->yoffset += apply_step(opt_hscroll, view->width);
2443                 redraw_view(view);
2444                 report("");
2445                 return;
2446         case REQ_SCROLL_PAGE_DOWN:
2447                 lines = view->height;
2448         case REQ_SCROLL_LINE_DOWN:
2449                 if (view->offset + lines > view->lines)
2450                         lines = view->lines - view->offset;
2452                 if (lines == 0 || view->offset + view->height >= view->lines) {
2453                         report("Cannot scroll beyond the last line");
2454                         return;
2455                 }
2456                 break;
2458         case REQ_SCROLL_PAGE_UP:
2459                 lines = view->height;
2460         case REQ_SCROLL_LINE_UP:
2461                 if (lines > view->offset)
2462                         lines = view->offset;
2464                 if (lines == 0) {
2465                         report("Cannot scroll beyond the first line");
2466                         return;
2467                 }
2469                 lines = -lines;
2470                 break;
2472         default:
2473                 die("request %d not handled in switch", request);
2474         }
2476         do_scroll_view(view, lines);
2479 /* Cursor moving */
2480 static void
2481 move_view(struct view *view, enum request request)
2483         int scroll_steps = 0;
2484         int steps;
2486         switch (request) {
2487         case REQ_MOVE_FIRST_LINE:
2488                 steps = -view->lineno;
2489                 break;
2491         case REQ_MOVE_LAST_LINE:
2492                 steps = view->lines - view->lineno - 1;
2493                 break;
2495         case REQ_MOVE_PAGE_UP:
2496                 steps = view->height > view->lineno
2497                       ? -view->lineno : -view->height;
2498                 break;
2500         case REQ_MOVE_PAGE_DOWN:
2501                 steps = view->lineno + view->height >= view->lines
2502                       ? view->lines - view->lineno - 1 : view->height;
2503                 break;
2505         case REQ_MOVE_UP:
2506                 steps = -1;
2507                 break;
2509         case REQ_MOVE_DOWN:
2510                 steps = 1;
2511                 break;
2513         default:
2514                 die("request %d not handled in switch", request);
2515         }
2517         if (steps <= 0 && view->lineno == 0) {
2518                 report("Cannot move beyond the first line");
2519                 return;
2521         } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2522                 report("Cannot move beyond the last line");
2523                 return;
2524         }
2526         /* Move the current line */
2527         view->lineno += steps;
2528         assert(0 <= view->lineno && view->lineno < view->lines);
2530         /* Check whether the view needs to be scrolled */
2531         if (view->lineno < view->offset ||
2532             view->lineno >= view->offset + view->height) {
2533                 scroll_steps = steps;
2534                 if (steps < 0 && -steps > view->offset) {
2535                         scroll_steps = -view->offset;
2537                 } else if (steps > 0) {
2538                         if (view->lineno == view->lines - 1 &&
2539                             view->lines > view->height) {
2540                                 scroll_steps = view->lines - view->offset - 1;
2541                                 if (scroll_steps >= view->height)
2542                                         scroll_steps -= view->height - 1;
2543                         }
2544                 }
2545         }
2547         if (!view_is_displayed(view)) {
2548                 view->offset += scroll_steps;
2549                 assert(0 <= view->offset && view->offset < view->lines);
2550                 view->ops->select(view, &view->line[view->lineno]);
2551                 return;
2552         }
2554         /* Repaint the old "current" line if we be scrolling */
2555         if (ABS(steps) < view->height)
2556                 draw_view_line(view, view->lineno - steps - view->offset);
2558         if (scroll_steps) {
2559                 do_scroll_view(view, scroll_steps);
2560                 return;
2561         }
2563         /* Draw the current line */
2564         draw_view_line(view, view->lineno - view->offset);
2566         wnoutrefresh(view->win);
2567         report("");
2571 /*
2572  * Searching
2573  */
2575 static void search_view(struct view *view, enum request request);
2577 static bool
2578 grep_text(struct view *view, const char *text[])
2580         regmatch_t pmatch;
2581         size_t i;
2583         for (i = 0; text[i]; i++)
2584                 if (*text[i] &&
2585                     regexec(view->regex, text[i], 1, &pmatch, 0) != REG_NOMATCH)
2586                         return TRUE;
2587         return FALSE;
2590 static void
2591 select_view_line(struct view *view, unsigned long lineno)
2593         unsigned long old_lineno = view->lineno;
2594         unsigned long old_offset = view->offset;
2596         if (goto_view_line(view, view->offset, lineno)) {
2597                 if (view_is_displayed(view)) {
2598                         if (old_offset != view->offset) {
2599                                 redraw_view(view);
2600                         } else {
2601                                 draw_view_line(view, old_lineno - view->offset);
2602                                 draw_view_line(view, view->lineno - view->offset);
2603                                 wnoutrefresh(view->win);
2604                         }
2605                 } else {
2606                         view->ops->select(view, &view->line[view->lineno]);
2607                 }
2608         }
2611 static void
2612 find_next(struct view *view, enum request request)
2614         unsigned long lineno = view->lineno;
2615         int direction;
2617         if (!*view->grep) {
2618                 if (!*opt_search)
2619                         report("No previous search");
2620                 else
2621                         search_view(view, request);
2622                 return;
2623         }
2625         switch (request) {
2626         case REQ_SEARCH:
2627         case REQ_FIND_NEXT:
2628                 direction = 1;
2629                 break;
2631         case REQ_SEARCH_BACK:
2632         case REQ_FIND_PREV:
2633                 direction = -1;
2634                 break;
2636         default:
2637                 return;
2638         }
2640         if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
2641                 lineno += direction;
2643         /* Note, lineno is unsigned long so will wrap around in which case it
2644          * will become bigger than view->lines. */
2645         for (; lineno < view->lines; lineno += direction) {
2646                 if (view->ops->grep(view, &view->line[lineno])) {
2647                         select_view_line(view, lineno);
2648                         report("Line %ld matches '%s'", lineno + 1, view->grep);
2649                         return;
2650                 }
2651         }
2653         report("No match found for '%s'", view->grep);
2656 static void
2657 search_view(struct view *view, enum request request)
2659         int regex_err;
2661         if (view->regex) {
2662                 regfree(view->regex);
2663                 *view->grep = 0;
2664         } else {
2665                 view->regex = calloc(1, sizeof(*view->regex));
2666                 if (!view->regex)
2667                         return;
2668         }
2670         regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
2671         if (regex_err != 0) {
2672                 char buf[SIZEOF_STR] = "unknown error";
2674                 regerror(regex_err, view->regex, buf, sizeof(buf));
2675                 report("Search failed: %s", buf);
2676                 return;
2677         }
2679         string_copy(view->grep, opt_search);
2681         find_next(view, request);
2684 /*
2685  * Incremental updating
2686  */
2688 static void
2689 reset_view(struct view *view)
2691         int i;
2693         for (i = 0; i < view->lines; i++)
2694                 free(view->line[i].data);
2695         free(view->line);
2697         view->p_offset = view->offset;
2698         view->p_yoffset = view->yoffset;
2699         view->p_lineno = view->lineno;
2701         view->line = NULL;
2702         view->offset = 0;
2703         view->yoffset = 0;
2704         view->lines  = 0;
2705         view->lineno = 0;
2706         view->vid[0] = 0;
2707         view->update_secs = 0;
2710 static void
2711 free_argv(const char *argv[])
2713         int argc;
2715         for (argc = 0; argv[argc]; argc++)
2716                 free((void *) argv[argc]);
2719 static bool
2720 format_argv(const char *dst_argv[], const char *src_argv[], enum format_flags flags)
2722         char buf[SIZEOF_STR];
2723         int argc;
2724         bool noreplace = flags == FORMAT_NONE;
2726         free_argv(dst_argv);
2728         for (argc = 0; src_argv[argc]; argc++) {
2729                 const char *arg = src_argv[argc];
2730                 size_t bufpos = 0;
2732                 while (arg) {
2733                         char *next = strstr(arg, "%(");
2734                         int len = next - arg;
2735                         const char *value;
2737                         if (!next || noreplace) {
2738                                 if (flags == FORMAT_DASH && !strcmp(arg, "--"))
2739                                         noreplace = TRUE;
2740                                 len = strlen(arg);
2741                                 value = "";
2743                         } else if (!prefixcmp(next, "%(directory)")) {
2744                                 value = opt_path;
2746                         } else if (!prefixcmp(next, "%(file)")) {
2747                                 value = opt_file;
2749                         } else if (!prefixcmp(next, "%(ref)")) {
2750                                 value = *opt_ref ? opt_ref : "HEAD";
2752                         } else if (!prefixcmp(next, "%(head)")) {
2753                                 value = ref_head;
2755                         } else if (!prefixcmp(next, "%(commit)")) {
2756                                 value = ref_commit;
2758                         } else if (!prefixcmp(next, "%(blob)")) {
2759                                 value = ref_blob;
2761                         } else {
2762                                 report("Unknown replacement: `%s`", next);
2763                                 return FALSE;
2764                         }
2766                         if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
2767                                 return FALSE;
2769                         arg = next && !noreplace ? strchr(next, ')') + 1 : NULL;
2770                 }
2772                 dst_argv[argc] = strdup(buf);
2773                 if (!dst_argv[argc])
2774                         break;
2775         }
2777         dst_argv[argc] = NULL;
2779         return src_argv[argc] == NULL;
2782 static bool
2783 restore_view_position(struct view *view)
2785         if (!view->p_restore || (view->pipe && view->lines <= view->p_lineno))
2786                 return FALSE;
2788         /* Changing the view position cancels the restoring. */
2789         /* FIXME: Changing back to the first line is not detected. */
2790         if (view->offset != 0 || view->lineno != 0) {
2791                 view->p_restore = FALSE;
2792                 return FALSE;
2793         }
2795         if (goto_view_line(view, view->p_offset, view->p_lineno) &&
2796             view_is_displayed(view))
2797                 werase(view->win);
2799         view->yoffset = view->p_yoffset;
2800         view->p_restore = FALSE;
2802         return TRUE;
2805 static void
2806 end_update(struct view *view, bool force)
2808         if (!view->pipe)
2809                 return;
2810         while (!view->ops->read(view, NULL))
2811                 if (!force)
2812                         return;
2813         set_nonblocking_input(FALSE);
2814         if (force)
2815                 kill_io(view->pipe);
2816         done_io(view->pipe);
2817         view->pipe = NULL;
2820 static void
2821 setup_update(struct view *view, const char *vid)
2823         set_nonblocking_input(TRUE);
2824         reset_view(view);
2825         string_copy_rev(view->vid, vid);
2826         view->pipe = &view->io;
2827         view->start_time = time(NULL);
2830 static bool
2831 prepare_update(struct view *view, const char *argv[], const char *dir,
2832                enum format_flags flags)
2834         if (view->pipe)
2835                 end_update(view, TRUE);
2836         return init_io_rd(&view->io, argv, dir, flags);
2839 static bool
2840 prepare_update_file(struct view *view, const char *name)
2842         if (view->pipe)
2843                 end_update(view, TRUE);
2844         return io_open(&view->io, name);
2847 static bool
2848 begin_update(struct view *view, bool refresh)
2850         if (view->pipe)
2851                 end_update(view, TRUE);
2853         if (refresh) {
2854                 if (!start_io(&view->io))
2855                         return FALSE;
2857         } else {
2858                 if (view == VIEW(REQ_VIEW_TREE) && strcmp(view->vid, view->id))
2859                         opt_path[0] = 0;
2861                 if (!run_io_rd(&view->io, view->ops->argv, FORMAT_ALL))
2862                         return FALSE;
2864                 /* Put the current ref_* value to the view title ref
2865                  * member. This is needed by the blob view. Most other
2866                  * views sets it automatically after loading because the
2867                  * first line is a commit line. */
2868                 string_copy_rev(view->ref, view->id);
2869         }
2871         setup_update(view, view->id);
2873         return TRUE;
2876 static bool
2877 update_view(struct view *view)
2879         char out_buffer[BUFSIZ * 2];
2880         char *line;
2881         /* Clear the view and redraw everything since the tree sorting
2882          * might have rearranged things. */
2883         bool redraw = view->lines == 0;
2884         bool can_read = TRUE;
2886         if (!view->pipe)
2887                 return TRUE;
2889         if (!io_can_read(view->pipe)) {
2890                 if (view->lines == 0 && view_is_displayed(view)) {
2891                         time_t secs = time(NULL) - view->start_time;
2893                         if (secs > 1 && secs > view->update_secs) {
2894                                 if (view->update_secs == 0)
2895                                         redraw_view(view);
2896                                 update_view_title(view);
2897                                 view->update_secs = secs;
2898                         }
2899                 }
2900                 return TRUE;
2901         }
2903         for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
2904                 if (opt_iconv != ICONV_NONE) {
2905                         ICONV_CONST char *inbuf = line;
2906                         size_t inlen = strlen(line) + 1;
2908                         char *outbuf = out_buffer;
2909                         size_t outlen = sizeof(out_buffer);
2911                         size_t ret;
2913                         ret = iconv(opt_iconv, &inbuf, &inlen, &outbuf, &outlen);
2914                         if (ret != (size_t) -1)
2915                                 line = out_buffer;
2916                 }
2918                 if (!view->ops->read(view, line)) {
2919                         report("Allocation failure");
2920                         end_update(view, TRUE);
2921                         return FALSE;
2922                 }
2923         }
2925         {
2926                 unsigned long lines = view->lines;
2927                 int digits;
2929                 for (digits = 0; lines; digits++)
2930                         lines /= 10;
2932                 /* Keep the displayed view in sync with line number scaling. */
2933                 if (digits != view->digits) {
2934                         view->digits = digits;
2935                         if (opt_line_number || view == VIEW(REQ_VIEW_BLAME))
2936                                 redraw = TRUE;
2937                 }
2938         }
2940         if (io_error(view->pipe)) {
2941                 report("Failed to read: %s", io_strerror(view->pipe));
2942                 end_update(view, TRUE);
2944         } else if (io_eof(view->pipe)) {
2945                 report("");
2946                 end_update(view, FALSE);
2947         }
2949         if (restore_view_position(view))
2950                 redraw = TRUE;
2952         if (!view_is_displayed(view))
2953                 return TRUE;
2955         if (redraw)
2956                 redraw_view_from(view, 0);
2957         else
2958                 redraw_view_dirty(view);
2960         /* Update the title _after_ the redraw so that if the redraw picks up a
2961          * commit reference in view->ref it'll be available here. */
2962         update_view_title(view);
2963         return TRUE;
2966 DEFINE_ALLOCATOR(realloc_lines, struct line, 256)
2968 static struct line *
2969 add_line_data(struct view *view, void *data, enum line_type type)
2971         struct line *line;
2973         if (!realloc_lines(&view->line, view->lines, 1))
2974                 return NULL;
2976         line = &view->line[view->lines++];
2977         memset(line, 0, sizeof(*line));
2978         line->type = type;
2979         line->data = data;
2980         line->dirty = 1;
2982         return line;
2985 static struct line *
2986 add_line_text(struct view *view, const char *text, enum line_type type)
2988         char *data = text ? strdup(text) : NULL;
2990         return data ? add_line_data(view, data, type) : NULL;
2993 static struct line *
2994 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
2996         char buf[SIZEOF_STR];
2997         va_list args;
2999         va_start(args, fmt);
3000         if (vsnprintf(buf, sizeof(buf), fmt, args) >= sizeof(buf))
3001                 buf[0] = 0;
3002         va_end(args);
3004         return buf[0] ? add_line_text(view, buf, type) : NULL;
3007 /*
3008  * View opening
3009  */
3011 enum open_flags {
3012         OPEN_DEFAULT = 0,       /* Use default view switching. */
3013         OPEN_SPLIT = 1,         /* Split current view. */
3014         OPEN_RELOAD = 4,        /* Reload view even if it is the current. */
3015         OPEN_REFRESH = 16,      /* Refresh view using previous command. */
3016         OPEN_PREPARED = 32,     /* Open already prepared command. */
3017 };
3019 static void
3020 open_view(struct view *prev, enum request request, enum open_flags flags)
3022         bool split = !!(flags & OPEN_SPLIT);
3023         bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
3024         bool nomaximize = !!(flags & OPEN_REFRESH);
3025         struct view *view = VIEW(request);
3026         int nviews = displayed_views();
3027         struct view *base_view = display[0];
3029         if (view == prev && nviews == 1 && !reload) {
3030                 report("Already in %s view", view->name);
3031                 return;
3032         }
3034         if (view->git_dir && !opt_git_dir[0]) {
3035                 report("The %s view is disabled in pager view", view->name);
3036                 return;
3037         }
3039         if (split) {
3040                 display[1] = view;
3041                 current_view = 1;
3042         } else if (!nomaximize) {
3043                 /* Maximize the current view. */
3044                 memset(display, 0, sizeof(display));
3045                 current_view = 0;
3046                 display[current_view] = view;
3047         }
3049         /* Resize the view when switching between split- and full-screen,
3050          * or when switching between two different full-screen views. */
3051         if (nviews != displayed_views() ||
3052             (nviews == 1 && base_view != display[0]))
3053                 resize_display();
3055         if (view->ops->open) {
3056                 if (view->pipe)
3057                         end_update(view, TRUE);
3058                 if (!view->ops->open(view)) {
3059                         report("Failed to load %s view", view->name);
3060                         return;
3061                 }
3062                 restore_view_position(view);
3064         } else if ((reload || strcmp(view->vid, view->id)) &&
3065                    !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
3066                 report("Failed to load %s view", view->name);
3067                 return;
3068         }
3070         if (split && prev->lineno - prev->offset >= prev->height) {
3071                 /* Take the title line into account. */
3072                 int lines = prev->lineno - prev->offset - prev->height + 1;
3074                 /* Scroll the view that was split if the current line is
3075                  * outside the new limited view. */
3076                 do_scroll_view(prev, lines);
3077         }
3079         if (prev && view != prev) {
3080                 if (split) {
3081                         /* "Blur" the previous view. */
3082                         update_view_title(prev);
3083                 }
3085                 view->parent = prev;
3086         }
3088         if (view->pipe && view->lines == 0) {
3089                 /* Clear the old view and let the incremental updating refill
3090                  * the screen. */
3091                 werase(view->win);
3092                 view->p_restore = flags & (OPEN_RELOAD | OPEN_REFRESH);
3093                 report("");
3094         } else if (view_is_displayed(view)) {
3095                 redraw_view(view);
3096                 report("");
3097         }
3100 static void
3101 open_external_viewer(const char *argv[], const char *dir)
3103         def_prog_mode();           /* save current tty modes */
3104         endwin();                  /* restore original tty modes */
3105         run_io_fg(argv, dir);
3106         fprintf(stderr, "Press Enter to continue");
3107         getc(opt_tty);
3108         reset_prog_mode();
3109         redraw_display(TRUE);
3112 static void
3113 open_mergetool(const char *file)
3115         const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
3117         open_external_viewer(mergetool_argv, opt_cdup);
3120 static void
3121 open_editor(bool from_root, const char *file)
3123         const char *editor_argv[] = { "vi", file, NULL };
3124         const char *editor;
3126         editor = getenv("GIT_EDITOR");
3127         if (!editor && *opt_editor)
3128                 editor = opt_editor;
3129         if (!editor)
3130                 editor = getenv("VISUAL");
3131         if (!editor)
3132                 editor = getenv("EDITOR");
3133         if (!editor)
3134                 editor = "vi";
3136         editor_argv[0] = editor;
3137         open_external_viewer(editor_argv, from_root ? opt_cdup : NULL);
3140 static void
3141 open_run_request(enum request request)
3143         struct run_request *req = get_run_request(request);
3144         const char *argv[ARRAY_SIZE(req->argv)] = { NULL };
3146         if (!req) {
3147                 report("Unknown run request");
3148                 return;
3149         }
3151         if (format_argv(argv, req->argv, FORMAT_ALL))
3152                 open_external_viewer(argv, NULL);
3153         free_argv(argv);
3156 /*
3157  * User request switch noodle
3158  */
3160 static int
3161 view_driver(struct view *view, enum request request)
3163         int i;
3165         if (request == REQ_NONE)
3166                 return TRUE;
3168         if (request > REQ_NONE) {
3169                 open_run_request(request);
3170                 /* FIXME: When all views can refresh always do this. */
3171                 if (view == VIEW(REQ_VIEW_STATUS) ||
3172                     view == VIEW(REQ_VIEW_MAIN) ||
3173                     view == VIEW(REQ_VIEW_LOG) ||
3174                     view == VIEW(REQ_VIEW_BRANCH) ||
3175                     view == VIEW(REQ_VIEW_STAGE))
3176                         request = REQ_REFRESH;
3177                 else
3178                         return TRUE;
3179         }
3181         if (view && view->lines) {
3182                 request = view->ops->request(view, request, &view->line[view->lineno]);
3183                 if (request == REQ_NONE)
3184                         return TRUE;
3185         }
3187         switch (request) {
3188         case REQ_MOVE_UP:
3189         case REQ_MOVE_DOWN:
3190         case REQ_MOVE_PAGE_UP:
3191         case REQ_MOVE_PAGE_DOWN:
3192         case REQ_MOVE_FIRST_LINE:
3193         case REQ_MOVE_LAST_LINE:
3194                 move_view(view, request);
3195                 break;
3197         case REQ_SCROLL_LEFT:
3198         case REQ_SCROLL_RIGHT:
3199         case REQ_SCROLL_LINE_DOWN:
3200         case REQ_SCROLL_LINE_UP:
3201         case REQ_SCROLL_PAGE_DOWN:
3202         case REQ_SCROLL_PAGE_UP:
3203                 scroll_view(view, request);
3204                 break;
3206         case REQ_VIEW_BLAME:
3207                 if (!opt_file[0]) {
3208                         report("No file chosen, press %s to open tree view",
3209                                get_key(REQ_VIEW_TREE));
3210                         break;
3211                 }
3212                 open_view(view, request, OPEN_DEFAULT);
3213                 break;
3215         case REQ_VIEW_BLOB:
3216                 if (!ref_blob[0]) {
3217                         report("No file chosen, press %s to open tree view",
3218                                get_key(REQ_VIEW_TREE));
3219                         break;
3220                 }
3221                 open_view(view, request, OPEN_DEFAULT);
3222                 break;
3224         case REQ_VIEW_PAGER:
3225                 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3226                         report("No pager content, press %s to run command from prompt",
3227                                get_key(REQ_PROMPT));
3228                         break;
3229                 }
3230                 open_view(view, request, OPEN_DEFAULT);
3231                 break;
3233         case REQ_VIEW_STAGE:
3234                 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3235                         report("No stage content, press %s to open the status view and choose file",
3236                                get_key(REQ_VIEW_STATUS));
3237                         break;
3238                 }
3239                 open_view(view, request, OPEN_DEFAULT);
3240                 break;
3242         case REQ_VIEW_STATUS:
3243                 if (opt_is_inside_work_tree == FALSE) {
3244                         report("The status view requires a working tree");
3245                         break;
3246                 }
3247                 open_view(view, request, OPEN_DEFAULT);
3248                 break;
3250         case REQ_VIEW_MAIN:
3251         case REQ_VIEW_DIFF:
3252         case REQ_VIEW_LOG:
3253         case REQ_VIEW_TREE:
3254         case REQ_VIEW_HELP:
3255         case REQ_VIEW_BRANCH:
3256                 open_view(view, request, OPEN_DEFAULT);
3257                 break;
3259         case REQ_NEXT:
3260         case REQ_PREVIOUS:
3261                 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3263                 if ((view == VIEW(REQ_VIEW_DIFF) &&
3264                      view->parent == VIEW(REQ_VIEW_MAIN)) ||
3265                    (view == VIEW(REQ_VIEW_DIFF) &&
3266                      view->parent == VIEW(REQ_VIEW_BLAME)) ||
3267                    (view == VIEW(REQ_VIEW_STAGE) &&
3268                      view->parent == VIEW(REQ_VIEW_STATUS)) ||
3269                    (view == VIEW(REQ_VIEW_BLOB) &&
3270                      view->parent == VIEW(REQ_VIEW_TREE)) ||
3271                    (view == VIEW(REQ_VIEW_MAIN) &&
3272                      view->parent == VIEW(REQ_VIEW_BRANCH))) {
3273                         int line;
3275                         view = view->parent;
3276                         line = view->lineno;
3277                         move_view(view, request);
3278                         if (view_is_displayed(view))
3279                                 update_view_title(view);
3280                         if (line != view->lineno)
3281                                 view->ops->request(view, REQ_ENTER,
3282                                                    &view->line[view->lineno]);
3284                 } else {
3285                         move_view(view, request);
3286                 }
3287                 break;
3289         case REQ_VIEW_NEXT:
3290         {
3291                 int nviews = displayed_views();
3292                 int next_view = (current_view + 1) % nviews;
3294                 if (next_view == current_view) {
3295                         report("Only one view is displayed");
3296                         break;
3297                 }
3299                 current_view = next_view;
3300                 /* Blur out the title of the previous view. */
3301                 update_view_title(view);
3302                 report("");
3303                 break;
3304         }
3305         case REQ_REFRESH:
3306                 report("Refreshing is not yet supported for the %s view", view->name);
3307                 break;
3309         case REQ_MAXIMIZE:
3310                 if (displayed_views() == 2)
3311                         maximize_view(view);
3312                 break;
3314         case REQ_OPTIONS:
3315                 open_option_menu();
3316                 break;
3318         case REQ_TOGGLE_LINENO:
3319                 toggle_view_option(&opt_line_number, "line numbers");
3320                 break;
3322         case REQ_TOGGLE_DATE:
3323                 toggle_view_option(&opt_date, "date display");
3324                 break;
3326         case REQ_TOGGLE_AUTHOR:
3327                 toggle_view_option(&opt_author, "author display");
3328                 break;
3330         case REQ_TOGGLE_REV_GRAPH:
3331                 toggle_view_option(&opt_rev_graph, "revision graph display");
3332                 break;
3334         case REQ_TOGGLE_REFS:
3335                 toggle_view_option(&opt_show_refs, "reference display");
3336                 break;
3338         case REQ_TOGGLE_SORT_FIELD:
3339         case REQ_TOGGLE_SORT_ORDER:
3340                 report("Sorting is not yet supported for the %s view", view->name);
3341                 break;
3343         case REQ_SEARCH:
3344         case REQ_SEARCH_BACK:
3345                 search_view(view, request);
3346                 break;
3348         case REQ_FIND_NEXT:
3349         case REQ_FIND_PREV:
3350                 find_next(view, request);
3351                 break;
3353         case REQ_STOP_LOADING:
3354                 for (i = 0; i < ARRAY_SIZE(views); i++) {
3355                         view = &views[i];
3356                         if (view->pipe)
3357                                 report("Stopped loading the %s view", view->name),
3358                         end_update(view, TRUE);
3359                 }
3360                 break;
3362         case REQ_SHOW_VERSION:
3363                 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3364                 return TRUE;
3366         case REQ_SCREEN_REDRAW:
3367                 redraw_display(TRUE);
3368                 break;
3370         case REQ_EDIT:
3371                 report("Nothing to edit");
3372                 break;
3374         case REQ_ENTER:
3375                 report("Nothing to enter");
3376                 break;
3378         case REQ_VIEW_CLOSE:
3379                 /* XXX: Mark closed views by letting view->parent point to the
3380                  * view itself. Parents to closed view should never be
3381                  * followed. */
3382                 if (view->parent &&
3383                     view->parent->parent != view->parent) {
3384                         maximize_view(view->parent);
3385                         view->parent = view;
3386                         break;
3387                 }
3388                 /* Fall-through */
3389         case REQ_QUIT:
3390                 return FALSE;
3392         default:
3393                 report("Unknown key, press 'h' for help");
3394                 return TRUE;
3395         }
3397         return TRUE;
3401 /*
3402  * View backend utilities
3403  */
3405 enum sort_field {
3406         ORDERBY_NAME,
3407         ORDERBY_DATE,
3408         ORDERBY_AUTHOR,
3409 };
3411 struct sort_state {
3412         const enum sort_field *fields;
3413         size_t size, current;
3414         bool reverse;
3415 };
3417 #define SORT_STATE(fields) { fields, ARRAY_SIZE(fields), 0 }
3418 #define get_sort_field(state) ((state).fields[(state).current])
3419 #define sort_order(state, result) ((state).reverse ? -(result) : (result))
3421 static void
3422 sort_view(struct view *view, enum request request, struct sort_state *state,
3423           int (*compare)(const void *, const void *))
3425         switch (request) {
3426         case REQ_TOGGLE_SORT_FIELD:
3427                 state->current = (state->current + 1) % state->size;
3428                 break;
3430         case REQ_TOGGLE_SORT_ORDER:
3431                 state->reverse = !state->reverse;
3432                 break;
3433         default:
3434                 die("Not a sort request");
3435         }
3437         qsort(view->line, view->lines, sizeof(*view->line), compare);
3438         redraw_view(view);
3441 DEFINE_ALLOCATOR(realloc_authors, const char *, 256)
3443 /* Small author cache to reduce memory consumption. It uses binary
3444  * search to lookup or find place to position new entries. No entries
3445  * are ever freed. */
3446 static const char *
3447 get_author(const char *name)
3449         static const char **authors;
3450         static size_t authors_size;
3451         int from = 0, to = authors_size - 1;
3453         while (from <= to) {
3454                 size_t pos = (to + from) / 2;
3455                 int cmp = strcmp(name, authors[pos]);
3457                 if (!cmp)
3458                         return authors[pos];
3460                 if (cmp < 0)
3461                         to = pos - 1;
3462                 else
3463                         from = pos + 1;
3464         }
3466         if (!realloc_authors(&authors, authors_size, 1))
3467                 return NULL;
3468         name = strdup(name);
3469         if (!name)
3470                 return NULL;
3472         memmove(authors + from + 1, authors + from, (authors_size - from) * sizeof(*authors));
3473         authors[from] = name;
3474         authors_size++;
3476         return name;
3479 static void
3480 parse_timezone(time_t *time, const char *zone)
3482         long tz;
3484         tz  = ('0' - zone[1]) * 60 * 60 * 10;
3485         tz += ('0' - zone[2]) * 60 * 60;
3486         tz += ('0' - zone[3]) * 60;
3487         tz += ('0' - zone[4]);
3489         if (zone[0] == '-')
3490                 tz = -tz;
3492         *time -= tz;
3495 /* Parse author lines where the name may be empty:
3496  *      author  <email@address.tld> 1138474660 +0100
3497  */
3498 static void
3499 parse_author_line(char *ident, const char **author, time_t *time)
3501         char *nameend = strchr(ident, '<');
3502         char *emailend = strchr(ident, '>');
3504         if (nameend && emailend)
3505                 *nameend = *emailend = 0;
3506         ident = chomp_string(ident);
3507         if (!*ident) {
3508                 if (nameend)
3509                         ident = chomp_string(nameend + 1);
3510                 if (!*ident)
3511                         ident = "Unknown";
3512         }
3514         *author = get_author(ident);
3516         /* Parse epoch and timezone */
3517         if (emailend && emailend[1] == ' ') {
3518                 char *secs = emailend + 2;
3519                 char *zone = strchr(secs, ' ');
3521                 *time = (time_t) atol(secs);
3523                 if (zone && strlen(zone) == STRING_SIZE(" +0700"))
3524                         parse_timezone(time, zone + 1);
3525         }
3528 static bool
3529 open_commit_parent_menu(char buf[SIZEOF_STR], int *parents)
3531         char rev[SIZEOF_REV];
3532         const char *revlist_argv[] = {
3533                 "git", "log", "--no-color", "-1", "--pretty=format:%s", rev, NULL
3534         };
3535         struct menu_item *items;
3536         char text[SIZEOF_STR];
3537         bool ok = TRUE;
3538         int i;
3540         items = calloc(*parents + 1, sizeof(*items));
3541         if (!items)
3542                 return FALSE;
3544         for (i = 0; i < *parents; i++) {
3545                 string_copy_rev(rev, &buf[SIZEOF_REV * i]);
3546                 if (!run_io_buf(revlist_argv, text, sizeof(text)) ||
3547                     !(items[i].text = strdup(text))) {
3548                         ok = FALSE;
3549                         break;
3550                 }
3551         }
3553         if (ok) {
3554                 *parents = 0;
3555                 ok = prompt_menu("Select parent", items, parents);
3556         }
3557         for (i = 0; items[i].text; i++)
3558                 free((char *) items[i].text);
3559         free(items);
3560         return ok;
3563 static bool
3564 select_commit_parent(const char *id, char rev[SIZEOF_REV], const char *path)
3566         char buf[SIZEOF_STR * 4];
3567         const char *revlist_argv[] = {
3568                 "git", "log", "--no-color", "-1",
3569                         "--pretty=format:%P", id, "--", path, NULL
3570         };
3571         int parents;
3573         if (!run_io_buf(revlist_argv, buf, sizeof(buf)) ||
3574             (parents = strlen(buf) / 40) < 0) {
3575                 report("Failed to get parent information");
3576                 return FALSE;
3578         } else if (parents == 0) {
3579                 if (path)
3580                         report("Path '%s' does not exist in the parent", path);
3581                 else
3582                         report("The selected commit has no parents");
3583                 return FALSE;
3584         }
3586         if (parents > 1 && !open_commit_parent_menu(buf, &parents))
3587                 return FALSE;
3589         string_copy_rev(rev, &buf[41 * parents]);
3590         return TRUE;
3593 /*
3594  * Pager backend
3595  */
3597 static bool
3598 pager_draw(struct view *view, struct line *line, unsigned int lineno)
3600         char text[SIZEOF_STR];
3602         if (opt_line_number && draw_lineno(view, lineno))
3603                 return TRUE;
3605         string_expand(text, sizeof(text), line->data, opt_tab_size);
3606         draw_text(view, line->type, text, TRUE);
3607         return TRUE;
3610 static bool
3611 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
3613         const char *describe_argv[] = { "git", "describe", commit_id, NULL };
3614         char ref[SIZEOF_STR];
3616         if (!run_io_buf(describe_argv, ref, sizeof(ref)) || !*ref)
3617                 return TRUE;
3619         /* This is the only fatal call, since it can "corrupt" the buffer. */
3620         if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
3621                 return FALSE;
3623         return TRUE;
3626 static void
3627 add_pager_refs(struct view *view, struct line *line)
3629         char buf[SIZEOF_STR];
3630         char *commit_id = (char *)line->data + STRING_SIZE("commit ");
3631         struct ref_list *list;
3632         size_t bufpos = 0, i;
3633         const char *sep = "Refs: ";
3634         bool is_tag = FALSE;
3636         assert(line->type == LINE_COMMIT);
3638         list = get_ref_list(commit_id);
3639         if (!list) {
3640                 if (view == VIEW(REQ_VIEW_DIFF))
3641                         goto try_add_describe_ref;
3642                 return;
3643         }
3645         for (i = 0; i < list->size; i++) {
3646                 struct ref *ref = list->refs[i];
3647                 const char *fmt = ref->tag    ? "%s[%s]" :
3648                                   ref->remote ? "%s<%s>" : "%s%s";
3650                 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
3651                         return;
3652                 sep = ", ";
3653                 if (ref->tag)
3654                         is_tag = TRUE;
3655         }
3657         if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
3658 try_add_describe_ref:
3659                 /* Add <tag>-g<commit_id> "fake" reference. */
3660                 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
3661                         return;
3662         }
3664         if (bufpos == 0)
3665                 return;
3667         add_line_text(view, buf, LINE_PP_REFS);
3670 static bool
3671 pager_read(struct view *view, char *data)
3673         struct line *line;
3675         if (!data)
3676                 return TRUE;
3678         line = add_line_text(view, data, get_line_type(data));
3679         if (!line)
3680                 return FALSE;
3682         if (line->type == LINE_COMMIT &&
3683             (view == VIEW(REQ_VIEW_DIFF) ||
3684              view == VIEW(REQ_VIEW_LOG)))
3685                 add_pager_refs(view, line);
3687         return TRUE;
3690 static enum request
3691 pager_request(struct view *view, enum request request, struct line *line)
3693         int split = 0;
3695         if (request != REQ_ENTER)
3696                 return request;
3698         if (line->type == LINE_COMMIT &&
3699            (view == VIEW(REQ_VIEW_LOG) ||
3700             view == VIEW(REQ_VIEW_PAGER))) {
3701                 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
3702                 split = 1;
3703         }
3705         /* Always scroll the view even if it was split. That way
3706          * you can use Enter to scroll through the log view and
3707          * split open each commit diff. */
3708         scroll_view(view, REQ_SCROLL_LINE_DOWN);
3710         /* FIXME: A minor workaround. Scrolling the view will call report("")
3711          * but if we are scrolling a non-current view this won't properly
3712          * update the view title. */
3713         if (split)
3714                 update_view_title(view);
3716         return REQ_NONE;
3719 static bool
3720 pager_grep(struct view *view, struct line *line)
3722         const char *text[] = { line->data, NULL };
3724         return grep_text(view, text);
3727 static void
3728 pager_select(struct view *view, struct line *line)
3730         if (line->type == LINE_COMMIT) {
3731                 char *text = (char *)line->data + STRING_SIZE("commit ");
3733                 if (view != VIEW(REQ_VIEW_PAGER))
3734                         string_copy_rev(view->ref, text);
3735                 string_copy_rev(ref_commit, text);
3736         }
3739 static struct view_ops pager_ops = {
3740         "line",
3741         NULL,
3742         NULL,
3743         pager_read,
3744         pager_draw,
3745         pager_request,
3746         pager_grep,
3747         pager_select,
3748 };
3750 static const char *log_argv[SIZEOF_ARG] = {
3751         "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
3752 };
3754 static enum request
3755 log_request(struct view *view, enum request request, struct line *line)
3757         switch (request) {
3758         case REQ_REFRESH:
3759                 load_refs();
3760                 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
3761                 return REQ_NONE;
3762         default:
3763                 return pager_request(view, request, line);
3764         }
3767 static struct view_ops log_ops = {
3768         "line",
3769         log_argv,
3770         NULL,
3771         pager_read,
3772         pager_draw,
3773         log_request,
3774         pager_grep,
3775         pager_select,
3776 };
3778 static const char *diff_argv[SIZEOF_ARG] = {
3779         "git", "show", "--pretty=fuller", "--no-color", "--root",
3780                 "--patch-with-stat", "--find-copies-harder", "-C", "%(commit)", NULL
3781 };
3783 static struct view_ops diff_ops = {
3784         "line",
3785         diff_argv,
3786         NULL,
3787         pager_read,
3788         pager_draw,
3789         pager_request,
3790         pager_grep,
3791         pager_select,
3792 };
3794 /*
3795  * Help backend
3796  */
3798 static bool
3799 help_open(struct view *view)
3801         char buf[SIZEOF_STR];
3802         size_t bufpos;
3803         int i;
3805         if (view->lines > 0)
3806                 return TRUE;
3808         add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
3810         for (i = 0; i < ARRAY_SIZE(req_info); i++) {
3811                 const char *key;
3813                 if (req_info[i].request == REQ_NONE)
3814                         continue;
3816                 if (!req_info[i].request) {
3817                         add_line_text(view, "", LINE_DEFAULT);
3818                         add_line_text(view, req_info[i].help, LINE_DEFAULT);
3819                         continue;
3820                 }
3822                 key = get_key(req_info[i].request);
3823                 if (!*key)
3824                         key = "(no key defined)";
3826                 for (bufpos = 0; bufpos <= req_info[i].namelen; bufpos++) {
3827                         buf[bufpos] = tolower(req_info[i].name[bufpos]);
3828                         if (buf[bufpos] == '_')
3829                                 buf[bufpos] = '-';
3830                 }
3832                 add_line_format(view, LINE_DEFAULT, "    %-25s %-20s %s",
3833                                 key, buf, req_info[i].help);
3834         }
3836         if (run_requests) {
3837                 add_line_text(view, "", LINE_DEFAULT);
3838                 add_line_text(view, "External commands:", LINE_DEFAULT);
3839         }
3841         for (i = 0; i < run_requests; i++) {
3842                 struct run_request *req = get_run_request(REQ_NONE + i + 1);
3843                 const char *key;
3844                 int argc;
3846                 if (!req)
3847                         continue;
3849                 key = get_key_name(req->key);
3850                 if (!*key)
3851                         key = "(no key defined)";
3853                 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
3854                         if (!string_format_from(buf, &bufpos, "%s%s",
3855                                                 argc ? " " : "", req->argv[argc]))
3856                                 return REQ_NONE;
3858                 add_line_format(view, LINE_DEFAULT, "    %-10s %-14s `%s`",
3859                                 keymap_table[req->keymap].name, key, buf);
3860         }
3862         return TRUE;
3865 static struct view_ops help_ops = {
3866         "line",
3867         NULL,
3868         help_open,
3869         NULL,
3870         pager_draw,
3871         pager_request,
3872         pager_grep,
3873         pager_select,
3874 };
3877 /*
3878  * Tree backend
3879  */
3881 struct tree_stack_entry {
3882         struct tree_stack_entry *prev;  /* Entry below this in the stack */
3883         unsigned long lineno;           /* Line number to restore */
3884         char *name;                     /* Position of name in opt_path */
3885 };
3887 /* The top of the path stack. */
3888 static struct tree_stack_entry *tree_stack = NULL;
3889 unsigned long tree_lineno = 0;
3891 static void
3892 pop_tree_stack_entry(void)
3894         struct tree_stack_entry *entry = tree_stack;
3896         tree_lineno = entry->lineno;
3897         entry->name[0] = 0;
3898         tree_stack = entry->prev;
3899         free(entry);
3902 static void
3903 push_tree_stack_entry(const char *name, unsigned long lineno)
3905         struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
3906         size_t pathlen = strlen(opt_path);
3908         if (!entry)
3909                 return;
3911         entry->prev = tree_stack;
3912         entry->name = opt_path + pathlen;
3913         tree_stack = entry;
3915         if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
3916                 pop_tree_stack_entry();
3917                 return;
3918         }
3920         /* Move the current line to the first tree entry. */
3921         tree_lineno = 1;
3922         entry->lineno = lineno;
3925 /* Parse output from git-ls-tree(1):
3926  *
3927  * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
3928  */
3930 #define SIZEOF_TREE_ATTR \
3931         STRING_SIZE("100644 blob f931e1d229c3e185caad4449bf5b66ed72462657\t")
3933 #define SIZEOF_TREE_MODE \
3934         STRING_SIZE("100644 ")
3936 #define TREE_ID_OFFSET \
3937         STRING_SIZE("100644 blob ")
3939 struct tree_entry {
3940         char id[SIZEOF_REV];
3941         mode_t mode;
3942         time_t time;                    /* Date from the author ident. */
3943         const char *author;             /* Author of the commit. */
3944         char name[1];
3945 };
3947 static const char *
3948 tree_path(const struct line *line)
3950         return ((struct tree_entry *) line->data)->name;
3953 static int
3954 tree_compare_entry(const struct line *line1, const struct line *line2)
3956         if (line1->type != line2->type)
3957                 return line1->type == LINE_TREE_DIR ? -1 : 1;
3958         return strcmp(tree_path(line1), tree_path(line2));
3961 static const enum sort_field tree_sort_fields[] = {
3962         ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
3963 };
3964 static struct sort_state tree_sort_state = SORT_STATE(tree_sort_fields);
3966 static int
3967 tree_compare(const void *l1, const void *l2)
3969         const struct line *line1 = (const struct line *) l1;
3970         const struct line *line2 = (const struct line *) l2;
3971         const struct tree_entry *entry1 = ((const struct line *) l1)->data;
3972         const struct tree_entry *entry2 = ((const struct line *) l2)->data;
3974         if (line1->type == LINE_TREE_HEAD)
3975                 return -1;
3976         if (line2->type == LINE_TREE_HEAD)
3977                 return 1;
3979         switch (get_sort_field(tree_sort_state)) {
3980         case ORDERBY_DATE:
3981                 return sort_order(tree_sort_state, entry1->time - entry2->time);
3983         case ORDERBY_AUTHOR:
3984                 return sort_order(tree_sort_state, strcmp(entry1->author, entry2->author));
3986         case ORDERBY_NAME:
3987         default:
3988                 return sort_order(tree_sort_state, tree_compare_entry(line1, line2));
3989         }
3993 static struct line *
3994 tree_entry(struct view *view, enum line_type type, const char *path,
3995            const char *mode, const char *id)
3997         struct tree_entry *entry = calloc(1, sizeof(*entry) + strlen(path));
3998         struct line *line = entry ? add_line_data(view, entry, type) : NULL;
4000         if (!entry || !line) {
4001                 free(entry);
4002                 return NULL;
4003         }
4005         strncpy(entry->name, path, strlen(path));
4006         if (mode)
4007                 entry->mode = strtoul(mode, NULL, 8);
4008         if (id)
4009                 string_copy_rev(entry->id, id);
4011         return line;
4014 static bool
4015 tree_read_date(struct view *view, char *text, bool *read_date)
4017         static const char *author_name;
4018         static time_t author_time;
4020         if (!text && *read_date) {
4021                 *read_date = FALSE;
4022                 return TRUE;
4024         } else if (!text) {
4025                 char *path = *opt_path ? opt_path : ".";
4026                 /* Find next entry to process */
4027                 const char *log_file[] = {
4028                         "git", "log", "--no-color", "--pretty=raw",
4029                                 "--cc", "--raw", view->id, "--", path, NULL
4030                 };
4031                 struct io io = {};
4033                 if (!view->lines) {
4034                         tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL);
4035                         report("Tree is empty");
4036                         return TRUE;
4037                 }
4039                 if (!run_io_rd(&io, log_file, FORMAT_NONE)) {
4040                         report("Failed to load tree data");
4041                         return TRUE;
4042                 }
4044                 done_io(view->pipe);
4045                 view->io = io;
4046                 *read_date = TRUE;
4047                 return FALSE;
4049         } else if (*text == 'a' && get_line_type(text) == LINE_AUTHOR) {
4050                 parse_author_line(text + STRING_SIZE("author "),
4051                                   &author_name, &author_time);
4053         } else if (*text == ':') {
4054                 char *pos;
4055                 size_t annotated = 1;
4056                 size_t i;
4058                 pos = strchr(text, '\t');
4059                 if (!pos)
4060                         return TRUE;
4061                 text = pos + 1;
4062                 if (*opt_prefix && !strncmp(text, opt_prefix, strlen(opt_prefix)))
4063                         text += strlen(opt_prefix);
4064                 if (*opt_path && !strncmp(text, opt_path, strlen(opt_path)))
4065                         text += strlen(opt_path);
4066                 pos = strchr(text, '/');
4067                 if (pos)
4068                         *pos = 0;
4070                 for (i = 1; i < view->lines; i++) {
4071                         struct line *line = &view->line[i];
4072                         struct tree_entry *entry = line->data;
4074                         annotated += !!entry->author;
4075                         if (entry->author || strcmp(entry->name, text))
4076                                 continue;
4078                         entry->author = author_name;
4079                         entry->time = author_time;
4080                         line->dirty = 1;
4081                         break;
4082                 }
4084                 if (annotated == view->lines)
4085                         kill_io(view->pipe);
4086         }
4087         return TRUE;
4090 static bool
4091 tree_read(struct view *view, char *text)
4093         static bool read_date = FALSE;
4094         struct tree_entry *data;
4095         struct line *entry, *line;
4096         enum line_type type;
4097         size_t textlen = text ? strlen(text) : 0;
4098         char *path = text + SIZEOF_TREE_ATTR;
4100         if (read_date || !text)
4101                 return tree_read_date(view, text, &read_date);
4103         if (textlen <= SIZEOF_TREE_ATTR)
4104                 return FALSE;
4105         if (view->lines == 0 &&
4106             !tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL))
4107                 return FALSE;
4109         /* Strip the path part ... */
4110         if (*opt_path) {
4111                 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
4112                 size_t striplen = strlen(opt_path);
4114                 if (pathlen > striplen)
4115                         memmove(path, path + striplen,
4116                                 pathlen - striplen + 1);
4118                 /* Insert "link" to parent directory. */
4119                 if (view->lines == 1 &&
4120                     !tree_entry(view, LINE_TREE_DIR, "..", "040000", view->ref))
4121                         return FALSE;
4122         }
4124         type = text[SIZEOF_TREE_MODE] == 't' ? LINE_TREE_DIR : LINE_TREE_FILE;
4125         entry = tree_entry(view, type, path, text, text + TREE_ID_OFFSET);
4126         if (!entry)
4127                 return FALSE;
4128         data = entry->data;
4130         /* Skip "Directory ..." and ".." line. */
4131         for (line = &view->line[1 + !!*opt_path]; line < entry; line++) {
4132                 if (tree_compare_entry(line, entry) <= 0)
4133                         continue;
4135                 memmove(line + 1, line, (entry - line) * sizeof(*entry));
4137                 line->data = data;
4138                 line->type = type;
4139                 for (; line <= entry; line++)
4140                         line->dirty = line->cleareol = 1;
4141                 return TRUE;
4142         }
4144         if (tree_lineno > view->lineno) {
4145                 view->lineno = tree_lineno;
4146                 tree_lineno = 0;
4147         }
4149         return TRUE;
4152 static bool
4153 tree_draw(struct view *view, struct line *line, unsigned int lineno)
4155         struct tree_entry *entry = line->data;
4157         if (line->type == LINE_TREE_HEAD) {
4158                 if (draw_text(view, line->type, "Directory path /", TRUE))
4159                         return TRUE;
4160         } else {
4161                 if (draw_mode(view, entry->mode))
4162                         return TRUE;
4164                 if (opt_author && draw_author(view, entry->author))
4165                         return TRUE;
4167                 if (opt_date && draw_date(view, entry->author ? &entry->time : NULL))
4168                         return TRUE;
4169         }
4170         if (draw_text(view, line->type, entry->name, TRUE))
4171                 return TRUE;
4172         return TRUE;
4175 static void
4176 open_blob_editor()
4178         char file[SIZEOF_STR] = "/tmp/tigblob.XXXXXX";
4179         int fd = mkstemp(file);
4181         if (fd == -1)
4182                 report("Failed to create temporary file");
4183         else if (!run_io_append(blob_ops.argv, FORMAT_ALL, fd))
4184                 report("Failed to save blob data to file");
4185         else
4186                 open_editor(FALSE, file);
4187         if (fd != -1)
4188                 unlink(file);
4191 static enum request
4192 tree_request(struct view *view, enum request request, struct line *line)
4194         enum open_flags flags;
4196         switch (request) {
4197         case REQ_VIEW_BLAME:
4198                 if (line->type != LINE_TREE_FILE) {
4199                         report("Blame only supported for files");
4200                         return REQ_NONE;
4201                 }
4203                 string_copy(opt_ref, view->vid);
4204                 return request;
4206         case REQ_EDIT:
4207                 if (line->type != LINE_TREE_FILE) {
4208                         report("Edit only supported for files");
4209                 } else if (!is_head_commit(view->vid)) {
4210                         open_blob_editor();
4211                 } else {
4212                         open_editor(TRUE, opt_file);
4213                 }
4214                 return REQ_NONE;
4216         case REQ_TOGGLE_SORT_FIELD:
4217         case REQ_TOGGLE_SORT_ORDER:
4218                 sort_view(view, request, &tree_sort_state, tree_compare);
4219                 return REQ_NONE;
4221         case REQ_PARENT:
4222                 if (!*opt_path) {
4223                         /* quit view if at top of tree */
4224                         return REQ_VIEW_CLOSE;
4225                 }
4226                 /* fake 'cd  ..' */
4227                 line = &view->line[1];
4228                 break;
4230         case REQ_ENTER:
4231                 break;
4233         default:
4234                 return request;
4235         }
4237         /* Cleanup the stack if the tree view is at a different tree. */
4238         while (!*opt_path && tree_stack)
4239                 pop_tree_stack_entry();
4241         switch (line->type) {
4242         case LINE_TREE_DIR:
4243                 /* Depending on whether it is a subdirectory or parent link
4244                  * mangle the path buffer. */
4245                 if (line == &view->line[1] && *opt_path) {
4246                         pop_tree_stack_entry();
4248                 } else {
4249                         const char *basename = tree_path(line);
4251                         push_tree_stack_entry(basename, view->lineno);
4252                 }
4254                 /* Trees and subtrees share the same ID, so they are not not
4255                  * unique like blobs. */
4256                 flags = OPEN_RELOAD;
4257                 request = REQ_VIEW_TREE;
4258                 break;
4260         case LINE_TREE_FILE:
4261                 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4262                 request = REQ_VIEW_BLOB;
4263                 break;
4265         default:
4266                 return REQ_NONE;
4267         }
4269         open_view(view, request, flags);
4270         if (request == REQ_VIEW_TREE)
4271                 view->lineno = tree_lineno;
4273         return REQ_NONE;
4276 static bool
4277 tree_grep(struct view *view, struct line *line)
4279         struct tree_entry *entry = line->data;
4280         const char *text[] = {
4281                 entry->name,
4282                 opt_author ? entry->author : "",
4283                 opt_date ? mkdate(&entry->time) : "",
4284                 NULL
4285         };
4287         return grep_text(view, text);
4290 static void
4291 tree_select(struct view *view, struct line *line)
4293         struct tree_entry *entry = line->data;
4295         if (line->type == LINE_TREE_FILE) {
4296                 string_copy_rev(ref_blob, entry->id);
4297                 string_format(opt_file, "%s%s", opt_path, tree_path(line));
4299         } else if (line->type != LINE_TREE_DIR) {
4300                 return;
4301         }
4303         string_copy_rev(view->ref, entry->id);
4306 static const char *tree_argv[SIZEOF_ARG] = {
4307         "git", "ls-tree", "%(commit)", "%(directory)", NULL
4308 };
4310 static struct view_ops tree_ops = {
4311         "file",
4312         tree_argv,
4313         NULL,
4314         tree_read,
4315         tree_draw,
4316         tree_request,
4317         tree_grep,
4318         tree_select,
4319 };
4321 static bool
4322 blob_read(struct view *view, char *line)
4324         if (!line)
4325                 return TRUE;
4326         return add_line_text(view, line, LINE_DEFAULT) != NULL;
4329 static enum request
4330 blob_request(struct view *view, enum request request, struct line *line)
4332         switch (request) {
4333         case REQ_EDIT:
4334                 open_blob_editor();
4335                 return REQ_NONE;
4336         default:
4337                 return pager_request(view, request, line);
4338         }
4341 static const char *blob_argv[SIZEOF_ARG] = {
4342         "git", "cat-file", "blob", "%(blob)", NULL
4343 };
4345 static struct view_ops blob_ops = {
4346         "line",
4347         blob_argv,
4348         NULL,
4349         blob_read,
4350         pager_draw,
4351         blob_request,
4352         pager_grep,
4353         pager_select,
4354 };
4356 /*
4357  * Blame backend
4358  *
4359  * Loading the blame view is a two phase job:
4360  *
4361  *  1. File content is read either using opt_file from the
4362  *     filesystem or using git-cat-file.
4363  *  2. Then blame information is incrementally added by
4364  *     reading output from git-blame.
4365  */
4367 static const char *blame_head_argv[] = {
4368         "git", "blame", "--incremental", "--", "%(file)", NULL
4369 };
4371 static const char *blame_ref_argv[] = {
4372         "git", "blame", "--incremental", "%(ref)", "--", "%(file)", NULL
4373 };
4375 static const char *blame_cat_file_argv[] = {
4376         "git", "cat-file", "blob", "%(ref):%(file)", NULL
4377 };
4379 struct blame_commit {
4380         char id[SIZEOF_REV];            /* SHA1 ID. */
4381         char title[128];                /* First line of the commit message. */
4382         const char *author;             /* Author of the commit. */
4383         time_t time;                    /* Date from the author ident. */
4384         char filename[128];             /* Name of file. */
4385         bool has_previous;              /* Was a "previous" line detected. */
4386 };
4388 struct blame {
4389         struct blame_commit *commit;
4390         unsigned long lineno;
4391         char text[1];
4392 };
4394 static bool
4395 blame_open(struct view *view)
4397         if (*opt_ref || !io_open(&view->io, opt_file)) {
4398                 if (!run_io_rd(&view->io, blame_cat_file_argv, FORMAT_ALL))
4399                         return FALSE;
4400         }
4402         setup_update(view, opt_file);
4403         string_format(view->ref, "%s ...", opt_file);
4405         return TRUE;
4408 static struct blame_commit *
4409 get_blame_commit(struct view *view, const char *id)
4411         size_t i;
4413         for (i = 0; i < view->lines; i++) {
4414                 struct blame *blame = view->line[i].data;
4416                 if (!blame->commit)
4417                         continue;
4419                 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
4420                         return blame->commit;
4421         }
4423         {
4424                 struct blame_commit *commit = calloc(1, sizeof(*commit));
4426                 if (commit)
4427                         string_ncopy(commit->id, id, SIZEOF_REV);
4428                 return commit;
4429         }
4432 static bool
4433 parse_number(const char **posref, size_t *number, size_t min, size_t max)
4435         const char *pos = *posref;
4437         *posref = NULL;
4438         pos = strchr(pos + 1, ' ');
4439         if (!pos || !isdigit(pos[1]))
4440                 return FALSE;
4441         *number = atoi(pos + 1);
4442         if (*number < min || *number > max)
4443                 return FALSE;
4445         *posref = pos;
4446         return TRUE;
4449 static struct blame_commit *
4450 parse_blame_commit(struct view *view, const char *text, int *blamed)
4452         struct blame_commit *commit;
4453         struct blame *blame;
4454         const char *pos = text + SIZEOF_REV - 2;
4455         size_t orig_lineno = 0;
4456         size_t lineno;
4457         size_t group;
4459         if (strlen(text) <= SIZEOF_REV || pos[1] != ' ')
4460                 return NULL;
4462         if (!parse_number(&pos, &orig_lineno, 1, 9999999) ||
4463             !parse_number(&pos, &lineno, 1, view->lines) ||
4464             !parse_number(&pos, &group, 1, view->lines - lineno + 1))
4465                 return NULL;
4467         commit = get_blame_commit(view, text);
4468         if (!commit)
4469                 return NULL;
4471         *blamed += group;
4472         while (group--) {
4473                 struct line *line = &view->line[lineno + group - 1];
4475                 blame = line->data;
4476                 blame->commit = commit;
4477                 blame->lineno = orig_lineno + group - 1;
4478                 line->dirty = 1;
4479         }
4481         return commit;
4484 static bool
4485 blame_read_file(struct view *view, const char *line, bool *read_file)
4487         if (!line) {
4488                 const char **argv = *opt_ref ? blame_ref_argv : blame_head_argv;
4489                 struct io io = {};
4491                 if (view->lines == 0 && !view->parent)
4492                         die("No blame exist for %s", view->vid);
4494                 if (view->lines == 0 || !run_io_rd(&io, argv, FORMAT_ALL)) {
4495                         report("Failed to load blame data");
4496                         return TRUE;
4497                 }
4499                 done_io(view->pipe);
4500                 view->io = io;
4501                 *read_file = FALSE;
4502                 return FALSE;
4504         } else {
4505                 size_t linelen = strlen(line);
4506                 struct blame *blame = malloc(sizeof(*blame) + linelen);
4508                 if (!blame)
4509                         return FALSE;
4511                 blame->commit = NULL;
4512                 strncpy(blame->text, line, linelen);
4513                 blame->text[linelen] = 0;
4514                 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
4515         }
4518 static bool
4519 match_blame_header(const char *name, char **line)
4521         size_t namelen = strlen(name);
4522         bool matched = !strncmp(name, *line, namelen);
4524         if (matched)
4525                 *line += namelen;
4527         return matched;
4530 static bool
4531 blame_read(struct view *view, char *line)
4533         static struct blame_commit *commit = NULL;
4534         static int blamed = 0;
4535         static bool read_file = TRUE;
4537         if (read_file)
4538                 return blame_read_file(view, line, &read_file);
4540         if (!line) {
4541                 /* Reset all! */
4542                 commit = NULL;
4543                 blamed = 0;
4544                 read_file = TRUE;
4545                 string_format(view->ref, "%s", view->vid);
4546                 if (view_is_displayed(view)) {
4547                         update_view_title(view);
4548                         redraw_view_from(view, 0);
4549                 }
4550                 return TRUE;
4551         }
4553         if (!commit) {
4554                 commit = parse_blame_commit(view, line, &blamed);
4555                 string_format(view->ref, "%s %2d%%", view->vid,
4556                               view->lines ? blamed * 100 / view->lines : 0);
4558         } else if (match_blame_header("author ", &line)) {
4559                 commit->author = get_author(line);
4561         } else if (match_blame_header("author-time ", &line)) {
4562                 commit->time = (time_t) atol(line);
4564         } else if (match_blame_header("author-tz ", &line)) {
4565                 parse_timezone(&commit->time, line);
4567         } else if (match_blame_header("summary ", &line)) {
4568                 string_ncopy(commit->title, line, strlen(line));
4570         } else if (match_blame_header("previous ", &line)) {
4571                 commit->has_previous = TRUE;
4573         } else if (match_blame_header("filename ", &line)) {
4574                 string_ncopy(commit->filename, line, strlen(line));
4575                 commit = NULL;
4576         }
4578         return TRUE;
4581 static bool
4582 blame_draw(struct view *view, struct line *line, unsigned int lineno)
4584         struct blame *blame = line->data;
4585         time_t *time = NULL;
4586         const char *id = NULL, *author = NULL;
4587         char text[SIZEOF_STR];
4589         if (blame->commit && *blame->commit->filename) {
4590                 id = blame->commit->id;
4591                 author = blame->commit->author;
4592                 time = &blame->commit->time;
4593         }
4595         if (opt_date && draw_date(view, time))
4596                 return TRUE;
4598         if (opt_author && draw_author(view, author))
4599                 return TRUE;
4601         if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
4602                 return TRUE;
4604         if (draw_lineno(view, lineno))
4605                 return TRUE;
4607         string_expand(text, sizeof(text), blame->text, opt_tab_size);
4608         draw_text(view, LINE_DEFAULT, text, TRUE);
4609         return TRUE;
4612 static bool
4613 check_blame_commit(struct blame *blame, bool check_null_id)
4615         if (!blame->commit)
4616                 report("Commit data not loaded yet");
4617         else if (check_null_id && !strcmp(blame->commit->id, NULL_ID))
4618                 report("No commit exist for the selected line");
4619         else
4620                 return TRUE;
4621         return FALSE;
4624 static void
4625 setup_blame_parent_line(struct view *view, struct blame *blame)
4627         const char *diff_tree_argv[] = {
4628                 "git", "diff-tree", "-U0", blame->commit->id,
4629                         "--", blame->commit->filename, NULL
4630         };
4631         struct io io = {};
4632         int parent_lineno = -1;
4633         int blamed_lineno = -1;
4634         char *line;
4636         if (!run_io(&io, diff_tree_argv, NULL, IO_RD))
4637                 return;
4639         while ((line = io_get(&io, '\n', TRUE))) {
4640                 if (*line == '@') {
4641                         char *pos = strchr(line, '+');
4643                         parent_lineno = atoi(line + 4);
4644                         if (pos)
4645                                 blamed_lineno = atoi(pos + 1);
4647                 } else if (*line == '+' && parent_lineno != -1) {
4648                         if (blame->lineno == blamed_lineno - 1 &&
4649                             !strcmp(blame->text, line + 1)) {
4650                                 view->lineno = parent_lineno ? parent_lineno - 1 : 0;
4651                                 break;
4652                         }
4653                         blamed_lineno++;
4654                 }
4655         }
4657         done_io(&io);
4660 static enum request
4661 blame_request(struct view *view, enum request request, struct line *line)
4663         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4664         struct blame *blame = line->data;
4666         switch (request) {
4667         case REQ_VIEW_BLAME:
4668                 if (check_blame_commit(blame, TRUE)) {
4669                         string_copy(opt_ref, blame->commit->id);
4670                         string_copy(opt_file, blame->commit->filename);
4671                         if (blame->lineno)
4672                                 view->lineno = blame->lineno;
4673                         open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4674                 }
4675                 break;
4677         case REQ_PARENT:
4678                 if (check_blame_commit(blame, TRUE) &&
4679                     select_commit_parent(blame->commit->id, opt_ref,
4680                                          blame->commit->filename)) {
4681                         string_copy(opt_file, blame->commit->filename);
4682                         setup_blame_parent_line(view, blame);
4683                         open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4684                 }
4685                 break;
4687         case REQ_ENTER:
4688                 if (!check_blame_commit(blame, FALSE))
4689                         break;
4691                 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
4692                     !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
4693                         break;
4695                 if (!strcmp(blame->commit->id, NULL_ID)) {
4696                         struct view *diff = VIEW(REQ_VIEW_DIFF);
4697                         const char *diff_index_argv[] = {
4698                                 "git", "diff-index", "--root", "--patch-with-stat",
4699                                         "-C", "-M", "HEAD", "--", view->vid, NULL
4700                         };
4702                         if (!blame->commit->has_previous) {
4703                                 diff_index_argv[1] = "diff";
4704                                 diff_index_argv[2] = "--no-color";
4705                                 diff_index_argv[6] = "--";
4706                                 diff_index_argv[7] = "/dev/null";
4707                         }
4709                         if (!prepare_update(diff, diff_index_argv, NULL, FORMAT_DASH)) {
4710                                 report("Failed to allocate diff command");
4711                                 break;
4712                         }
4713                         flags |= OPEN_PREPARED;
4714                 }
4716                 open_view(view, REQ_VIEW_DIFF, flags);
4717                 if (VIEW(REQ_VIEW_DIFF)->pipe && !strcmp(blame->commit->id, NULL_ID))
4718                         string_copy_rev(VIEW(REQ_VIEW_DIFF)->ref, NULL_ID);
4719                 break;
4721         default:
4722                 return request;
4723         }
4725         return REQ_NONE;
4728 static bool
4729 blame_grep(struct view *view, struct line *line)
4731         struct blame *blame = line->data;
4732         struct blame_commit *commit = blame->commit;
4733         const char *text[] = {
4734                 blame->text,
4735                 commit ? commit->title : "",
4736                 commit ? commit->id : "",
4737                 commit && opt_author ? commit->author : "",
4738                 commit && opt_date ? mkdate(&commit->time) : "",
4739                 NULL
4740         };
4742         return grep_text(view, text);
4745 static void
4746 blame_select(struct view *view, struct line *line)
4748         struct blame *blame = line->data;
4749         struct blame_commit *commit = blame->commit;
4751         if (!commit)
4752                 return;
4754         if (!strcmp(commit->id, NULL_ID))
4755                 string_ncopy(ref_commit, "HEAD", 4);
4756         else
4757                 string_copy_rev(ref_commit, commit->id);
4760 static struct view_ops blame_ops = {
4761         "line",
4762         NULL,
4763         blame_open,
4764         blame_read,
4765         blame_draw,
4766         blame_request,
4767         blame_grep,
4768         blame_select,
4769 };
4771 /*
4772  * Branch backend
4773  */
4775 struct branch {
4776         const char *author;             /* Author of the last commit. */
4777         time_t time;                    /* Date of the last activity. */
4778         struct ref *ref;                /* Name and commit ID information. */
4779 };
4781 static const enum sort_field branch_sort_fields[] = {
4782         ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
4783 };
4784 static struct sort_state branch_sort_state = SORT_STATE(branch_sort_fields);
4786 static int
4787 branch_compare(const void *l1, const void *l2)
4789         const struct branch *branch1 = ((const struct line *) l1)->data;
4790         const struct branch *branch2 = ((const struct line *) l2)->data;
4792         switch (get_sort_field(branch_sort_state)) {
4793         case ORDERBY_DATE:
4794                 return sort_order(branch_sort_state, branch1->time - branch2->time);
4796         case ORDERBY_AUTHOR:
4797                 return sort_order(branch_sort_state, strcmp(branch1->author, branch2->author));
4799         case ORDERBY_NAME:
4800         default:
4801                 return sort_order(branch_sort_state, strcmp(branch1->ref->name, branch2->ref->name));
4802         }
4805 static bool
4806 branch_draw(struct view *view, struct line *line, unsigned int lineno)
4808         struct branch *branch = line->data;
4809         enum line_type type = branch->ref->head ? LINE_MAIN_HEAD : LINE_DEFAULT;
4811         if (opt_date && draw_date(view, branch->author ? &branch->time : NULL))
4812                 return TRUE;
4814         if (opt_author && draw_author(view, branch->author))
4815                 return TRUE;
4817         draw_text(view, type, branch->ref->name, TRUE);
4818         return TRUE;
4821 static enum request
4822 branch_request(struct view *view, enum request request, struct line *line)
4824         switch (request) {
4825         case REQ_REFRESH:
4826                 load_refs();
4827                 open_view(view, REQ_VIEW_BRANCH, OPEN_REFRESH);
4828                 return REQ_NONE;
4830         case REQ_TOGGLE_SORT_FIELD:
4831         case REQ_TOGGLE_SORT_ORDER:
4832                 sort_view(view, request, &branch_sort_state, branch_compare);
4833                 return REQ_NONE;
4835         case REQ_ENTER:
4836                 open_view(view, REQ_VIEW_MAIN, OPEN_SPLIT);
4837                 return REQ_NONE;
4839         default:
4840                 return request;
4841         }
4844 static bool
4845 branch_read(struct view *view, char *line)
4847         static char id[SIZEOF_REV];
4848         struct branch *reference;
4849         size_t i;
4851         if (!line)
4852                 return TRUE;
4854         switch (get_line_type(line)) {
4855         case LINE_COMMIT:
4856                 string_copy_rev(id, line + STRING_SIZE("commit "));
4857                 return TRUE;
4859         case LINE_AUTHOR:
4860                 for (i = 0, reference = NULL; i < view->lines; i++) {
4861                         struct branch *branch = view->line[i].data;
4863                         if (strcmp(branch->ref->id, id))
4864                                 continue;
4866                         view->line[i].dirty = TRUE;
4867                         if (reference) {
4868                                 branch->author = reference->author;
4869                                 branch->time = reference->time;
4870                                 continue;
4871                         }
4873                         parse_author_line(line + STRING_SIZE("author "),
4874                                           &branch->author, &branch->time);
4875                         reference = branch;
4876                 }
4877                 return TRUE;
4879         default:
4880                 return TRUE;
4881         }
4885 static bool
4886 branch_open_visitor(void *data, struct ref *ref)
4888         struct view *view = data;
4889         struct branch *branch;
4891         if (ref->tag || ref->ltag || ref->remote)
4892                 return TRUE;
4894         branch = calloc(1, sizeof(*branch));
4895         if (!branch)
4896                 return FALSE;
4898         branch->ref = ref;
4899         return !!add_line_data(view, branch, LINE_DEFAULT);
4902 static bool
4903 branch_open(struct view *view)
4905         const char *branch_log[] = {
4906                 "git", "log", "--no-color", "--pretty=raw",
4907                         "--simplify-by-decoration", "--all", NULL
4908         };
4910         if (!run_io_rd(&view->io, branch_log, FORMAT_NONE)) {
4911                 report("Failed to load branch data");
4912                 return TRUE;
4913         }
4915         setup_update(view, view->id);
4916         foreach_ref(branch_open_visitor, view);
4917         view->p_restore = TRUE;
4919         return TRUE;
4922 static bool
4923 branch_grep(struct view *view, struct line *line)
4925         struct branch *branch = line->data;
4926         const char *text[] = {
4927                 branch->ref->name,
4928                 branch->author,
4929                 NULL
4930         };
4932         return grep_text(view, text);
4935 static void
4936 branch_select(struct view *view, struct line *line)
4938         struct branch *branch = line->data;
4940         string_copy_rev(view->ref, branch->ref->id);
4941         string_copy_rev(ref_commit, branch->ref->id);
4942         string_copy_rev(ref_head, branch->ref->id);
4945 static struct view_ops branch_ops = {
4946         "branch",
4947         NULL,
4948         branch_open,
4949         branch_read,
4950         branch_draw,
4951         branch_request,
4952         branch_grep,
4953         branch_select,
4954 };
4956 /*
4957  * Status backend
4958  */
4960 struct status {
4961         char status;
4962         struct {
4963                 mode_t mode;
4964                 char rev[SIZEOF_REV];
4965                 char name[SIZEOF_STR];
4966         } old;
4967         struct {
4968                 mode_t mode;
4969                 char rev[SIZEOF_REV];
4970                 char name[SIZEOF_STR];
4971         } new;
4972 };
4974 static char status_onbranch[SIZEOF_STR];
4975 static struct status stage_status;
4976 static enum line_type stage_line_type;
4977 static size_t stage_chunks;
4978 static int *stage_chunk;
4980 DEFINE_ALLOCATOR(realloc_ints, int, 32)
4982 /* This should work even for the "On branch" line. */
4983 static inline bool
4984 status_has_none(struct view *view, struct line *line)
4986         return line < view->line + view->lines && !line[1].data;
4989 /* Get fields from the diff line:
4990  * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
4991  */
4992 static inline bool
4993 status_get_diff(struct status *file, const char *buf, size_t bufsize)
4995         const char *old_mode = buf +  1;
4996         const char *new_mode = buf +  8;
4997         const char *old_rev  = buf + 15;
4998         const char *new_rev  = buf + 56;
4999         const char *status   = buf + 97;
5001         if (bufsize < 98 ||
5002             old_mode[-1] != ':' ||
5003             new_mode[-1] != ' ' ||
5004             old_rev[-1]  != ' ' ||
5005             new_rev[-1]  != ' ' ||
5006             status[-1]   != ' ')
5007                 return FALSE;
5009         file->status = *status;
5011         string_copy_rev(file->old.rev, old_rev);
5012         string_copy_rev(file->new.rev, new_rev);
5014         file->old.mode = strtoul(old_mode, NULL, 8);
5015         file->new.mode = strtoul(new_mode, NULL, 8);
5017         file->old.name[0] = file->new.name[0] = 0;
5019         return TRUE;
5022 static bool
5023 status_run(struct view *view, const char *argv[], char status, enum line_type type)
5025         struct status *unmerged = NULL;
5026         char *buf;
5027         struct io io = {};
5029         if (!run_io(&io, argv, NULL, IO_RD))
5030                 return FALSE;
5032         add_line_data(view, NULL, type);
5034         while ((buf = io_get(&io, 0, TRUE))) {
5035                 struct status *file = unmerged;
5037                 if (!file) {
5038                         file = calloc(1, sizeof(*file));
5039                         if (!file || !add_line_data(view, file, type))
5040                                 goto error_out;
5041                 }
5043                 /* Parse diff info part. */
5044                 if (status) {
5045                         file->status = status;
5046                         if (status == 'A')
5047                                 string_copy(file->old.rev, NULL_ID);
5049                 } else if (!file->status || file == unmerged) {
5050                         if (!status_get_diff(file, buf, strlen(buf)))
5051                                 goto error_out;
5053                         buf = io_get(&io, 0, TRUE);
5054                         if (!buf)
5055                                 break;
5057                         /* Collapse all modified entries that follow an
5058                          * associated unmerged entry. */
5059                         if (unmerged == file) {
5060                                 unmerged->status = 'U';
5061                                 unmerged = NULL;
5062                         } else if (file->status == 'U') {
5063                                 unmerged = file;
5064                         }
5065                 }
5067                 /* Grab the old name for rename/copy. */
5068                 if (!*file->old.name &&
5069                     (file->status == 'R' || file->status == 'C')) {
5070                         string_ncopy(file->old.name, buf, strlen(buf));
5072                         buf = io_get(&io, 0, TRUE);
5073                         if (!buf)
5074                                 break;
5075                 }
5077                 /* git-ls-files just delivers a NUL separated list of
5078                  * file names similar to the second half of the
5079                  * git-diff-* output. */
5080                 string_ncopy(file->new.name, buf, strlen(buf));
5081                 if (!*file->old.name)
5082                         string_copy(file->old.name, file->new.name);
5083                 file = NULL;
5084         }
5086         if (io_error(&io)) {
5087 error_out:
5088                 done_io(&io);
5089                 return FALSE;
5090         }
5092         if (!view->line[view->lines - 1].data)
5093                 add_line_data(view, NULL, LINE_STAT_NONE);
5095         done_io(&io);
5096         return TRUE;
5099 /* Don't show unmerged entries in the staged section. */
5100 static const char *status_diff_index_argv[] = {
5101         "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
5102                              "--cached", "-M", "HEAD", NULL
5103 };
5105 static const char *status_diff_files_argv[] = {
5106         "git", "diff-files", "-z", NULL
5107 };
5109 static const char *status_list_other_argv[] = {
5110         "git", "ls-files", "-z", "--others", "--exclude-standard", NULL
5111 };
5113 static const char *status_list_no_head_argv[] = {
5114         "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
5115 };
5117 static const char *update_index_argv[] = {
5118         "git", "update-index", "-q", "--unmerged", "--refresh", NULL
5119 };
5121 /* Restore the previous line number to stay in the context or select a
5122  * line with something that can be updated. */
5123 static void
5124 status_restore(struct view *view)
5126         if (view->p_lineno >= view->lines)
5127                 view->p_lineno = view->lines - 1;
5128         while (view->p_lineno < view->lines && !view->line[view->p_lineno].data)
5129                 view->p_lineno++;
5130         while (view->p_lineno > 0 && !view->line[view->p_lineno].data)
5131                 view->p_lineno--;
5133         /* If the above fails, always skip the "On branch" line. */
5134         if (view->p_lineno < view->lines)
5135                 view->lineno = view->p_lineno;
5136         else
5137                 view->lineno = 1;
5139         if (view->lineno < view->offset)
5140                 view->offset = view->lineno;
5141         else if (view->offset + view->height <= view->lineno)
5142                 view->offset = view->lineno - view->height + 1;
5144         view->p_restore = FALSE;
5147 static void
5148 status_update_onbranch(void)
5150         static const char *paths[][2] = {
5151                 { "rebase-apply/rebasing",      "Rebasing" },
5152                 { "rebase-apply/applying",      "Applying mailbox" },
5153                 { "rebase-apply/",              "Rebasing mailbox" },
5154                 { "rebase-merge/interactive",   "Interactive rebase" },
5155                 { "rebase-merge/",              "Rebase merge" },
5156                 { "MERGE_HEAD",                 "Merging" },
5157                 { "BISECT_LOG",                 "Bisecting" },
5158                 { "HEAD",                       "On branch" },
5159         };
5160         char buf[SIZEOF_STR];
5161         struct stat stat;
5162         int i;
5164         if (is_initial_commit()) {
5165                 string_copy(status_onbranch, "Initial commit");
5166                 return;
5167         }
5169         for (i = 0; i < ARRAY_SIZE(paths); i++) {
5170                 char *head = opt_head;
5172                 if (!string_format(buf, "%s/%s", opt_git_dir, paths[i][0]) ||
5173                     lstat(buf, &stat) < 0)
5174                         continue;
5176                 if (!*opt_head) {
5177                         struct io io = {};
5179                         if (string_format(buf, "%s/rebase-merge/head-name", opt_git_dir) &&
5180                             io_open(&io, buf) &&
5181                             io_read_buf(&io, buf, sizeof(buf))) {
5182                                 head = buf;
5183                                 if (!prefixcmp(head, "refs/heads/"))
5184                                         head += STRING_SIZE("refs/heads/");
5185                         }
5186                 }
5188                 if (!string_format(status_onbranch, "%s %s", paths[i][1], head))
5189                         string_copy(status_onbranch, opt_head);
5190                 return;
5191         }
5193         string_copy(status_onbranch, "Not currently on any branch");
5196 /* First parse staged info using git-diff-index(1), then parse unstaged
5197  * info using git-diff-files(1), and finally untracked files using
5198  * git-ls-files(1). */
5199 static bool
5200 status_open(struct view *view)
5202         reset_view(view);
5204         add_line_data(view, NULL, LINE_STAT_HEAD);
5205         status_update_onbranch();
5207         run_io_bg(update_index_argv);
5209         if (is_initial_commit()) {
5210                 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
5211                         return FALSE;
5212         } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
5213                 return FALSE;
5214         }
5216         if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
5217             !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
5218                 return FALSE;
5220         /* Restore the exact position or use the specialized restore
5221          * mode? */
5222         if (!view->p_restore)
5223                 status_restore(view);
5224         return TRUE;
5227 static bool
5228 status_draw(struct view *view, struct line *line, unsigned int lineno)
5230         struct status *status = line->data;
5231         enum line_type type;
5232         const char *text;
5234         if (!status) {
5235                 switch (line->type) {
5236                 case LINE_STAT_STAGED:
5237                         type = LINE_STAT_SECTION;
5238                         text = "Changes to be committed:";
5239                         break;
5241                 case LINE_STAT_UNSTAGED:
5242                         type = LINE_STAT_SECTION;
5243                         text = "Changed but not updated:";
5244                         break;
5246                 case LINE_STAT_UNTRACKED:
5247                         type = LINE_STAT_SECTION;
5248                         text = "Untracked files:";
5249                         break;
5251                 case LINE_STAT_NONE:
5252                         type = LINE_DEFAULT;
5253                         text = "  (no files)";
5254                         break;
5256                 case LINE_STAT_HEAD:
5257                         type = LINE_STAT_HEAD;
5258                         text = status_onbranch;
5259                         break;
5261                 default:
5262                         return FALSE;
5263                 }
5264         } else {
5265                 static char buf[] = { '?', ' ', ' ', ' ', 0 };
5267                 buf[0] = status->status;
5268                 if (draw_text(view, line->type, buf, TRUE))
5269                         return TRUE;
5270                 type = LINE_DEFAULT;
5271                 text = status->new.name;
5272         }
5274         draw_text(view, type, text, TRUE);
5275         return TRUE;
5278 static enum request
5279 status_load_error(struct view *view, struct view *stage, const char *path)
5281         if (displayed_views() == 2 || display[current_view] != view)
5282                 maximize_view(view);
5283         report("Failed to load '%s': %s", path, io_strerror(&stage->io));
5284         return REQ_NONE;
5287 static enum request
5288 status_enter(struct view *view, struct line *line)
5290         struct status *status = line->data;
5291         const char *oldpath = status ? status->old.name : NULL;
5292         /* Diffs for unmerged entries are empty when passing the new
5293          * path, so leave it empty. */
5294         const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
5295         const char *info;
5296         enum open_flags split;
5297         struct view *stage = VIEW(REQ_VIEW_STAGE);
5299         if (line->type == LINE_STAT_NONE ||
5300             (!status && line[1].type == LINE_STAT_NONE)) {
5301                 report("No file to diff");
5302                 return REQ_NONE;
5303         }
5305         switch (line->type) {
5306         case LINE_STAT_STAGED:
5307                 if (is_initial_commit()) {
5308                         const char *no_head_diff_argv[] = {
5309                                 "git", "diff", "--no-color", "--patch-with-stat",
5310                                         "--", "/dev/null", newpath, NULL
5311                         };
5313                         if (!prepare_update(stage, no_head_diff_argv, opt_cdup, FORMAT_DASH))
5314                                 return status_load_error(view, stage, newpath);
5315                 } else {
5316                         const char *index_show_argv[] = {
5317                                 "git", "diff-index", "--root", "--patch-with-stat",
5318                                         "-C", "-M", "--cached", "HEAD", "--",
5319                                         oldpath, newpath, NULL
5320                         };
5322                         if (!prepare_update(stage, index_show_argv, opt_cdup, FORMAT_DASH))
5323                                 return status_load_error(view, stage, newpath);
5324                 }
5326                 if (status)
5327                         info = "Staged changes to %s";
5328                 else
5329                         info = "Staged changes";
5330                 break;
5332         case LINE_STAT_UNSTAGED:
5333         {
5334                 const char *files_show_argv[] = {
5335                         "git", "diff-files", "--root", "--patch-with-stat",
5336                                 "-C", "-M", "--", oldpath, newpath, NULL
5337                 };
5339                 if (!prepare_update(stage, files_show_argv, opt_cdup, FORMAT_DASH))
5340                         return status_load_error(view, stage, newpath);
5341                 if (status)
5342                         info = "Unstaged changes to %s";
5343                 else
5344                         info = "Unstaged changes";
5345                 break;
5346         }
5347         case LINE_STAT_UNTRACKED:
5348                 if (!newpath) {
5349                         report("No file to show");
5350                         return REQ_NONE;
5351                 }
5353                 if (!suffixcmp(status->new.name, -1, "/")) {
5354                         report("Cannot display a directory");
5355                         return REQ_NONE;
5356                 }
5358                 if (!prepare_update_file(stage, newpath))
5359                         return status_load_error(view, stage, newpath);
5360                 info = "Untracked file %s";
5361                 break;
5363         case LINE_STAT_HEAD:
5364                 return REQ_NONE;
5366         default:
5367                 die("line type %d not handled in switch", line->type);
5368         }
5370         split = view_is_displayed(view) ? OPEN_SPLIT : 0;
5371         open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split);
5372         if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
5373                 if (status) {
5374                         stage_status = *status;
5375                 } else {
5376                         memset(&stage_status, 0, sizeof(stage_status));
5377                 }
5379                 stage_line_type = line->type;
5380                 stage_chunks = 0;
5381                 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
5382         }
5384         return REQ_NONE;
5387 static bool
5388 status_exists(struct status *status, enum line_type type)
5390         struct view *view = VIEW(REQ_VIEW_STATUS);
5391         unsigned long lineno;
5393         for (lineno = 0; lineno < view->lines; lineno++) {
5394                 struct line *line = &view->line[lineno];
5395                 struct status *pos = line->data;
5397                 if (line->type != type)
5398                         continue;
5399                 if (!pos && (!status || !status->status) && line[1].data) {
5400                         select_view_line(view, lineno);
5401                         return TRUE;
5402                 }
5403                 if (pos && !strcmp(status->new.name, pos->new.name)) {
5404                         select_view_line(view, lineno);
5405                         return TRUE;
5406                 }
5407         }
5409         return FALSE;
5413 static bool
5414 status_update_prepare(struct io *io, enum line_type type)
5416         const char *staged_argv[] = {
5417                 "git", "update-index", "-z", "--index-info", NULL
5418         };
5419         const char *others_argv[] = {
5420                 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
5421         };
5423         switch (type) {
5424         case LINE_STAT_STAGED:
5425                 return run_io(io, staged_argv, opt_cdup, IO_WR);
5427         case LINE_STAT_UNSTAGED:
5428                 return run_io(io, others_argv, opt_cdup, IO_WR);
5430         case LINE_STAT_UNTRACKED:
5431                 return run_io(io, others_argv, NULL, IO_WR);
5433         default:
5434                 die("line type %d not handled in switch", type);
5435                 return FALSE;
5436         }
5439 static bool
5440 status_update_write(struct io *io, struct status *status, enum line_type type)
5442         char buf[SIZEOF_STR];
5443         size_t bufsize = 0;
5445         switch (type) {
5446         case LINE_STAT_STAGED:
5447                 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
5448                                         status->old.mode,
5449                                         status->old.rev,
5450                                         status->old.name, 0))
5451                         return FALSE;
5452                 break;
5454         case LINE_STAT_UNSTAGED:
5455         case LINE_STAT_UNTRACKED:
5456                 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
5457                         return FALSE;
5458                 break;
5460         default:
5461                 die("line type %d not handled in switch", type);
5462         }
5464         return io_write(io, buf, bufsize);
5467 static bool
5468 status_update_file(struct status *status, enum line_type type)
5470         struct io io = {};
5471         bool result;
5473         if (!status_update_prepare(&io, type))
5474                 return FALSE;
5476         result = status_update_write(&io, status, type);
5477         return done_io(&io) && result;
5480 static bool
5481 status_update_files(struct view *view, struct line *line)
5483         char buf[sizeof(view->ref)];
5484         struct io io = {};
5485         bool result = TRUE;
5486         struct line *pos = view->line + view->lines;
5487         int files = 0;
5488         int file, done;
5489         int cursor_y, cursor_x;
5491         if (!status_update_prepare(&io, line->type))
5492                 return FALSE;
5494         for (pos = line; pos < view->line + view->lines && pos->data; pos++)
5495                 files++;
5497         string_copy(buf, view->ref);
5498         getsyx(cursor_y, cursor_x);
5499         for (file = 0, done = 5; result && file < files; line++, file++) {
5500                 int almost_done = file * 100 / files;
5502                 if (almost_done > done) {
5503                         done = almost_done;
5504                         string_format(view->ref, "updating file %u of %u (%d%% done)",
5505                                       file, files, done);
5506                         update_view_title(view);
5507                         setsyx(cursor_y, cursor_x);
5508                         doupdate();
5509                 }
5510                 result = status_update_write(&io, line->data, line->type);
5511         }
5512         string_copy(view->ref, buf);
5514         return done_io(&io) && result;
5517 static bool
5518 status_update(struct view *view)
5520         struct line *line = &view->line[view->lineno];
5522         assert(view->lines);
5524         if (!line->data) {
5525                 /* This should work even for the "On branch" line. */
5526                 if (line < view->line + view->lines && !line[1].data) {
5527                         report("Nothing to update");
5528                         return FALSE;
5529                 }
5531                 if (!status_update_files(view, line + 1)) {
5532                         report("Failed to update file status");
5533                         return FALSE;
5534                 }
5536         } else if (!status_update_file(line->data, line->type)) {
5537                 report("Failed to update file status");
5538                 return FALSE;
5539         }
5541         return TRUE;
5544 static bool
5545 status_revert(struct status *status, enum line_type type, bool has_none)
5547         if (!status || type != LINE_STAT_UNSTAGED) {
5548                 if (type == LINE_STAT_STAGED) {
5549                         report("Cannot revert changes to staged files");
5550                 } else if (type == LINE_STAT_UNTRACKED) {
5551                         report("Cannot revert changes to untracked files");
5552                 } else if (has_none) {
5553                         report("Nothing to revert");
5554                 } else {
5555                         report("Cannot revert changes to multiple files");
5556                 }
5557                 return FALSE;
5559         } else {
5560                 char mode[10] = "100644";
5561                 const char *reset_argv[] = {
5562                         "git", "update-index", "--cacheinfo", mode,
5563                                 status->old.rev, status->old.name, NULL
5564                 };
5565                 const char *checkout_argv[] = {
5566                         "git", "checkout", "--", status->old.name, NULL
5567                 };
5569                 if (!prompt_yesno("Are you sure you want to overwrite any changes?"))
5570                         return FALSE;
5571                 string_format(mode, "%o", status->old.mode);
5572                 return (status->status != 'U' || run_io_fg(reset_argv, opt_cdup)) &&
5573                         run_io_fg(checkout_argv, opt_cdup);
5574         }
5577 static enum request
5578 status_request(struct view *view, enum request request, struct line *line)
5580         struct status *status = line->data;
5582         switch (request) {
5583         case REQ_STATUS_UPDATE:
5584                 if (!status_update(view))
5585                         return REQ_NONE;
5586                 break;
5588         case REQ_STATUS_REVERT:
5589                 if (!status_revert(status, line->type, status_has_none(view, line)))
5590                         return REQ_NONE;
5591                 break;
5593         case REQ_STATUS_MERGE:
5594                 if (!status || status->status != 'U') {
5595                         report("Merging only possible for files with unmerged status ('U').");
5596                         return REQ_NONE;
5597                 }
5598                 open_mergetool(status->new.name);
5599                 break;
5601         case REQ_EDIT:
5602                 if (!status)
5603                         return request;
5604                 if (status->status == 'D') {
5605                         report("File has been deleted.");
5606                         return REQ_NONE;
5607                 }
5609                 open_editor(status->status != '?', status->new.name);
5610                 break;
5612         case REQ_VIEW_BLAME:
5613                 if (status) {
5614                         string_copy(opt_file, status->new.name);
5615                         opt_ref[0] = 0;
5616                 }
5617                 return request;
5619         case REQ_ENTER:
5620                 /* After returning the status view has been split to
5621                  * show the stage view. No further reloading is
5622                  * necessary. */
5623                 return status_enter(view, line);
5625         case REQ_REFRESH:
5626                 /* Simply reload the view. */
5627                 break;
5629         default:
5630                 return request;
5631         }
5633         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
5635         return REQ_NONE;
5638 static void
5639 status_select(struct view *view, struct line *line)
5641         struct status *status = line->data;
5642         char file[SIZEOF_STR] = "all files";
5643         const char *text;
5644         const char *key;
5646         if (status && !string_format(file, "'%s'", status->new.name))
5647                 return;
5649         if (!status && line[1].type == LINE_STAT_NONE)
5650                 line++;
5652         switch (line->type) {
5653         case LINE_STAT_STAGED:
5654                 text = "Press %s to unstage %s for commit";
5655                 break;
5657         case LINE_STAT_UNSTAGED:
5658                 text = "Press %s to stage %s for commit";
5659                 break;
5661         case LINE_STAT_UNTRACKED:
5662                 text = "Press %s to stage %s for addition";
5663                 break;
5665         case LINE_STAT_HEAD:
5666         case LINE_STAT_NONE:
5667                 text = "Nothing to update";
5668                 break;
5670         default:
5671                 die("line type %d not handled in switch", line->type);
5672         }
5674         if (status && status->status == 'U') {
5675                 text = "Press %s to resolve conflict in %s";
5676                 key = get_key(REQ_STATUS_MERGE);
5678         } else {
5679                 key = get_key(REQ_STATUS_UPDATE);
5680         }
5682         string_format(view->ref, text, key, file);
5685 static bool
5686 status_grep(struct view *view, struct line *line)
5688         struct status *status = line->data;
5690         if (status) {
5691                 const char buf[2] = { status->status, 0 };
5692                 const char *text[] = { status->new.name, buf, NULL };
5694                 return grep_text(view, text);
5695         }
5697         return FALSE;
5700 static struct view_ops status_ops = {
5701         "file",
5702         NULL,
5703         status_open,
5704         NULL,
5705         status_draw,
5706         status_request,
5707         status_grep,
5708         status_select,
5709 };
5712 static bool
5713 stage_diff_write(struct io *io, struct line *line, struct line *end)
5715         while (line < end) {
5716                 if (!io_write(io, line->data, strlen(line->data)) ||
5717                     !io_write(io, "\n", 1))
5718                         return FALSE;
5719                 line++;
5720                 if (line->type == LINE_DIFF_CHUNK ||
5721                     line->type == LINE_DIFF_HEADER)
5722                         break;
5723         }
5725         return TRUE;
5728 static struct line *
5729 stage_diff_find(struct view *view, struct line *line, enum line_type type)
5731         for (; view->line < line; line--)
5732                 if (line->type == type)
5733                         return line;
5735         return NULL;
5738 static bool
5739 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
5741         const char *apply_argv[SIZEOF_ARG] = {
5742                 "git", "apply", "--whitespace=nowarn", NULL
5743         };
5744         struct line *diff_hdr;
5745         struct io io = {};
5746         int argc = 3;
5748         diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
5749         if (!diff_hdr)
5750                 return FALSE;
5752         if (!revert)
5753                 apply_argv[argc++] = "--cached";
5754         if (revert || stage_line_type == LINE_STAT_STAGED)
5755                 apply_argv[argc++] = "-R";
5756         apply_argv[argc++] = "-";
5757         apply_argv[argc++] = NULL;
5758         if (!run_io(&io, apply_argv, opt_cdup, IO_WR))
5759                 return FALSE;
5761         if (!stage_diff_write(&io, diff_hdr, chunk) ||
5762             !stage_diff_write(&io, chunk, view->line + view->lines))
5763                 chunk = NULL;
5765         done_io(&io);
5766         run_io_bg(update_index_argv);
5768         return chunk ? TRUE : FALSE;
5771 static bool
5772 stage_update(struct view *view, struct line *line)
5774         struct line *chunk = NULL;
5776         if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
5777                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
5779         if (chunk) {
5780                 if (!stage_apply_chunk(view, chunk, FALSE)) {
5781                         report("Failed to apply chunk");
5782                         return FALSE;
5783                 }
5785         } else if (!stage_status.status) {
5786                 view = VIEW(REQ_VIEW_STATUS);
5788                 for (line = view->line; line < view->line + view->lines; line++)
5789                         if (line->type == stage_line_type)
5790                                 break;
5792                 if (!status_update_files(view, line + 1)) {
5793                         report("Failed to update files");
5794                         return FALSE;
5795                 }
5797         } else if (!status_update_file(&stage_status, stage_line_type)) {
5798                 report("Failed to update file");
5799                 return FALSE;
5800         }
5802         return TRUE;
5805 static bool
5806 stage_revert(struct view *view, struct line *line)
5808         struct line *chunk = NULL;
5810         if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
5811                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
5813         if (chunk) {
5814                 if (!prompt_yesno("Are you sure you want to revert changes?"))
5815                         return FALSE;
5817                 if (!stage_apply_chunk(view, chunk, TRUE)) {
5818                         report("Failed to revert chunk");
5819                         return FALSE;
5820                 }
5821                 return TRUE;
5823         } else {
5824                 return status_revert(stage_status.status ? &stage_status : NULL,
5825                                      stage_line_type, FALSE);
5826         }
5830 static void
5831 stage_next(struct view *view, struct line *line)
5833         int i;
5835         if (!stage_chunks) {
5836                 for (line = view->line; line < view->line + view->lines; line++) {
5837                         if (line->type != LINE_DIFF_CHUNK)
5838                                 continue;
5840                         if (!realloc_ints(&stage_chunk, stage_chunks, 1)) {
5841                                 report("Allocation failure");
5842                                 return;
5843                         }
5845                         stage_chunk[stage_chunks++] = line - view->line;
5846                 }
5847         }
5849         for (i = 0; i < stage_chunks; i++) {
5850                 if (stage_chunk[i] > view->lineno) {
5851                         do_scroll_view(view, stage_chunk[i] - view->lineno);
5852                         report("Chunk %d of %d", i + 1, stage_chunks);
5853                         return;
5854                 }
5855         }
5857         report("No next chunk found");
5860 static enum request
5861 stage_request(struct view *view, enum request request, struct line *line)
5863         switch (request) {
5864         case REQ_STATUS_UPDATE:
5865                 if (!stage_update(view, line))
5866                         return REQ_NONE;
5867                 break;
5869         case REQ_STATUS_REVERT:
5870                 if (!stage_revert(view, line))
5871                         return REQ_NONE;
5872                 break;
5874         case REQ_STAGE_NEXT:
5875                 if (stage_line_type == LINE_STAT_UNTRACKED) {
5876                         report("File is untracked; press %s to add",
5877                                get_key(REQ_STATUS_UPDATE));
5878                         return REQ_NONE;
5879                 }
5880                 stage_next(view, line);
5881                 return REQ_NONE;
5883         case REQ_EDIT:
5884                 if (!stage_status.new.name[0])
5885                         return request;
5886                 if (stage_status.status == 'D') {
5887                         report("File has been deleted.");
5888                         return REQ_NONE;
5889                 }
5891                 open_editor(stage_status.status != '?', stage_status.new.name);
5892                 break;
5894         case REQ_REFRESH:
5895                 /* Reload everything ... */
5896                 break;
5898         case REQ_VIEW_BLAME:
5899                 if (stage_status.new.name[0]) {
5900                         string_copy(opt_file, stage_status.new.name);
5901                         opt_ref[0] = 0;
5902                 }
5903                 return request;
5905         case REQ_ENTER:
5906                 return pager_request(view, request, line);
5908         default:
5909                 return request;
5910         }
5912         VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
5913         open_view(view, REQ_VIEW_STATUS, OPEN_REFRESH);
5915         /* Check whether the staged entry still exists, and close the
5916          * stage view if it doesn't. */
5917         if (!status_exists(&stage_status, stage_line_type)) {
5918                 status_restore(VIEW(REQ_VIEW_STATUS));
5919                 return REQ_VIEW_CLOSE;
5920         }
5922         if (stage_line_type == LINE_STAT_UNTRACKED) {
5923                 if (!suffixcmp(stage_status.new.name, -1, "/")) {
5924                         report("Cannot display a directory");
5925                         return REQ_NONE;
5926                 }
5928                 if (!prepare_update_file(view, stage_status.new.name)) {
5929                         report("Failed to open file: %s", strerror(errno));
5930                         return REQ_NONE;
5931                 }
5932         }
5933         open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
5935         return REQ_NONE;
5938 static struct view_ops stage_ops = {
5939         "line",
5940         NULL,
5941         NULL,
5942         pager_read,
5943         pager_draw,
5944         stage_request,
5945         pager_grep,
5946         pager_select,
5947 };
5950 /*
5951  * Revision graph
5952  */
5954 struct commit {
5955         char id[SIZEOF_REV];            /* SHA1 ID. */
5956         char title[128];                /* First line of the commit message. */
5957         const char *author;             /* Author of the commit. */
5958         time_t time;                    /* Date from the author ident. */
5959         struct ref_list *refs;          /* Repository references. */
5960         chtype graph[SIZEOF_REVGRAPH];  /* Ancestry chain graphics. */
5961         size_t graph_size;              /* The width of the graph array. */
5962         bool has_parents;               /* Rewritten --parents seen. */
5963 };
5965 /* Size of rev graph with no  "padding" columns */
5966 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
5968 struct rev_graph {
5969         struct rev_graph *prev, *next, *parents;
5970         char rev[SIZEOF_REVITEMS][SIZEOF_REV];
5971         size_t size;
5972         struct commit *commit;
5973         size_t pos;
5974         unsigned int boundary:1;
5975 };
5977 /* Parents of the commit being visualized. */
5978 static struct rev_graph graph_parents[4];
5980 /* The current stack of revisions on the graph. */
5981 static struct rev_graph graph_stacks[4] = {
5982         { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
5983         { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
5984         { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
5985         { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
5986 };
5988 static inline bool
5989 graph_parent_is_merge(struct rev_graph *graph)
5991         return graph->parents->size > 1;
5994 static inline void
5995 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
5997         struct commit *commit = graph->commit;
5999         if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
6000                 commit->graph[commit->graph_size++] = symbol;
6003 static void
6004 clear_rev_graph(struct rev_graph *graph)
6006         graph->boundary = 0;
6007         graph->size = graph->pos = 0;
6008         graph->commit = NULL;
6009         memset(graph->parents, 0, sizeof(*graph->parents));
6012 static void
6013 done_rev_graph(struct rev_graph *graph)
6015         if (graph_parent_is_merge(graph) &&
6016             graph->pos < graph->size - 1 &&
6017             graph->next->size == graph->size + graph->parents->size - 1) {
6018                 size_t i = graph->pos + graph->parents->size - 1;
6020                 graph->commit->graph_size = i * 2;
6021                 while (i < graph->next->size - 1) {
6022                         append_to_rev_graph(graph, ' ');
6023                         append_to_rev_graph(graph, '\\');
6024                         i++;
6025                 }
6026         }
6028         clear_rev_graph(graph);
6031 static void
6032 push_rev_graph(struct rev_graph *graph, const char *parent)
6034         int i;
6036         /* "Collapse" duplicate parents lines.
6037          *
6038          * FIXME: This needs to also update update the drawn graph but
6039          * for now it just serves as a method for pruning graph lines. */
6040         for (i = 0; i < graph->size; i++)
6041                 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
6042                         return;
6044         if (graph->size < SIZEOF_REVITEMS) {
6045                 string_copy_rev(graph->rev[graph->size++], parent);
6046         }
6049 static chtype
6050 get_rev_graph_symbol(struct rev_graph *graph)
6052         chtype symbol;
6054         if (graph->boundary)
6055                 symbol = REVGRAPH_BOUND;
6056         else if (graph->parents->size == 0)
6057                 symbol = REVGRAPH_INIT;
6058         else if (graph_parent_is_merge(graph))
6059                 symbol = REVGRAPH_MERGE;
6060         else if (graph->pos >= graph->size)
6061                 symbol = REVGRAPH_BRANCH;
6062         else
6063                 symbol = REVGRAPH_COMMIT;
6065         return symbol;
6068 static void
6069 draw_rev_graph(struct rev_graph *graph)
6071         struct rev_filler {
6072                 chtype separator, line;
6073         };
6074         enum { DEFAULT, RSHARP, RDIAG, LDIAG };
6075         static struct rev_filler fillers[] = {
6076                 { ' ',  '|' },
6077                 { '`',  '.' },
6078                 { '\'', ' ' },
6079                 { '/',  ' ' },
6080         };
6081         chtype symbol = get_rev_graph_symbol(graph);
6082         struct rev_filler *filler;
6083         size_t i;
6085         if (opt_line_graphics)
6086                 fillers[DEFAULT].line = line_graphics[LINE_GRAPHIC_VLINE];
6088         filler = &fillers[DEFAULT];
6090         for (i = 0; i < graph->pos; i++) {
6091                 append_to_rev_graph(graph, filler->line);
6092                 if (graph_parent_is_merge(graph->prev) &&
6093                     graph->prev->pos == i)
6094                         filler = &fillers[RSHARP];
6096                 append_to_rev_graph(graph, filler->separator);
6097         }
6099         /* Place the symbol for this revision. */
6100         append_to_rev_graph(graph, symbol);
6102         if (graph->prev->size > graph->size)
6103                 filler = &fillers[RDIAG];
6104         else
6105                 filler = &fillers[DEFAULT];
6107         i++;
6109         for (; i < graph->size; i++) {
6110                 append_to_rev_graph(graph, filler->separator);
6111                 append_to_rev_graph(graph, filler->line);
6112                 if (graph_parent_is_merge(graph->prev) &&
6113                     i < graph->prev->pos + graph->parents->size)
6114                         filler = &fillers[RSHARP];
6115                 if (graph->prev->size > graph->size)
6116                         filler = &fillers[LDIAG];
6117         }
6119         if (graph->prev->size > graph->size) {
6120                 append_to_rev_graph(graph, filler->separator);
6121                 if (filler->line != ' ')
6122                         append_to_rev_graph(graph, filler->line);
6123         }
6126 /* Prepare the next rev graph */
6127 static void
6128 prepare_rev_graph(struct rev_graph *graph)
6130         size_t i;
6132         /* First, traverse all lines of revisions up to the active one. */
6133         for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
6134                 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
6135                         break;
6137                 push_rev_graph(graph->next, graph->rev[graph->pos]);
6138         }
6140         /* Interleave the new revision parent(s). */
6141         for (i = 0; !graph->boundary && i < graph->parents->size; i++)
6142                 push_rev_graph(graph->next, graph->parents->rev[i]);
6144         /* Lastly, put any remaining revisions. */
6145         for (i = graph->pos + 1; i < graph->size; i++)
6146                 push_rev_graph(graph->next, graph->rev[i]);
6149 static void
6150 update_rev_graph(struct view *view, struct rev_graph *graph)
6152         /* If this is the finalizing update ... */
6153         if (graph->commit)
6154                 prepare_rev_graph(graph);
6156         /* Graph visualization needs a one rev look-ahead,
6157          * so the first update doesn't visualize anything. */
6158         if (!graph->prev->commit)
6159                 return;
6161         if (view->lines > 2)
6162                 view->line[view->lines - 3].dirty = 1;
6163         if (view->lines > 1)
6164                 view->line[view->lines - 2].dirty = 1;
6165         draw_rev_graph(graph->prev);
6166         done_rev_graph(graph->prev->prev);
6170 /*
6171  * Main view backend
6172  */
6174 static const char *main_argv[SIZEOF_ARG] = {
6175         "git", "log", "--no-color", "--pretty=raw", "--parents",
6176                       "--topo-order", "%(head)", NULL
6177 };
6179 static bool
6180 main_draw(struct view *view, struct line *line, unsigned int lineno)
6182         struct commit *commit = line->data;
6184         if (!commit->author)
6185                 return FALSE;
6187         if (opt_date && draw_date(view, &commit->time))
6188                 return TRUE;
6190         if (opt_author && draw_author(view, commit->author))
6191                 return TRUE;
6193         if (opt_rev_graph && commit->graph_size &&
6194             draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
6195                 return TRUE;
6197         if (opt_show_refs && commit->refs) {
6198                 size_t i;
6200                 for (i = 0; i < commit->refs->size; i++) {
6201                         struct ref *ref = commit->refs->refs[i];
6202                         enum line_type type;
6204                         if (ref->head)
6205                                 type = LINE_MAIN_HEAD;
6206                         else if (ref->ltag)
6207                                 type = LINE_MAIN_LOCAL_TAG;
6208                         else if (ref->tag)
6209                                 type = LINE_MAIN_TAG;
6210                         else if (ref->tracked)
6211                                 type = LINE_MAIN_TRACKED;
6212                         else if (ref->remote)
6213                                 type = LINE_MAIN_REMOTE;
6214                         else
6215                                 type = LINE_MAIN_REF;
6217                         if (draw_text(view, type, "[", TRUE) ||
6218                             draw_text(view, type, ref->name, TRUE) ||
6219                             draw_text(view, type, "]", TRUE))
6220                                 return TRUE;
6222                         if (draw_text(view, LINE_DEFAULT, " ", TRUE))
6223                                 return TRUE;
6224                 }
6225         }
6227         draw_text(view, LINE_DEFAULT, commit->title, TRUE);
6228         return TRUE;
6231 /* Reads git log --pretty=raw output and parses it into the commit struct. */
6232 static bool
6233 main_read(struct view *view, char *line)
6235         static struct rev_graph *graph = graph_stacks;
6236         enum line_type type;
6237         struct commit *commit;
6239         if (!line) {
6240                 int i;
6242                 if (!view->lines && !view->parent)
6243                         die("No revisions match the given arguments.");
6244                 if (view->lines > 0) {
6245                         commit = view->line[view->lines - 1].data;
6246                         view->line[view->lines - 1].dirty = 1;
6247                         if (!commit->author) {
6248                                 view->lines--;
6249                                 free(commit);
6250                                 graph->commit = NULL;
6251                         }
6252                 }
6253                 update_rev_graph(view, graph);
6255                 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
6256                         clear_rev_graph(&graph_stacks[i]);
6257                 return TRUE;
6258         }
6260         type = get_line_type(line);
6261         if (type == LINE_COMMIT) {
6262                 commit = calloc(1, sizeof(struct commit));
6263                 if (!commit)
6264                         return FALSE;
6266                 line += STRING_SIZE("commit ");
6267                 if (*line == '-') {
6268                         graph->boundary = 1;
6269                         line++;
6270                 }
6272                 string_copy_rev(commit->id, line);
6273                 commit->refs = get_ref_list(commit->id);
6274                 graph->commit = commit;
6275                 add_line_data(view, commit, LINE_MAIN_COMMIT);
6277                 while ((line = strchr(line, ' '))) {
6278                         line++;
6279                         push_rev_graph(graph->parents, line);
6280                         commit->has_parents = TRUE;
6281                 }
6282                 return TRUE;
6283         }
6285         if (!view->lines)
6286                 return TRUE;
6287         commit = view->line[view->lines - 1].data;
6289         switch (type) {
6290         case LINE_PARENT:
6291                 if (commit->has_parents)
6292                         break;
6293                 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
6294                 break;
6296         case LINE_AUTHOR:
6297                 parse_author_line(line + STRING_SIZE("author "),
6298                                   &commit->author, &commit->time);
6299                 update_rev_graph(view, graph);
6300                 graph = graph->next;
6301                 break;
6303         default:
6304                 /* Fill in the commit title if it has not already been set. */
6305                 if (commit->title[0])
6306                         break;
6308                 /* Require titles to start with a non-space character at the
6309                  * offset used by git log. */
6310                 if (strncmp(line, "    ", 4))
6311                         break;
6312                 line += 4;
6313                 /* Well, if the title starts with a whitespace character,
6314                  * try to be forgiving.  Otherwise we end up with no title. */
6315                 while (isspace(*line))
6316                         line++;
6317                 if (*line == '\0')
6318                         break;
6319                 /* FIXME: More graceful handling of titles; append "..." to
6320                  * shortened titles, etc. */
6322                 string_expand(commit->title, sizeof(commit->title), line, 1);
6323                 view->line[view->lines - 1].dirty = 1;
6324         }
6326         return TRUE;
6329 static enum request
6330 main_request(struct view *view, enum request request, struct line *line)
6332         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
6334         switch (request) {
6335         case REQ_ENTER:
6336                 open_view(view, REQ_VIEW_DIFF, flags);
6337                 break;
6338         case REQ_REFRESH:
6339                 load_refs();
6340                 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
6341                 break;
6342         default:
6343                 return request;
6344         }
6346         return REQ_NONE;
6349 static bool
6350 grep_refs(struct ref_list *list, regex_t *regex)
6352         regmatch_t pmatch;
6353         size_t i;
6355         if (!opt_show_refs || !list)
6356                 return FALSE;
6358         for (i = 0; i < list->size; i++) {
6359                 if (regexec(regex, list->refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
6360                         return TRUE;
6361         }
6363         return FALSE;
6366 static bool
6367 main_grep(struct view *view, struct line *line)
6369         struct commit *commit = line->data;
6370         const char *text[] = {
6371                 commit->title,
6372                 opt_author ? commit->author : "",
6373                 opt_date ? mkdate(&commit->time) : "",
6374                 NULL
6375         };
6377         return grep_text(view, text) || grep_refs(commit->refs, view->regex);
6380 static void
6381 main_select(struct view *view, struct line *line)
6383         struct commit *commit = line->data;
6385         string_copy_rev(view->ref, commit->id);
6386         string_copy_rev(ref_commit, view->ref);
6389 static struct view_ops main_ops = {
6390         "commit",
6391         main_argv,
6392         NULL,
6393         main_read,
6394         main_draw,
6395         main_request,
6396         main_grep,
6397         main_select,
6398 };
6401 /*
6402  * Unicode / UTF-8 handling
6403  *
6404  * NOTE: Much of the following code for dealing with Unicode is derived from
6405  * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
6406  * src/intl/charset.c from the UTF-8 branch commit elinks-0.11.0-g31f2c28.
6407  */
6409 static inline int
6410 unicode_width(unsigned long c)
6412         if (c >= 0x1100 &&
6413            (c <= 0x115f                         /* Hangul Jamo */
6414             || c == 0x2329
6415             || c == 0x232a
6416             || (c >= 0x2e80  && c <= 0xa4cf && c != 0x303f)
6417                                                 /* CJK ... Yi */
6418             || (c >= 0xac00  && c <= 0xd7a3)    /* Hangul Syllables */
6419             || (c >= 0xf900  && c <= 0xfaff)    /* CJK Compatibility Ideographs */
6420             || (c >= 0xfe30  && c <= 0xfe6f)    /* CJK Compatibility Forms */
6421             || (c >= 0xff00  && c <= 0xff60)    /* Fullwidth Forms */
6422             || (c >= 0xffe0  && c <= 0xffe6)
6423             || (c >= 0x20000 && c <= 0x2fffd)
6424             || (c >= 0x30000 && c <= 0x3fffd)))
6425                 return 2;
6427         if (c == '\t')
6428                 return opt_tab_size;
6430         return 1;
6433 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
6434  * Illegal bytes are set one. */
6435 static const unsigned char utf8_bytes[256] = {
6436         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,
6437         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,
6438         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,
6439         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,
6440         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,
6441         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,
6442         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,
6443         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,
6444 };
6446 /* Decode UTF-8 multi-byte representation into a Unicode character. */
6447 static inline unsigned long
6448 utf8_to_unicode(const char *string, size_t length)
6450         unsigned long unicode;
6452         switch (length) {
6453         case 1:
6454                 unicode  =   string[0];
6455                 break;
6456         case 2:
6457                 unicode  =  (string[0] & 0x1f) << 6;
6458                 unicode +=  (string[1] & 0x3f);
6459                 break;
6460         case 3:
6461                 unicode  =  (string[0] & 0x0f) << 12;
6462                 unicode += ((string[1] & 0x3f) << 6);
6463                 unicode +=  (string[2] & 0x3f);
6464                 break;
6465         case 4:
6466                 unicode  =  (string[0] & 0x0f) << 18;
6467                 unicode += ((string[1] & 0x3f) << 12);
6468                 unicode += ((string[2] & 0x3f) << 6);
6469                 unicode +=  (string[3] & 0x3f);
6470                 break;
6471         case 5:
6472                 unicode  =  (string[0] & 0x0f) << 24;
6473                 unicode += ((string[1] & 0x3f) << 18);
6474                 unicode += ((string[2] & 0x3f) << 12);
6475                 unicode += ((string[3] & 0x3f) << 6);
6476                 unicode +=  (string[4] & 0x3f);
6477                 break;
6478         case 6:
6479                 unicode  =  (string[0] & 0x01) << 30;
6480                 unicode += ((string[1] & 0x3f) << 24);
6481                 unicode += ((string[2] & 0x3f) << 18);
6482                 unicode += ((string[3] & 0x3f) << 12);
6483                 unicode += ((string[4] & 0x3f) << 6);
6484                 unicode +=  (string[5] & 0x3f);
6485                 break;
6486         default:
6487                 die("Invalid Unicode length");
6488         }
6490         /* Invalid characters could return the special 0xfffd value but NUL
6491          * should be just as good. */
6492         return unicode > 0xffff ? 0 : unicode;
6495 /* Calculates how much of string can be shown within the given maximum width
6496  * and sets trimmed parameter to non-zero value if all of string could not be
6497  * shown. If the reserve flag is TRUE, it will reserve at least one
6498  * trailing character, which can be useful when drawing a delimiter.
6499  *
6500  * Returns the number of bytes to output from string to satisfy max_width. */
6501 static size_t
6502 utf8_length(const char **start, size_t skip, int *width, size_t max_width, int *trimmed, bool reserve)
6504         const char *string = *start;
6505         const char *end = strchr(string, '\0');
6506         unsigned char last_bytes = 0;
6507         size_t last_ucwidth = 0;
6509         *width = 0;
6510         *trimmed = 0;
6512         while (string < end) {
6513                 int c = *(unsigned char *) string;
6514                 unsigned char bytes = utf8_bytes[c];
6515                 size_t ucwidth;
6516                 unsigned long unicode;
6518                 if (string + bytes > end)
6519                         break;
6521                 /* Change representation to figure out whether
6522                  * it is a single- or double-width character. */
6524                 unicode = utf8_to_unicode(string, bytes);
6525                 /* FIXME: Graceful handling of invalid Unicode character. */
6526                 if (!unicode)
6527                         break;
6529                 ucwidth = unicode_width(unicode);
6530                 if (skip > 0) {
6531                         skip -= ucwidth <= skip ? ucwidth : skip;
6532                         *start += bytes;
6533                 }
6534                 *width  += ucwidth;
6535                 if (*width > max_width) {
6536                         *trimmed = 1;
6537                         *width -= ucwidth;
6538                         if (reserve && *width == max_width) {
6539                                 string -= last_bytes;
6540                                 *width -= last_ucwidth;
6541                         }
6542                         break;
6543                 }
6545                 string  += bytes;
6546                 last_bytes = ucwidth ? bytes : 0;
6547                 last_ucwidth = ucwidth;
6548         }
6550         return string - *start;
6554 /*
6555  * Status management
6556  */
6558 /* Whether or not the curses interface has been initialized. */
6559 static bool cursed = FALSE;
6561 /* Terminal hacks and workarounds. */
6562 static bool use_scroll_redrawwin;
6563 static bool use_scroll_status_wclear;
6565 /* The status window is used for polling keystrokes. */
6566 static WINDOW *status_win;
6568 /* Reading from the prompt? */
6569 static bool input_mode = FALSE;
6571 static bool status_empty = FALSE;
6573 /* Update status and title window. */
6574 static void
6575 report(const char *msg, ...)
6577         struct view *view = display[current_view];
6579         if (input_mode)
6580                 return;
6582         if (!view) {
6583                 char buf[SIZEOF_STR];
6584                 va_list args;
6586                 va_start(args, msg);
6587                 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
6588                         buf[sizeof(buf) - 1] = 0;
6589                         buf[sizeof(buf) - 2] = '.';
6590                         buf[sizeof(buf) - 3] = '.';
6591                         buf[sizeof(buf) - 4] = '.';
6592                 }
6593                 va_end(args);
6594                 die("%s", buf);
6595         }
6597         if (!status_empty || *msg) {
6598                 va_list args;
6600                 va_start(args, msg);
6602                 wmove(status_win, 0, 0);
6603                 if (view->has_scrolled && use_scroll_status_wclear)
6604                         wclear(status_win);
6605                 if (*msg) {
6606                         vwprintw(status_win, msg, args);
6607                         status_empty = FALSE;
6608                 } else {
6609                         status_empty = TRUE;
6610                 }
6611                 wclrtoeol(status_win);
6612                 wnoutrefresh(status_win);
6614                 va_end(args);
6615         }
6617         update_view_title(view);
6620 /* Controls when nodelay should be in effect when polling user input. */
6621 static void
6622 set_nonblocking_input(bool loading)
6624         static unsigned int loading_views;
6626         if ((loading == FALSE && loading_views-- == 1) ||
6627             (loading == TRUE  && loading_views++ == 0))
6628                 nodelay(status_win, loading);
6631 static void
6632 init_display(void)
6634         const char *term;
6635         int x, y;
6637         /* Initialize the curses library */
6638         if (isatty(STDIN_FILENO)) {
6639                 cursed = !!initscr();
6640                 opt_tty = stdin;
6641         } else {
6642                 /* Leave stdin and stdout alone when acting as a pager. */
6643                 opt_tty = fopen("/dev/tty", "r+");
6644                 if (!opt_tty)
6645                         die("Failed to open /dev/tty");
6646                 cursed = !!newterm(NULL, opt_tty, opt_tty);
6647         }
6649         if (!cursed)
6650                 die("Failed to initialize curses");
6652         nonl();         /* Disable conversion and detect newlines from input. */
6653         cbreak();       /* Take input chars one at a time, no wait for \n */
6654         noecho();       /* Don't echo input */
6655         leaveok(stdscr, FALSE);
6657         if (has_colors())
6658                 init_colors();
6660         getmaxyx(stdscr, y, x);
6661         status_win = newwin(1, 0, y - 1, 0);
6662         if (!status_win)
6663                 die("Failed to create status window");
6665         /* Enable keyboard mapping */
6666         keypad(status_win, TRUE);
6667         wbkgdset(status_win, get_line_attr(LINE_STATUS));
6669         TABSIZE = opt_tab_size;
6670         if (opt_line_graphics) {
6671                 line_graphics[LINE_GRAPHIC_VLINE] = ACS_VLINE;
6672         }
6674         term = getenv("XTERM_VERSION") ? NULL : getenv("COLORTERM");
6675         if (term && !strcmp(term, "gnome-terminal")) {
6676                 /* In the gnome-terminal-emulator, the message from
6677                  * scrolling up one line when impossible followed by
6678                  * scrolling down one line causes corruption of the
6679                  * status line. This is fixed by calling wclear. */
6680                 use_scroll_status_wclear = TRUE;
6681                 use_scroll_redrawwin = FALSE;
6683         } else if (term && !strcmp(term, "xrvt-xpm")) {
6684                 /* No problems with full optimizations in xrvt-(unicode)
6685                  * and aterm. */
6686                 use_scroll_status_wclear = use_scroll_redrawwin = FALSE;
6688         } else {
6689                 /* When scrolling in (u)xterm the last line in the
6690                  * scrolling direction will update slowly. */
6691                 use_scroll_redrawwin = TRUE;
6692                 use_scroll_status_wclear = FALSE;
6693         }
6696 static int
6697 get_input(int prompt_position)
6699         struct view *view;
6700         int i, key, cursor_y, cursor_x;
6702         if (prompt_position)
6703                 input_mode = TRUE;
6705         while (TRUE) {
6706                 foreach_view (view, i) {
6707                         update_view(view);
6708                         if (view_is_displayed(view) && view->has_scrolled &&
6709                             use_scroll_redrawwin)
6710                                 redrawwin(view->win);
6711                         view->has_scrolled = FALSE;
6712                 }
6714                 /* Update the cursor position. */
6715                 if (prompt_position) {
6716                         getbegyx(status_win, cursor_y, cursor_x);
6717                         cursor_x = prompt_position;
6718                 } else {
6719                         view = display[current_view];
6720                         getbegyx(view->win, cursor_y, cursor_x);
6721                         cursor_x = view->width - 1;
6722                         cursor_y += view->lineno - view->offset;
6723                 }
6724                 setsyx(cursor_y, cursor_x);
6726                 /* Refresh, accept single keystroke of input */
6727                 doupdate();
6728                 key = wgetch(status_win);
6730                 /* wgetch() with nodelay() enabled returns ERR when
6731                  * there's no input. */
6732                 if (key == ERR) {
6734                 } else if (key == KEY_RESIZE) {
6735                         int height, width;
6737                         getmaxyx(stdscr, height, width);
6739                         wresize(status_win, 1, width);
6740                         mvwin(status_win, height - 1, 0);
6741                         wnoutrefresh(status_win);
6742                         resize_display();
6743                         redraw_display(TRUE);
6745                 } else {
6746                         input_mode = FALSE;
6747                         return key;
6748                 }
6749         }
6752 static char *
6753 prompt_input(const char *prompt, input_handler handler, void *data)
6755         enum input_status status = INPUT_OK;
6756         static char buf[SIZEOF_STR];
6757         size_t pos = 0;
6759         buf[pos] = 0;
6761         while (status == INPUT_OK || status == INPUT_SKIP) {
6762                 int key;
6764                 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
6765                 wclrtoeol(status_win);
6767                 key = get_input(pos + 1);
6768                 switch (key) {
6769                 case KEY_RETURN:
6770                 case KEY_ENTER:
6771                 case '\n':
6772                         status = pos ? INPUT_STOP : INPUT_CANCEL;
6773                         break;
6775                 case KEY_BACKSPACE:
6776                         if (pos > 0)
6777                                 buf[--pos] = 0;
6778                         else
6779                                 status = INPUT_CANCEL;
6780                         break;
6782                 case KEY_ESC:
6783                         status = INPUT_CANCEL;
6784                         break;
6786                 default:
6787                         if (pos >= sizeof(buf)) {
6788                                 report("Input string too long");
6789                                 return NULL;
6790                         }
6792                         status = handler(data, buf, key);
6793                         if (status == INPUT_OK)
6794                                 buf[pos++] = (char) key;
6795                 }
6796         }
6798         /* Clear the status window */
6799         status_empty = FALSE;
6800         report("");
6802         if (status == INPUT_CANCEL)
6803                 return NULL;
6805         buf[pos++] = 0;
6807         return buf;
6810 static enum input_status
6811 prompt_yesno_handler(void *data, char *buf, int c)
6813         if (c == 'y' || c == 'Y')
6814                 return INPUT_STOP;
6815         if (c == 'n' || c == 'N')
6816                 return INPUT_CANCEL;
6817         return INPUT_SKIP;
6820 static bool
6821 prompt_yesno(const char *prompt)
6823         char prompt2[SIZEOF_STR];
6825         if (!string_format(prompt2, "%s [Yy/Nn]", prompt))
6826                 return FALSE;
6828         return !!prompt_input(prompt2, prompt_yesno_handler, NULL);
6831 static enum input_status
6832 read_prompt_handler(void *data, char *buf, int c)
6834         return isprint(c) ? INPUT_OK : INPUT_SKIP;
6837 static char *
6838 read_prompt(const char *prompt)
6840         return prompt_input(prompt, read_prompt_handler, NULL);
6843 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected)
6845         enum input_status status = INPUT_OK;
6846         int size = 0;
6848         while (items[size].text)
6849                 size++;
6851         while (status == INPUT_OK) {
6852                 const struct menu_item *item = &items[*selected];
6853                 int key;
6854                 int i;
6856                 mvwprintw(status_win, 0, 0, "%s (%d of %d) ",
6857                           prompt, *selected + 1, size);
6858                 if (item->hotkey)
6859                         wprintw(status_win, "[%c] ", (char) item->hotkey);
6860                 wprintw(status_win, "%s", item->text);
6861                 wclrtoeol(status_win);
6863                 key = get_input(COLS - 1);
6864                 switch (key) {
6865                 case KEY_RETURN:
6866                 case KEY_ENTER:
6867                 case '\n':
6868                         status = INPUT_STOP;
6869                         break;
6871                 case KEY_LEFT:
6872                 case KEY_UP:
6873                         *selected = *selected - 1;
6874                         if (*selected < 0)
6875                                 *selected = size - 1;
6876                         break;
6878                 case KEY_RIGHT:
6879                 case KEY_DOWN:
6880                         *selected = (*selected + 1) % size;
6881                         break;
6883                 case KEY_ESC:
6884                         status = INPUT_CANCEL;
6885                         break;
6887                 default:
6888                         for (i = 0; items[i].text; i++)
6889                                 if (items[i].hotkey == key) {
6890                                         *selected = i;
6891                                         status = INPUT_STOP;
6892                                         break;
6893                                 }
6894                 }
6895         }
6897         /* Clear the status window */
6898         status_empty = FALSE;
6899         report("");
6901         return status != INPUT_CANCEL;
6904 /*
6905  * Repository properties
6906  */
6908 static struct ref **refs = NULL;
6909 static size_t refs_size = 0;
6911 static struct ref_list **ref_lists = NULL;
6912 static size_t ref_lists_size = 0;
6914 DEFINE_ALLOCATOR(realloc_refs, struct ref *, 256)
6915 DEFINE_ALLOCATOR(realloc_refs_list, struct ref *, 8)
6916 DEFINE_ALLOCATOR(realloc_ref_lists, struct ref_list *, 8)
6918 static int
6919 compare_refs(const void *ref1_, const void *ref2_)
6921         const struct ref *ref1 = *(const struct ref **)ref1_;
6922         const struct ref *ref2 = *(const struct ref **)ref2_;
6924         if (ref1->tag != ref2->tag)
6925                 return ref2->tag - ref1->tag;
6926         if (ref1->ltag != ref2->ltag)
6927                 return ref2->ltag - ref2->ltag;
6928         if (ref1->head != ref2->head)
6929                 return ref2->head - ref1->head;
6930         if (ref1->tracked != ref2->tracked)
6931                 return ref2->tracked - ref1->tracked;
6932         if (ref1->remote != ref2->remote)
6933                 return ref2->remote - ref1->remote;
6934         return strcmp(ref1->name, ref2->name);
6937 static void
6938 foreach_ref(bool (*visitor)(void *data, struct ref *ref), void *data)
6940         size_t i;
6942         for (i = 0; i < refs_size; i++)
6943                 if (!visitor(data, refs[i]))
6944                         break;
6947 static struct ref_list *
6948 get_ref_list(const char *id)
6950         struct ref_list *list;
6951         size_t i;
6953         for (i = 0; i < ref_lists_size; i++)
6954                 if (!strcmp(id, ref_lists[i]->id))
6955                         return ref_lists[i];
6957         if (!realloc_ref_lists(&ref_lists, ref_lists_size, 1))
6958                 return NULL;
6959         list = calloc(1, sizeof(*list));
6960         if (!list)
6961                 return NULL;
6963         for (i = 0; i < refs_size; i++) {
6964                 if (!strcmp(id, refs[i]->id) &&
6965                     realloc_refs_list(&list->refs, list->size, 1))
6966                         list->refs[list->size++] = refs[i];
6967         }
6969         if (!list->refs) {
6970                 free(list);
6971                 return NULL;
6972         }
6974         qsort(list->refs, list->size, sizeof(*list->refs), compare_refs);
6975         ref_lists[ref_lists_size++] = list;
6976         return list;
6979 static int
6980 read_ref(char *id, size_t idlen, char *name, size_t namelen)
6982         struct ref *ref = NULL;
6983         bool tag = FALSE;
6984         bool ltag = FALSE;
6985         bool remote = FALSE;
6986         bool tracked = FALSE;
6987         bool head = FALSE;
6988         int from = 0, to = refs_size - 1;
6990         if (!prefixcmp(name, "refs/tags/")) {
6991                 if (!suffixcmp(name, namelen, "^{}")) {
6992                         namelen -= 3;
6993                         name[namelen] = 0;
6994                 } else {
6995                         ltag = TRUE;
6996                 }
6998                 tag = TRUE;
6999                 namelen -= STRING_SIZE("refs/tags/");
7000                 name    += STRING_SIZE("refs/tags/");
7002         } else if (!prefixcmp(name, "refs/remotes/")) {
7003                 remote = TRUE;
7004                 namelen -= STRING_SIZE("refs/remotes/");
7005                 name    += STRING_SIZE("refs/remotes/");
7006                 tracked  = !strcmp(opt_remote, name);
7008         } else if (!prefixcmp(name, "refs/heads/")) {
7009                 namelen -= STRING_SIZE("refs/heads/");
7010                 name    += STRING_SIZE("refs/heads/");
7011                 head     = !strncmp(opt_head, name, namelen);
7013         } else if (!strcmp(name, "HEAD")) {
7014                 string_ncopy(opt_head_rev, id, idlen);
7015                 return OK;
7016         }
7018         /* If we are reloading or it's an annotated tag, replace the
7019          * previous SHA1 with the resolved commit id; relies on the fact
7020          * git-ls-remote lists the commit id of an annotated tag right
7021          * before the commit id it points to. */
7022         while (from <= to) {
7023                 size_t pos = (to + from) / 2;
7024                 int cmp = strcmp(name, refs[pos]->name);
7026                 if (!cmp) {
7027                         ref = refs[pos];
7028                         break;
7029                 }
7031                 if (cmp < 0)
7032                         to = pos - 1;
7033                 else
7034                         from = pos + 1;
7035         }
7037         if (!ref) {
7038                 if (!realloc_refs(&refs, refs_size, 1))
7039                         return ERR;
7040                 ref = calloc(1, sizeof(*ref) + namelen);
7041                 if (!ref)
7042                         return ERR;
7043                 memmove(refs + from + 1, refs + from,
7044                         (refs_size - from) * sizeof(*refs));
7045                 refs[from] = ref;
7046                 strncpy(ref->name, name, namelen);
7047                 refs_size++;
7048         }
7050         ref->head = head;
7051         ref->tag = tag;
7052         ref->ltag = ltag;
7053         ref->remote = remote;
7054         ref->tracked = tracked;
7055         string_copy_rev(ref->id, id);
7057         return OK;
7060 static int
7061 load_refs(void)
7063         const char *head_argv[] = {
7064                 "git", "symbolic-ref", "HEAD", NULL
7065         };
7066         static const char *ls_remote_argv[SIZEOF_ARG] = {
7067                 "git", "ls-remote", opt_git_dir, NULL
7068         };
7069         static bool init = FALSE;
7070         size_t i;
7072         if (!init) {
7073                 argv_from_env(ls_remote_argv, "TIG_LS_REMOTE");
7074                 init = TRUE;
7075         }
7077         if (!*opt_git_dir)
7078                 return OK;
7080         if (run_io_buf(head_argv, opt_head, sizeof(opt_head)) &&
7081             !prefixcmp(opt_head, "refs/heads/")) {
7082                 char *offset = opt_head + STRING_SIZE("refs/heads/");
7084                 memmove(opt_head, offset, strlen(offset) + 1);
7085         }
7087         for (i = 0; i < refs_size; i++)
7088                 refs[i]->id[0] = 0;
7090         if (run_io_load(ls_remote_argv, "\t", read_ref) == ERR)
7091                 return ERR;
7093         /* Update the ref lists to reflect changes. */
7094         for (i = 0; i < ref_lists_size; i++) {
7095                 struct ref_list *list = ref_lists[i];
7096                 size_t old, new;
7098                 for (old = new = 0; old < list->size; old++)
7099                         if (!strcmp(list->id, list->refs[old]->id))
7100                                 list->refs[new++] = list->refs[old];
7101                 list->size = new;
7102         }
7104         return OK;
7107 static void
7108 set_remote_branch(const char *name, const char *value, size_t valuelen)
7110         if (!strcmp(name, ".remote")) {
7111                 string_ncopy(opt_remote, value, valuelen);
7113         } else if (*opt_remote && !strcmp(name, ".merge")) {
7114                 size_t from = strlen(opt_remote);
7116                 if (!prefixcmp(value, "refs/heads/"))
7117                         value += STRING_SIZE("refs/heads/");
7119                 if (!string_format_from(opt_remote, &from, "/%s", value))
7120                         opt_remote[0] = 0;
7121         }
7124 static void
7125 set_repo_config_option(char *name, char *value, int (*cmd)(int, const char **))
7127         const char *argv[SIZEOF_ARG] = { name, "=" };
7128         int argc = 1 + (cmd == option_set_command);
7129         int error = ERR;
7131         if (!argv_from_string(argv, &argc, value))
7132                 config_msg = "Too many option arguments";
7133         else
7134                 error = cmd(argc, argv);
7136         if (error == ERR)
7137                 warn("Option 'tig.%s': %s", name, config_msg);
7140 static bool
7141 set_environment_variable(const char *name, const char *value)
7143         size_t len = strlen(name) + 1 + strlen(value) + 1;
7144         char *env = malloc(len);
7146         if (env &&
7147             string_nformat(env, len, NULL, "%s=%s", name, value) &&
7148             putenv(env) == 0)
7149                 return TRUE;
7150         free(env);
7151         return FALSE;
7154 static void
7155 set_work_tree(const char *value)
7157         char cwd[SIZEOF_STR];
7159         if (!getcwd(cwd, sizeof(cwd)))
7160                 die("Failed to get cwd path: %s", strerror(errno));
7161         if (chdir(opt_git_dir) < 0)
7162                 die("Failed to chdir(%s): %s", strerror(errno));
7163         if (!getcwd(opt_git_dir, sizeof(opt_git_dir)))
7164                 die("Failed to get git path: %s", strerror(errno));
7165         if (chdir(cwd) < 0)
7166                 die("Failed to chdir(%s): %s", cwd, strerror(errno));
7167         if (chdir(value) < 0)
7168                 die("Failed to chdir(%s): %s", value, strerror(errno));
7169         if (!getcwd(cwd, sizeof(cwd)))
7170                 die("Failed to get cwd path: %s", strerror(errno));
7171         if (!set_environment_variable("GIT_WORK_TREE", cwd))
7172                 die("Failed to set GIT_WORK_TREE to '%s'", cwd);
7173         if (!set_environment_variable("GIT_DIR", opt_git_dir))
7174                 die("Failed to set GIT_DIR to '%s'", opt_git_dir);
7175         opt_is_inside_work_tree = TRUE;
7178 static int
7179 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
7181         if (!strcmp(name, "i18n.commitencoding"))
7182                 string_ncopy(opt_encoding, value, valuelen);
7184         else if (!strcmp(name, "core.editor"))
7185                 string_ncopy(opt_editor, value, valuelen);
7187         else if (!strcmp(name, "core.worktree"))
7188                 set_work_tree(value);
7190         else if (!prefixcmp(name, "tig.color."))
7191                 set_repo_config_option(name + 10, value, option_color_command);
7193         else if (!prefixcmp(name, "tig.bind."))
7194                 set_repo_config_option(name + 9, value, option_bind_command);
7196         else if (!prefixcmp(name, "tig."))
7197                 set_repo_config_option(name + 4, value, option_set_command);
7199         else if (*opt_head && !prefixcmp(name, "branch.") &&
7200                  !strncmp(name + 7, opt_head, strlen(opt_head)))
7201                 set_remote_branch(name + 7 + strlen(opt_head), value, valuelen);
7203         return OK;
7206 static int
7207 load_git_config(void)
7209         const char *config_list_argv[] = { "git", GIT_CONFIG, "--list", NULL };
7211         return run_io_load(config_list_argv, "=", read_repo_config_option);
7214 static int
7215 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
7217         if (!opt_git_dir[0]) {
7218                 string_ncopy(opt_git_dir, name, namelen);
7220         } else if (opt_is_inside_work_tree == -1) {
7221                 /* This can be 3 different values depending on the
7222                  * version of git being used. If git-rev-parse does not
7223                  * understand --is-inside-work-tree it will simply echo
7224                  * the option else either "true" or "false" is printed.
7225                  * Default to true for the unknown case. */
7226                 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
7228         } else if (*name == '.') {
7229                 string_ncopy(opt_cdup, name, namelen);
7231         } else {
7232                 string_ncopy(opt_prefix, name, namelen);
7233         }
7235         return OK;
7238 static int
7239 load_repo_info(void)
7241         const char *rev_parse_argv[] = {
7242                 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
7243                         "--show-cdup", "--show-prefix", NULL
7244         };
7246         return run_io_load(rev_parse_argv, "=", read_repo_info);
7250 /*
7251  * Main
7252  */
7254 static const char usage[] =
7255 "tig " TIG_VERSION " (" __DATE__ ")\n"
7256 "\n"
7257 "Usage: tig        [options] [revs] [--] [paths]\n"
7258 "   or: tig show   [options] [revs] [--] [paths]\n"
7259 "   or: tig blame  [rev] path\n"
7260 "   or: tig status\n"
7261 "   or: tig <      [git command output]\n"
7262 "\n"
7263 "Options:\n"
7264 "  -v, --version   Show version and exit\n"
7265 "  -h, --help      Show help message and exit";
7267 static void __NORETURN
7268 quit(int sig)
7270         /* XXX: Restore tty modes and let the OS cleanup the rest! */
7271         if (cursed)
7272                 endwin();
7273         exit(0);
7276 static void __NORETURN
7277 die(const char *err, ...)
7279         va_list args;
7281         endwin();
7283         va_start(args, err);
7284         fputs("tig: ", stderr);
7285         vfprintf(stderr, err, args);
7286         fputs("\n", stderr);
7287         va_end(args);
7289         exit(1);
7292 static void
7293 warn(const char *msg, ...)
7295         va_list args;
7297         va_start(args, msg);
7298         fputs("tig warning: ", stderr);
7299         vfprintf(stderr, msg, args);
7300         fputs("\n", stderr);
7301         va_end(args);
7304 static enum request
7305 parse_options(int argc, const char *argv[])
7307         enum request request = REQ_VIEW_MAIN;
7308         const char *subcommand;
7309         bool seen_dashdash = FALSE;
7310         /* XXX: This is vulnerable to the user overriding options
7311          * required for the main view parser. */
7312         const char *custom_argv[SIZEOF_ARG] = {
7313                 "git", "log", "--no-color", "--pretty=raw", "--parents",
7314                         "--topo-order", NULL
7315         };
7316         int i, j = 6;
7318         if (!isatty(STDIN_FILENO)) {
7319                 io_open(&VIEW(REQ_VIEW_PAGER)->io, "");
7320                 return REQ_VIEW_PAGER;
7321         }
7323         if (argc <= 1)
7324                 return REQ_NONE;
7326         subcommand = argv[1];
7327         if (!strcmp(subcommand, "status")) {
7328                 if (argc > 2)
7329                         warn("ignoring arguments after `%s'", subcommand);
7330                 return REQ_VIEW_STATUS;
7332         } else if (!strcmp(subcommand, "blame")) {
7333                 if (argc <= 2 || argc > 4)
7334                         die("invalid number of options to blame\n\n%s", usage);
7336                 i = 2;
7337                 if (argc == 4) {
7338                         string_ncopy(opt_ref, argv[i], strlen(argv[i]));
7339                         i++;
7340                 }
7342                 string_ncopy(opt_file, argv[i], strlen(argv[i]));
7343                 return REQ_VIEW_BLAME;
7345         } else if (!strcmp(subcommand, "show")) {
7346                 request = REQ_VIEW_DIFF;
7348         } else {
7349                 subcommand = NULL;
7350         }
7352         if (subcommand) {
7353                 custom_argv[1] = subcommand;
7354                 j = 2;
7355         }
7357         for (i = 1 + !!subcommand; i < argc; i++) {
7358                 const char *opt = argv[i];
7360                 if (seen_dashdash || !strcmp(opt, "--")) {
7361                         seen_dashdash = TRUE;
7363                 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
7364                         printf("tig version %s\n", TIG_VERSION);
7365                         quit(0);
7367                 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
7368                         printf("%s\n", usage);
7369                         quit(0);
7370                 }
7372                 custom_argv[j++] = opt;
7373                 if (j >= ARRAY_SIZE(custom_argv))
7374                         die("command too long");
7375         }
7377         if (!prepare_update(VIEW(request), custom_argv, NULL, FORMAT_NONE))                                                                        
7378                 die("Failed to format arguments"); 
7380         return request;
7383 int
7384 main(int argc, const char *argv[])
7386         enum request request = parse_options(argc, argv);
7387         struct view *view;
7388         size_t i;
7390         signal(SIGINT, quit);
7391         signal(SIGPIPE, SIG_IGN);
7393         if (setlocale(LC_ALL, "")) {
7394                 char *codeset = nl_langinfo(CODESET);
7396                 string_ncopy(opt_codeset, codeset, strlen(codeset));
7397         }
7399         if (load_repo_info() == ERR)
7400                 die("Failed to load repo info.");
7402         if (load_options() == ERR)
7403                 die("Failed to load user config.");
7405         if (load_git_config() == ERR)
7406                 die("Failed to load repo config.");
7408         /* Require a git repository unless when running in pager mode. */
7409         if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
7410                 die("Not a git repository");
7412         if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
7413                 opt_utf8 = FALSE;
7415         if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
7416                 opt_iconv = iconv_open(opt_codeset, opt_encoding);
7417                 if (opt_iconv == ICONV_NONE)
7418                         die("Failed to initialize character set conversion");
7419         }
7421         if (load_refs() == ERR)
7422                 die("Failed to load refs.");
7424         foreach_view (view, i)
7425                 argv_from_env(view->ops->argv, view->cmd_env);
7427         init_display();
7429         if (request != REQ_NONE)
7430                 open_view(NULL, request, OPEN_PREPARED);
7431         request = request == REQ_NONE ? REQ_VIEW_MAIN : REQ_NONE;
7433         while (view_driver(display[current_view], request)) {
7434                 int key = get_input(0);
7436                 view = display[current_view];
7437                 request = get_keybinding(view->keymap, key);
7439                 /* Some low-level request handling. This keeps access to
7440                  * status_win restricted. */
7441                 switch (request) {
7442                 case REQ_PROMPT:
7443                 {
7444                         char *cmd = read_prompt(":");
7446                         if (cmd && isdigit(*cmd)) {
7447                                 int lineno = view->lineno + 1;
7449                                 if (parse_int(&lineno, cmd, 1, view->lines + 1) == OK) {
7450                                         select_view_line(view, lineno - 1);
7451                                         report("");
7452                                 } else {
7453                                         report("Unable to parse '%s' as a line number", cmd);
7454                                 }
7456                         } else if (cmd) {
7457                                 struct view *next = VIEW(REQ_VIEW_PAGER);
7458                                 const char *argv[SIZEOF_ARG] = { "git" };
7459                                 int argc = 1;
7461                                 /* When running random commands, initially show the
7462                                  * command in the title. However, it maybe later be
7463                                  * overwritten if a commit line is selected. */
7464                                 string_ncopy(next->ref, cmd, strlen(cmd));
7466                                 if (!argv_from_string(argv, &argc, cmd)) {
7467                                         report("Too many arguments");
7468                                 } else if (!prepare_update(next, argv, NULL, FORMAT_DASH)) {
7469                                         report("Failed to format command");
7470                                 } else {
7471                                         open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
7472                                 }
7473                         }
7475                         request = REQ_NONE;
7476                         break;
7477                 }
7478                 case REQ_SEARCH:
7479                 case REQ_SEARCH_BACK:
7480                 {
7481                         const char *prompt = request == REQ_SEARCH ? "/" : "?";
7482                         char *search = read_prompt(prompt);
7484                         if (search)
7485                                 string_ncopy(opt_search, search, strlen(search));
7486                         else if (*opt_search)
7487                                 request = request == REQ_SEARCH ?
7488                                         REQ_FIND_NEXT :
7489                                         REQ_FIND_PREV;
7490                         else
7491                                 request = REQ_NONE;
7492                         break;
7493                 }
7494                 default:
7495                         break;
7496                 }
7497         }
7499         quit(0);
7501         return 0;