Code

Remove macros which are only used for default option values
[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 ID_COLS         8
109 #define MIN_VIEW_HEIGHT 4
111 #define NULL_ID         "0000000000000000000000000000000000000000"
113 #define S_ISGITLINK(mode) (((mode) & S_IFMT) == 0160000)
115 #ifndef GIT_CONFIG
116 #define GIT_CONFIG "config"
117 #endif
119 /* Some ASCII-shorthands fitted into the ncurses namespace. */
120 #define KEY_TAB         '\t'
121 #define KEY_RETURN      '\r'
122 #define KEY_ESC         27
125 struct ref {
126         char id[SIZEOF_REV];    /* Commit SHA1 ID */
127         unsigned int head:1;    /* Is it the current HEAD? */
128         unsigned int tag:1;     /* Is it a tag? */
129         unsigned int ltag:1;    /* If so, is the tag local? */
130         unsigned int remote:1;  /* Is it a remote ref? */
131         unsigned int tracked:1; /* Is it the remote for the current HEAD? */
132         char name[1];           /* Ref name; tag or head names are shortened. */
133 };
135 struct ref_list {
136         char id[SIZEOF_REV];    /* Commit SHA1 ID */
137         size_t size;            /* Number of refs. */
138         struct ref **refs;      /* References for this ID. */
139 };
141 static struct ref_list *get_ref_list(const char *id);
142 static void foreach_ref(bool (*visitor)(void *data, struct ref *ref), void *data);
143 static int load_refs(void);
145 enum format_flags {
146         FORMAT_ALL,             /* Perform replacement in all arguments. */
147         FORMAT_DASH,            /* Perform replacement up until "--". */
148         FORMAT_NONE             /* No replacement should be performed. */
149 };
151 static bool format_argv(const char *dst[], const char *src[], enum format_flags flags);
153 enum input_status {
154         INPUT_OK,
155         INPUT_SKIP,
156         INPUT_STOP,
157         INPUT_CANCEL
158 };
160 typedef enum input_status (*input_handler)(void *data, char *buf, int c);
162 static char *prompt_input(const char *prompt, input_handler handler, void *data);
163 static bool prompt_yesno(const char *prompt);
165 struct menu_item {
166         int hotkey;
167         const char *text;
168         void *data;
169 };
171 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected);
173 /*
174  * Allocation helpers ... Entering macro hell to never be seen again.
175  */
177 #define DEFINE_ALLOCATOR(name, type, chunk_size)                                \
178 static type *                                                                   \
179 name(type **mem, size_t size, size_t increase)                                  \
180 {                                                                               \
181         size_t num_chunks = (size + chunk_size - 1) / chunk_size;               \
182         size_t num_chunks_new = (size + increase + chunk_size - 1) / chunk_size;\
183         type *tmp = *mem;                                                       \
184                                                                                 \
185         if (mem == NULL || num_chunks != num_chunks_new) {                      \
186                 tmp = realloc(tmp, num_chunks_new * chunk_size * sizeof(type)); \
187                 if (tmp)                                                        \
188                         *mem = tmp;                                             \
189         }                                                                       \
190                                                                                 \
191         return tmp;                                                             \
194 /*
195  * String helpers
196  */
198 static inline void
199 string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t srclen)
201         if (srclen > dstlen - 1)
202                 srclen = dstlen - 1;
204         strncpy(dst, src, srclen);
205         dst[srclen] = 0;
208 /* Shorthands for safely copying into a fixed buffer. */
210 #define string_copy(dst, src) \
211         string_ncopy_do(dst, sizeof(dst), src, sizeof(src))
213 #define string_ncopy(dst, src, srclen) \
214         string_ncopy_do(dst, sizeof(dst), src, srclen)
216 #define string_copy_rev(dst, src) \
217         string_ncopy_do(dst, SIZEOF_REV, src, SIZEOF_REV - 1)
219 #define string_add(dst, from, src) \
220         string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src))
222 static void
223 string_expand(char *dst, size_t dstlen, const char *src, int tabsize)
225         size_t size, pos;
227         for (size = pos = 0; size < dstlen - 1 && src[pos]; pos++) {
228                 if (src[pos] == '\t') {
229                         size_t expanded = tabsize - (size % tabsize);
231                         if (expanded + size >= dstlen - 1)
232                                 expanded = dstlen - size - 1;
233                         memcpy(dst + size, "        ", expanded);
234                         size += expanded;
235                 } else {
236                         dst[size++] = src[pos];
237                 }
238         }
240         dst[size] = 0;
243 static char *
244 chomp_string(char *name)
246         int namelen;
248         while (isspace(*name))
249                 name++;
251         namelen = strlen(name) - 1;
252         while (namelen > 0 && isspace(name[namelen]))
253                 name[namelen--] = 0;
255         return name;
258 static bool
259 string_nformat(char *buf, size_t bufsize, size_t *bufpos, const char *fmt, ...)
261         va_list args;
262         size_t pos = bufpos ? *bufpos : 0;
264         va_start(args, fmt);
265         pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
266         va_end(args);
268         if (bufpos)
269                 *bufpos = pos;
271         return pos >= bufsize ? FALSE : TRUE;
274 #define string_format(buf, fmt, args...) \
275         string_nformat(buf, sizeof(buf), NULL, fmt, args)
277 #define string_format_from(buf, from, fmt, args...) \
278         string_nformat(buf, sizeof(buf), from, fmt, args)
280 static int
281 string_enum_compare(const char *str1, const char *str2, int len)
283         size_t i;
285 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
287         /* Diff-Header == DIFF_HEADER */
288         for (i = 0; i < len; i++) {
289                 if (toupper(str1[i]) == toupper(str2[i]))
290                         continue;
292                 if (string_enum_sep(str1[i]) &&
293                     string_enum_sep(str2[i]))
294                         continue;
296                 return str1[i] - str2[i];
297         }
299         return 0;
302 struct enum_map {
303         const char *name;
304         int namelen;
305         int value;
306 };
308 #define ENUM_MAP(name, value) { name, STRING_SIZE(name), value }
310 static bool
311 map_enum_do(const struct enum_map *map, size_t map_size, int *value, const char *name)
313         size_t namelen = strlen(name);
314         int i;
316         for (i = 0; i < map_size; i++)
317                 if (namelen == map[i].namelen &&
318                     !string_enum_compare(name, map[i].name, namelen)) {
319                         *value = map[i].value;
320                         return TRUE;
321                 }
323         return FALSE;
326 #define map_enum(attr, map, name) \
327         map_enum_do(map, ARRAY_SIZE(map), attr, name)
329 #define prefixcmp(str1, str2) \
330         strncmp(str1, str2, STRING_SIZE(str2))
332 static inline int
333 suffixcmp(const char *str, int slen, const char *suffix)
335         size_t len = slen >= 0 ? slen : strlen(str);
336         size_t suffixlen = strlen(suffix);
338         return suffixlen < len ? strcmp(str + len - suffixlen, suffix) : -1;
342 static const char *
343 mkdate(const time_t *time)
345         static char buf[DATE_COLS + 1];
346         struct tm tm;
348         gmtime_r(time, &tm);
349         return strftime(buf, sizeof(buf), DATE_FORMAT, &tm) ? buf : NULL;
353 static bool
354 argv_from_string(const char *argv[SIZEOF_ARG], int *argc, char *cmd)
356         int valuelen;
358         while (*cmd && *argc < SIZEOF_ARG && (valuelen = strcspn(cmd, " \t"))) {
359                 bool advance = cmd[valuelen] != 0;
361                 cmd[valuelen] = 0;
362                 argv[(*argc)++] = chomp_string(cmd);
363                 cmd = chomp_string(cmd + valuelen + advance);
364         }
366         if (*argc < SIZEOF_ARG)
367                 argv[*argc] = NULL;
368         return *argc < SIZEOF_ARG;
371 static void
372 argv_from_env(const char **argv, const char *name)
374         char *env = argv ? getenv(name) : NULL;
375         int argc = 0;
377         if (env && *env)
378                 env = strdup(env);
379         if (env && !argv_from_string(argv, &argc, env))
380                 die("Too many arguments in the `%s` environment variable", name);
384 /*
385  * Executing external commands.
386  */
388 enum io_type {
389         IO_FD,                  /* File descriptor based IO. */
390         IO_BG,                  /* Execute command in the background. */
391         IO_FG,                  /* Execute command with same std{in,out,err}. */
392         IO_RD,                  /* Read only fork+exec IO. */
393         IO_WR,                  /* Write only fork+exec IO. */
394         IO_AP,                  /* Append fork+exec output to file. */
395 };
397 struct io {
398         enum io_type type;      /* The requested type of pipe. */
399         const char *dir;        /* Directory from which to execute. */
400         pid_t pid;              /* Pipe for reading or writing. */
401         int pipe;               /* Pipe end for reading or writing. */
402         int error;              /* Error status. */
403         const char *argv[SIZEOF_ARG];   /* Shell command arguments. */
404         char *buf;              /* Read buffer. */
405         size_t bufalloc;        /* Allocated buffer size. */
406         size_t bufsize;         /* Buffer content size. */
407         char *bufpos;           /* Current buffer position. */
408         unsigned int eof:1;     /* Has end of file been reached. */
409 };
411 static void
412 reset_io(struct io *io)
414         io->pipe = -1;
415         io->pid = 0;
416         io->buf = io->bufpos = NULL;
417         io->bufalloc = io->bufsize = 0;
418         io->error = 0;
419         io->eof = 0;
422 static void
423 init_io(struct io *io, const char *dir, enum io_type type)
425         reset_io(io);
426         io->type = type;
427         io->dir = dir;
430 static bool
431 init_io_rd(struct io *io, const char *argv[], const char *dir,
432                 enum format_flags flags)
434         init_io(io, dir, IO_RD);
435         return format_argv(io->argv, argv, flags);
438 static bool
439 io_open(struct io *io, const char *name)
441         init_io(io, NULL, IO_FD);
442         io->pipe = *name ? open(name, O_RDONLY) : STDIN_FILENO;
443         if (io->pipe == -1)
444                 io->error = errno;
445         return io->pipe != -1;
448 static bool
449 kill_io(struct io *io)
451         return io->pid == 0 || kill(io->pid, SIGKILL) != -1;
454 static bool
455 done_io(struct io *io)
457         pid_t pid = io->pid;
459         if (io->pipe != -1)
460                 close(io->pipe);
461         free(io->buf);
462         reset_io(io);
464         while (pid > 0) {
465                 int status;
466                 pid_t waiting = waitpid(pid, &status, 0);
468                 if (waiting < 0) {
469                         if (errno == EINTR)
470                                 continue;
471                         report("waitpid failed (%s)", strerror(errno));
472                         return FALSE;
473                 }
475                 return waiting == pid &&
476                        !WIFSIGNALED(status) &&
477                        WIFEXITED(status) &&
478                        !WEXITSTATUS(status);
479         }
481         return TRUE;
484 static bool
485 start_io(struct io *io)
487         int pipefds[2] = { -1, -1 };
489         if (io->type == IO_FD)
490                 return TRUE;
492         if ((io->type == IO_RD || io->type == IO_WR) &&
493             pipe(pipefds) < 0)
494                 return FALSE;
495         else if (io->type == IO_AP)
496                 pipefds[1] = io->pipe;
498         if ((io->pid = fork())) {
499                 if (pipefds[!(io->type == IO_WR)] != -1)
500                         close(pipefds[!(io->type == IO_WR)]);
501                 if (io->pid != -1) {
502                         io->pipe = pipefds[!!(io->type == IO_WR)];
503                         return TRUE;
504                 }
506         } else {
507                 if (io->type != IO_FG) {
508                         int devnull = open("/dev/null", O_RDWR);
509                         int readfd  = io->type == IO_WR ? pipefds[0] : devnull;
510                         int writefd = (io->type == IO_RD || io->type == IO_AP)
511                                                         ? pipefds[1] : devnull;
513                         dup2(readfd,  STDIN_FILENO);
514                         dup2(writefd, STDOUT_FILENO);
515                         dup2(devnull, STDERR_FILENO);
517                         close(devnull);
518                         if (pipefds[0] != -1)
519                                 close(pipefds[0]);
520                         if (pipefds[1] != -1)
521                                 close(pipefds[1]);
522                 }
524                 if (io->dir && *io->dir && chdir(io->dir) == -1)
525                         die("Failed to change directory: %s", strerror(errno));
527                 execvp(io->argv[0], (char *const*) io->argv);
528                 die("Failed to execute program: %s", strerror(errno));
529         }
531         if (pipefds[!!(io->type == IO_WR)] != -1)
532                 close(pipefds[!!(io->type == IO_WR)]);
533         return FALSE;
536 static bool
537 run_io(struct io *io, const char **argv, const char *dir, enum io_type type)
539         init_io(io, dir, type);
540         if (!format_argv(io->argv, argv, FORMAT_NONE))
541                 return FALSE;
542         return start_io(io);
545 static int
546 run_io_do(struct io *io)
548         return start_io(io) && done_io(io);
551 static int
552 run_io_bg(const char **argv)
554         struct io io = {};
556         init_io(&io, NULL, IO_BG);
557         if (!format_argv(io.argv, argv, FORMAT_NONE))
558                 return FALSE;
559         return run_io_do(&io);
562 static bool
563 run_io_fg(const char **argv, const char *dir)
565         struct io io = {};
567         init_io(&io, dir, IO_FG);
568         if (!format_argv(io.argv, argv, FORMAT_NONE))
569                 return FALSE;
570         return run_io_do(&io);
573 static bool
574 run_io_append(const char **argv, enum format_flags flags, int fd)
576         struct io io = {};
578         init_io(&io, NULL, IO_AP);
579         io.pipe = fd;
580         if (format_argv(io.argv, argv, flags))
581                 return run_io_do(&io);
582         close(fd);
583         return FALSE;
586 static bool
587 run_io_rd(struct io *io, const char **argv, enum format_flags flags)
589         return init_io_rd(io, argv, NULL, flags) && start_io(io);
592 static bool
593 io_eof(struct io *io)
595         return io->eof;
598 static int
599 io_error(struct io *io)
601         return io->error;
604 static char *
605 io_strerror(struct io *io)
607         return strerror(io->error);
610 static bool
611 io_can_read(struct io *io)
613         struct timeval tv = { 0, 500 };
614         fd_set fds;
616         FD_ZERO(&fds);
617         FD_SET(io->pipe, &fds);
619         return select(io->pipe + 1, &fds, NULL, NULL, &tv) > 0;
622 static ssize_t
623 io_read(struct io *io, void *buf, size_t bufsize)
625         do {
626                 ssize_t readsize = read(io->pipe, buf, bufsize);
628                 if (readsize < 0 && (errno == EAGAIN || errno == EINTR))
629                         continue;
630                 else if (readsize == -1)
631                         io->error = errno;
632                 else if (readsize == 0)
633                         io->eof = 1;
634                 return readsize;
635         } while (1);
638 DEFINE_ALLOCATOR(realloc_io_buf, char, BUFSIZ)
640 static char *
641 io_get(struct io *io, int c, bool can_read)
643         char *eol;
644         ssize_t readsize;
646         while (TRUE) {
647                 if (io->bufsize > 0) {
648                         eol = memchr(io->bufpos, c, io->bufsize);
649                         if (eol) {
650                                 char *line = io->bufpos;
652                                 *eol = 0;
653                                 io->bufpos = eol + 1;
654                                 io->bufsize -= io->bufpos - line;
655                                 return line;
656                         }
657                 }
659                 if (io_eof(io)) {
660                         if (io->bufsize) {
661                                 io->bufpos[io->bufsize] = 0;
662                                 io->bufsize = 0;
663                                 return io->bufpos;
664                         }
665                         return NULL;
666                 }
668                 if (!can_read)
669                         return NULL;
671                 if (io->bufsize > 0 && io->bufpos > io->buf)
672                         memmove(io->buf, io->bufpos, io->bufsize);
674                 if (io->bufalloc == io->bufsize) {
675                         if (!realloc_io_buf(&io->buf, io->bufalloc, BUFSIZ))
676                                 return NULL;
677                         io->bufalloc += BUFSIZ;
678                 }
680                 io->bufpos = io->buf;
681                 readsize = io_read(io, io->buf + io->bufsize, io->bufalloc - io->bufsize);
682                 if (io_error(io))
683                         return NULL;
684                 io->bufsize += readsize;
685         }
688 static bool
689 io_write(struct io *io, const void *buf, size_t bufsize)
691         size_t written = 0;
693         while (!io_error(io) && written < bufsize) {
694                 ssize_t size;
696                 size = write(io->pipe, buf + written, bufsize - written);
697                 if (size < 0 && (errno == EAGAIN || errno == EINTR))
698                         continue;
699                 else if (size == -1)
700                         io->error = errno;
701                 else
702                         written += size;
703         }
705         return written == bufsize;
708 static bool
709 io_read_buf(struct io *io, char buf[], size_t bufsize)
711         char *result = io_get(io, '\n', TRUE);
713         if (result) {
714                 result = chomp_string(result);
715                 string_ncopy_do(buf, bufsize, result, strlen(result));
716         }
718         return done_io(io) && result;
721 static bool
722 run_io_buf(const char **argv, char buf[], size_t bufsize)
724         struct io io = {};
726         return run_io_rd(&io, argv, FORMAT_NONE) && io_read_buf(&io, buf, bufsize);
729 static int
730 io_load(struct io *io, const char *separators,
731         int (*read_property)(char *, size_t, char *, size_t))
733         char *name;
734         int state = OK;
736         if (!start_io(io))
737                 return ERR;
739         while (state == OK && (name = io_get(io, '\n', TRUE))) {
740                 char *value;
741                 size_t namelen;
742                 size_t valuelen;
744                 name = chomp_string(name);
745                 namelen = strcspn(name, separators);
747                 if (name[namelen]) {
748                         name[namelen] = 0;
749                         value = chomp_string(name + namelen + 1);
750                         valuelen = strlen(value);
752                 } else {
753                         value = "";
754                         valuelen = 0;
755                 }
757                 state = read_property(name, namelen, value, valuelen);
758         }
760         if (state != ERR && io_error(io))
761                 state = ERR;
762         done_io(io);
764         return state;
767 static int
768 run_io_load(const char **argv, const char *separators,
769             int (*read_property)(char *, size_t, char *, size_t))
771         struct io io = {};
773         return init_io_rd(&io, argv, NULL, FORMAT_NONE)
774                 ? io_load(&io, separators, read_property) : ERR;
778 /*
779  * User requests
780  */
782 #define REQ_INFO \
783         /* XXX: Keep the view request first and in sync with views[]. */ \
784         REQ_GROUP("View switching") \
785         REQ_(VIEW_MAIN,         "Show main view"), \
786         REQ_(VIEW_DIFF,         "Show diff view"), \
787         REQ_(VIEW_LOG,          "Show log view"), \
788         REQ_(VIEW_TREE,         "Show tree view"), \
789         REQ_(VIEW_BLOB,         "Show blob view"), \
790         REQ_(VIEW_BLAME,        "Show blame view"), \
791         REQ_(VIEW_BRANCH,       "Show branch view"), \
792         REQ_(VIEW_HELP,         "Show help page"), \
793         REQ_(VIEW_PAGER,        "Show pager view"), \
794         REQ_(VIEW_STATUS,       "Show status view"), \
795         REQ_(VIEW_STAGE,        "Show stage view"), \
796         \
797         REQ_GROUP("View manipulation") \
798         REQ_(ENTER,             "Enter current line and scroll"), \
799         REQ_(NEXT,              "Move to next"), \
800         REQ_(PREVIOUS,          "Move to previous"), \
801         REQ_(PARENT,            "Move to parent"), \
802         REQ_(VIEW_NEXT,         "Move focus to next view"), \
803         REQ_(REFRESH,           "Reload and refresh"), \
804         REQ_(MAXIMIZE,          "Maximize the current view"), \
805         REQ_(VIEW_CLOSE,        "Close the current view"), \
806         REQ_(QUIT,              "Close all views and quit"), \
807         \
808         REQ_GROUP("View specific requests") \
809         REQ_(STATUS_UPDATE,     "Update file status"), \
810         REQ_(STATUS_REVERT,     "Revert file changes"), \
811         REQ_(STATUS_MERGE,      "Merge file using external tool"), \
812         REQ_(STAGE_NEXT,        "Find next chunk to stage"), \
813         \
814         REQ_GROUP("Cursor navigation") \
815         REQ_(MOVE_UP,           "Move cursor one line up"), \
816         REQ_(MOVE_DOWN,         "Move cursor one line down"), \
817         REQ_(MOVE_PAGE_DOWN,    "Move cursor one page down"), \
818         REQ_(MOVE_PAGE_UP,      "Move cursor one page up"), \
819         REQ_(MOVE_FIRST_LINE,   "Move cursor to first line"), \
820         REQ_(MOVE_LAST_LINE,    "Move cursor to last line"), \
821         \
822         REQ_GROUP("Scrolling") \
823         REQ_(SCROLL_LEFT,       "Scroll two columns left"), \
824         REQ_(SCROLL_RIGHT,      "Scroll two columns right"), \
825         REQ_(SCROLL_LINE_UP,    "Scroll one line up"), \
826         REQ_(SCROLL_LINE_DOWN,  "Scroll one line down"), \
827         REQ_(SCROLL_PAGE_UP,    "Scroll one page up"), \
828         REQ_(SCROLL_PAGE_DOWN,  "Scroll one page down"), \
829         \
830         REQ_GROUP("Searching") \
831         REQ_(SEARCH,            "Search the view"), \
832         REQ_(SEARCH_BACK,       "Search backwards in the view"), \
833         REQ_(FIND_NEXT,         "Find next search match"), \
834         REQ_(FIND_PREV,         "Find previous search match"), \
835         \
836         REQ_GROUP("Option manipulation") \
837         REQ_(OPTIONS,           "Open option menu"), \
838         REQ_(TOGGLE_LINENO,     "Toggle line numbers"), \
839         REQ_(TOGGLE_DATE,       "Toggle date display"), \
840         REQ_(TOGGLE_AUTHOR,     "Toggle author display"), \
841         REQ_(TOGGLE_REV_GRAPH,  "Toggle revision graph visualization"), \
842         REQ_(TOGGLE_REFS,       "Toggle reference display (tags/branches)"), \
843         REQ_(TOGGLE_SORT_ORDER, "Toggle ascending/descending sort order"), \
844         REQ_(TOGGLE_SORT_FIELD, "Toggle field to sort by"), \
845         \
846         REQ_GROUP("Misc") \
847         REQ_(PROMPT,            "Bring up the prompt"), \
848         REQ_(SCREEN_REDRAW,     "Redraw the screen"), \
849         REQ_(SHOW_VERSION,      "Show version information"), \
850         REQ_(STOP_LOADING,      "Stop all loading views"), \
851         REQ_(EDIT,              "Open in editor"), \
852         REQ_(NONE,              "Do nothing")
855 /* User action requests. */
856 enum request {
857 #define REQ_GROUP(help)
858 #define REQ_(req, help) REQ_##req
860         /* Offset all requests to avoid conflicts with ncurses getch values. */
861         REQ_OFFSET = KEY_MAX + 1,
862         REQ_INFO
864 #undef  REQ_GROUP
865 #undef  REQ_
866 };
868 struct request_info {
869         enum request request;
870         const char *name;
871         int namelen;
872         const char *help;
873 };
875 static const struct request_info req_info[] = {
876 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
877 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
878         REQ_INFO
879 #undef  REQ_GROUP
880 #undef  REQ_
881 };
883 static enum request
884 get_request(const char *name)
886         int namelen = strlen(name);
887         int i;
889         for (i = 0; i < ARRAY_SIZE(req_info); i++)
890                 if (req_info[i].namelen == namelen &&
891                     !string_enum_compare(req_info[i].name, name, namelen))
892                         return req_info[i].request;
894         return REQ_NONE;
898 /*
899  * Options
900  */
902 /* Option and state variables. */
903 static bool opt_date                    = TRUE;
904 static bool opt_author                  = TRUE;
905 static bool opt_line_number             = FALSE;
906 static bool opt_line_graphics           = TRUE;
907 static bool opt_rev_graph               = FALSE;
908 static bool opt_show_refs               = TRUE;
909 static int opt_num_interval             = 5;
910 static double opt_hscroll               = 0.50;
911 static double opt_scale_split_view      = 2.0 / 3.0;
912 static int opt_tab_size                 = 8;
913 static int opt_author_cols              = 19;
914 static char opt_path[SIZEOF_STR]        = "";
915 static char opt_file[SIZEOF_STR]        = "";
916 static char opt_ref[SIZEOF_REF]         = "";
917 static char opt_head[SIZEOF_REF]        = "";
918 static char opt_head_rev[SIZEOF_REV]    = "";
919 static char opt_remote[SIZEOF_REF]      = "";
920 static char opt_encoding[20]            = "UTF-8";
921 static bool opt_utf8                    = TRUE;
922 static char opt_codeset[20]             = "UTF-8";
923 static iconv_t opt_iconv                = ICONV_NONE;
924 static char opt_search[SIZEOF_STR]      = "";
925 static char opt_cdup[SIZEOF_STR]        = "";
926 static char opt_prefix[SIZEOF_STR]      = "";
927 static char opt_git_dir[SIZEOF_STR]     = "";
928 static signed char opt_is_inside_work_tree      = -1; /* set to TRUE or FALSE */
929 static char opt_editor[SIZEOF_STR]      = "";
930 static FILE *opt_tty                    = NULL;
932 #define is_initial_commit()     (!*opt_head_rev)
933 #define is_head_commit(rev)     (!strcmp((rev), "HEAD") || !strcmp(opt_head_rev, (rev)))
936 /*
937  * Line-oriented content detection.
938  */
940 #define LINE_INFO \
941 LINE(DIFF_HEADER,  "diff --git ",       COLOR_YELLOW,   COLOR_DEFAULT,  0), \
942 LINE(DIFF_CHUNK,   "@@",                COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
943 LINE(DIFF_ADD,     "+",                 COLOR_GREEN,    COLOR_DEFAULT,  0), \
944 LINE(DIFF_DEL,     "-",                 COLOR_RED,      COLOR_DEFAULT,  0), \
945 LINE(DIFF_INDEX,        "index ",         COLOR_BLUE,   COLOR_DEFAULT,  0), \
946 LINE(DIFF_OLDMODE,      "old file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
947 LINE(DIFF_NEWMODE,      "new file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
948 LINE(DIFF_COPY_FROM,    "copy from",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
949 LINE(DIFF_COPY_TO,      "copy to",        COLOR_YELLOW, COLOR_DEFAULT,  0), \
950 LINE(DIFF_RENAME_FROM,  "rename from",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
951 LINE(DIFF_RENAME_TO,    "rename to",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
952 LINE(DIFF_SIMILARITY,   "similarity ",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
953 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
954 LINE(DIFF_TREE,         "diff-tree ",     COLOR_BLUE,   COLOR_DEFAULT,  0), \
955 LINE(PP_AUTHOR,    "Author: ",          COLOR_CYAN,     COLOR_DEFAULT,  0), \
956 LINE(PP_COMMIT,    "Commit: ",          COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
957 LINE(PP_MERGE,     "Merge: ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
958 LINE(PP_DATE,      "Date:   ",          COLOR_YELLOW,   COLOR_DEFAULT,  0), \
959 LINE(PP_ADATE,     "AuthorDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
960 LINE(PP_CDATE,     "CommitDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
961 LINE(PP_REFS,      "Refs: ",            COLOR_RED,      COLOR_DEFAULT,  0), \
962 LINE(COMMIT,       "commit ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
963 LINE(PARENT,       "parent ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
964 LINE(TREE,         "tree ",             COLOR_BLUE,     COLOR_DEFAULT,  0), \
965 LINE(AUTHOR,       "author ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
966 LINE(COMMITTER,    "committer ",        COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
967 LINE(SIGNOFF,      "    Signed-off-by", COLOR_YELLOW,   COLOR_DEFAULT,  0), \
968 LINE(ACKED,        "    Acked-by",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
969 LINE(DEFAULT,      "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
970 LINE(CURSOR,       "",                  COLOR_WHITE,    COLOR_GREEN,    A_BOLD), \
971 LINE(STATUS,       "",                  COLOR_GREEN,    COLOR_DEFAULT,  0), \
972 LINE(DELIMITER,    "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
973 LINE(DATE,         "",                  COLOR_BLUE,     COLOR_DEFAULT,  0), \
974 LINE(MODE,         "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
975 LINE(LINE_NUMBER,  "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
976 LINE(TITLE_BLUR,   "",                  COLOR_WHITE,    COLOR_BLUE,     0), \
977 LINE(TITLE_FOCUS,  "",                  COLOR_WHITE,    COLOR_BLUE,     A_BOLD), \
978 LINE(MAIN_COMMIT,  "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
979 LINE(MAIN_TAG,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  A_BOLD), \
980 LINE(MAIN_LOCAL_TAG,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
981 LINE(MAIN_REMOTE,  "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
982 LINE(MAIN_TRACKED, "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_BOLD), \
983 LINE(MAIN_REF,     "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
984 LINE(MAIN_HEAD,    "",                  COLOR_CYAN,     COLOR_DEFAULT,  A_BOLD), \
985 LINE(MAIN_REVGRAPH,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
986 LINE(TREE_HEAD,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_BOLD), \
987 LINE(TREE_DIR,     "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_NORMAL), \
988 LINE(TREE_FILE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
989 LINE(STAT_HEAD,    "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
990 LINE(STAT_SECTION, "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
991 LINE(STAT_NONE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
992 LINE(STAT_STAGED,  "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
993 LINE(STAT_UNSTAGED,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
994 LINE(STAT_UNTRACKED,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
995 LINE(BLAME_ID,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0)
997 enum line_type {
998 #define LINE(type, line, fg, bg, attr) \
999         LINE_##type
1000         LINE_INFO,
1001         LINE_NONE
1002 #undef  LINE
1003 };
1005 struct line_info {
1006         const char *name;       /* Option name. */
1007         int namelen;            /* Size of option name. */
1008         const char *line;       /* The start of line to match. */
1009         int linelen;            /* Size of string to match. */
1010         int fg, bg, attr;       /* Color and text attributes for the lines. */
1011 };
1013 static struct line_info line_info[] = {
1014 #define LINE(type, line, fg, bg, attr) \
1015         { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
1016         LINE_INFO
1017 #undef  LINE
1018 };
1020 static enum line_type
1021 get_line_type(const char *line)
1023         int linelen = strlen(line);
1024         enum line_type type;
1026         for (type = 0; type < ARRAY_SIZE(line_info); type++)
1027                 /* Case insensitive search matches Signed-off-by lines better. */
1028                 if (linelen >= line_info[type].linelen &&
1029                     !strncasecmp(line_info[type].line, line, line_info[type].linelen))
1030                         return type;
1032         return LINE_DEFAULT;
1035 static inline int
1036 get_line_attr(enum line_type type)
1038         assert(type < ARRAY_SIZE(line_info));
1039         return COLOR_PAIR(type) | line_info[type].attr;
1042 static struct line_info *
1043 get_line_info(const char *name)
1045         size_t namelen = strlen(name);
1046         enum line_type type;
1048         for (type = 0; type < ARRAY_SIZE(line_info); type++)
1049                 if (namelen == line_info[type].namelen &&
1050                     !string_enum_compare(line_info[type].name, name, namelen))
1051                         return &line_info[type];
1053         return NULL;
1056 static void
1057 init_colors(void)
1059         int default_bg = line_info[LINE_DEFAULT].bg;
1060         int default_fg = line_info[LINE_DEFAULT].fg;
1061         enum line_type type;
1063         start_color();
1065         if (assume_default_colors(default_fg, default_bg) == ERR) {
1066                 default_bg = COLOR_BLACK;
1067                 default_fg = COLOR_WHITE;
1068         }
1070         for (type = 0; type < ARRAY_SIZE(line_info); type++) {
1071                 struct line_info *info = &line_info[type];
1072                 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
1073                 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
1075                 init_pair(type, fg, bg);
1076         }
1079 struct line {
1080         enum line_type type;
1082         /* State flags */
1083         unsigned int selected:1;
1084         unsigned int dirty:1;
1085         unsigned int cleareol:1;
1087         void *data;             /* User data */
1088 };
1091 /*
1092  * Keys
1093  */
1095 struct keybinding {
1096         int alias;
1097         enum request request;
1098 };
1100 static const struct keybinding default_keybindings[] = {
1101         /* View switching */
1102         { 'm',          REQ_VIEW_MAIN },
1103         { 'd',          REQ_VIEW_DIFF },
1104         { 'l',          REQ_VIEW_LOG },
1105         { 't',          REQ_VIEW_TREE },
1106         { 'f',          REQ_VIEW_BLOB },
1107         { 'B',          REQ_VIEW_BLAME },
1108         { 'H',          REQ_VIEW_BRANCH },
1109         { 'p',          REQ_VIEW_PAGER },
1110         { 'h',          REQ_VIEW_HELP },
1111         { 'S',          REQ_VIEW_STATUS },
1112         { 'c',          REQ_VIEW_STAGE },
1114         /* View manipulation */
1115         { 'q',          REQ_VIEW_CLOSE },
1116         { KEY_TAB,      REQ_VIEW_NEXT },
1117         { KEY_RETURN,   REQ_ENTER },
1118         { KEY_UP,       REQ_PREVIOUS },
1119         { KEY_DOWN,     REQ_NEXT },
1120         { 'R',          REQ_REFRESH },
1121         { KEY_F(5),     REQ_REFRESH },
1122         { 'O',          REQ_MAXIMIZE },
1124         /* Cursor navigation */
1125         { 'k',          REQ_MOVE_UP },
1126         { 'j',          REQ_MOVE_DOWN },
1127         { KEY_HOME,     REQ_MOVE_FIRST_LINE },
1128         { KEY_END,      REQ_MOVE_LAST_LINE },
1129         { KEY_NPAGE,    REQ_MOVE_PAGE_DOWN },
1130         { ' ',          REQ_MOVE_PAGE_DOWN },
1131         { KEY_PPAGE,    REQ_MOVE_PAGE_UP },
1132         { 'b',          REQ_MOVE_PAGE_UP },
1133         { '-',          REQ_MOVE_PAGE_UP },
1135         /* Scrolling */
1136         { KEY_LEFT,     REQ_SCROLL_LEFT },
1137         { KEY_RIGHT,    REQ_SCROLL_RIGHT },
1138         { KEY_IC,       REQ_SCROLL_LINE_UP },
1139         { KEY_DC,       REQ_SCROLL_LINE_DOWN },
1140         { 'w',          REQ_SCROLL_PAGE_UP },
1141         { 's',          REQ_SCROLL_PAGE_DOWN },
1143         /* Searching */
1144         { '/',          REQ_SEARCH },
1145         { '?',          REQ_SEARCH_BACK },
1146         { 'n',          REQ_FIND_NEXT },
1147         { 'N',          REQ_FIND_PREV },
1149         /* Misc */
1150         { 'Q',          REQ_QUIT },
1151         { 'z',          REQ_STOP_LOADING },
1152         { 'v',          REQ_SHOW_VERSION },
1153         { 'r',          REQ_SCREEN_REDRAW },
1154         { 'o',          REQ_OPTIONS },
1155         { '.',          REQ_TOGGLE_LINENO },
1156         { 'D',          REQ_TOGGLE_DATE },
1157         { 'A',          REQ_TOGGLE_AUTHOR },
1158         { 'g',          REQ_TOGGLE_REV_GRAPH },
1159         { 'F',          REQ_TOGGLE_REFS },
1160         { 'I',          REQ_TOGGLE_SORT_ORDER },
1161         { 'i',          REQ_TOGGLE_SORT_FIELD },
1162         { ':',          REQ_PROMPT },
1163         { 'u',          REQ_STATUS_UPDATE },
1164         { '!',          REQ_STATUS_REVERT },
1165         { 'M',          REQ_STATUS_MERGE },
1166         { '@',          REQ_STAGE_NEXT },
1167         { ',',          REQ_PARENT },
1168         { 'e',          REQ_EDIT },
1169 };
1171 #define KEYMAP_INFO \
1172         KEYMAP_(GENERIC), \
1173         KEYMAP_(MAIN), \
1174         KEYMAP_(DIFF), \
1175         KEYMAP_(LOG), \
1176         KEYMAP_(TREE), \
1177         KEYMAP_(BLOB), \
1178         KEYMAP_(BLAME), \
1179         KEYMAP_(BRANCH), \
1180         KEYMAP_(PAGER), \
1181         KEYMAP_(HELP), \
1182         KEYMAP_(STATUS), \
1183         KEYMAP_(STAGE)
1185 enum keymap {
1186 #define KEYMAP_(name) KEYMAP_##name
1187         KEYMAP_INFO
1188 #undef  KEYMAP_
1189 };
1191 static const struct enum_map keymap_table[] = {
1192 #define KEYMAP_(name) ENUM_MAP(#name, KEYMAP_##name)
1193         KEYMAP_INFO
1194 #undef  KEYMAP_
1195 };
1197 #define set_keymap(map, name) map_enum(map, keymap_table, name)
1199 struct keybinding_table {
1200         struct keybinding *data;
1201         size_t size;
1202 };
1204 static struct keybinding_table keybindings[ARRAY_SIZE(keymap_table)];
1206 static void
1207 add_keybinding(enum keymap keymap, enum request request, int key)
1209         struct keybinding_table *table = &keybindings[keymap];
1211         table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data));
1212         if (!table->data)
1213                 die("Failed to allocate keybinding");
1214         table->data[table->size].alias = key;
1215         table->data[table->size++].request = request;
1218 /* Looks for a key binding first in the given map, then in the generic map, and
1219  * lastly in the default keybindings. */
1220 static enum request
1221 get_keybinding(enum keymap keymap, int key)
1223         size_t i;
1225         for (i = 0; i < keybindings[keymap].size; i++)
1226                 if (keybindings[keymap].data[i].alias == key)
1227                         return keybindings[keymap].data[i].request;
1229         for (i = 0; i < keybindings[KEYMAP_GENERIC].size; i++)
1230                 if (keybindings[KEYMAP_GENERIC].data[i].alias == key)
1231                         return keybindings[KEYMAP_GENERIC].data[i].request;
1233         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1234                 if (default_keybindings[i].alias == key)
1235                         return default_keybindings[i].request;
1237         return (enum request) key;
1241 struct key {
1242         const char *name;
1243         int value;
1244 };
1246 static const struct key key_table[] = {
1247         { "Enter",      KEY_RETURN },
1248         { "Space",      ' ' },
1249         { "Backspace",  KEY_BACKSPACE },
1250         { "Tab",        KEY_TAB },
1251         { "Escape",     KEY_ESC },
1252         { "Left",       KEY_LEFT },
1253         { "Right",      KEY_RIGHT },
1254         { "Up",         KEY_UP },
1255         { "Down",       KEY_DOWN },
1256         { "Insert",     KEY_IC },
1257         { "Delete",     KEY_DC },
1258         { "Hash",       '#' },
1259         { "Home",       KEY_HOME },
1260         { "End",        KEY_END },
1261         { "PageUp",     KEY_PPAGE },
1262         { "PageDown",   KEY_NPAGE },
1263         { "F1",         KEY_F(1) },
1264         { "F2",         KEY_F(2) },
1265         { "F3",         KEY_F(3) },
1266         { "F4",         KEY_F(4) },
1267         { "F5",         KEY_F(5) },
1268         { "F6",         KEY_F(6) },
1269         { "F7",         KEY_F(7) },
1270         { "F8",         KEY_F(8) },
1271         { "F9",         KEY_F(9) },
1272         { "F10",        KEY_F(10) },
1273         { "F11",        KEY_F(11) },
1274         { "F12",        KEY_F(12) },
1275 };
1277 static int
1278 get_key_value(const char *name)
1280         int i;
1282         for (i = 0; i < ARRAY_SIZE(key_table); i++)
1283                 if (!strcasecmp(key_table[i].name, name))
1284                         return key_table[i].value;
1286         if (strlen(name) == 1 && isprint(*name))
1287                 return (int) *name;
1289         return ERR;
1292 static const char *
1293 get_key_name(int key_value)
1295         static char key_char[] = "'X'";
1296         const char *seq = NULL;
1297         int key;
1299         for (key = 0; key < ARRAY_SIZE(key_table); key++)
1300                 if (key_table[key].value == key_value)
1301                         seq = key_table[key].name;
1303         if (seq == NULL &&
1304             key_value < 127 &&
1305             isprint(key_value)) {
1306                 key_char[1] = (char) key_value;
1307                 seq = key_char;
1308         }
1310         return seq ? seq : "(no key)";
1313 static const char *
1314 get_key(enum request request)
1316         static char buf[BUFSIZ];
1317         size_t pos = 0;
1318         char *sep = "";
1319         int i;
1321         buf[pos] = 0;
1323         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1324                 const struct keybinding *keybinding = &default_keybindings[i];
1326                 if (keybinding->request != request)
1327                         continue;
1329                 if (!string_format_from(buf, &pos, "%s%s", sep,
1330                                         get_key_name(keybinding->alias)))
1331                         return "Too many keybindings!";
1332                 sep = ", ";
1333         }
1335         return buf;
1338 struct run_request {
1339         enum keymap keymap;
1340         int key;
1341         const char *argv[SIZEOF_ARG];
1342 };
1344 static struct run_request *run_request;
1345 static size_t run_requests;
1347 DEFINE_ALLOCATOR(realloc_run_requests, struct run_request, 8)
1349 static enum request
1350 add_run_request(enum keymap keymap, int key, int argc, const char **argv)
1352         struct run_request *req;
1354         if (argc >= ARRAY_SIZE(req->argv) - 1)
1355                 return REQ_NONE;
1357         if (!realloc_run_requests(&run_request, run_requests, 1))
1358                 return REQ_NONE;
1360         req = &run_request[run_requests];
1361         req->keymap = keymap;
1362         req->key = key;
1363         req->argv[0] = NULL;
1365         if (!format_argv(req->argv, argv, FORMAT_NONE))
1366                 return REQ_NONE;
1368         return REQ_NONE + ++run_requests;
1371 static struct run_request *
1372 get_run_request(enum request request)
1374         if (request <= REQ_NONE)
1375                 return NULL;
1376         return &run_request[request - REQ_NONE - 1];
1379 static void
1380 add_builtin_run_requests(void)
1382         const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1383         const char *commit[] = { "git", "commit", NULL };
1384         const char *gc[] = { "git", "gc", NULL };
1385         struct {
1386                 enum keymap keymap;
1387                 int key;
1388                 int argc;
1389                 const char **argv;
1390         } reqs[] = {
1391                 { KEYMAP_MAIN,    'C', ARRAY_SIZE(cherry_pick) - 1, cherry_pick },
1392                 { KEYMAP_STATUS,  'C', ARRAY_SIZE(commit) - 1, commit },
1393                 { KEYMAP_GENERIC, 'G', ARRAY_SIZE(gc) - 1, gc },
1394         };
1395         int i;
1397         for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1398                 enum request req;
1400                 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argc, reqs[i].argv);
1401                 if (req != REQ_NONE)
1402                         add_keybinding(reqs[i].keymap, req, reqs[i].key);
1403         }
1406 /*
1407  * User config file handling.
1408  */
1410 static int   config_lineno;
1411 static bool  config_errors;
1412 static const char *config_msg;
1414 static const struct enum_map color_map[] = {
1415 #define COLOR_MAP(name) ENUM_MAP(#name, COLOR_##name)
1416         COLOR_MAP(DEFAULT),
1417         COLOR_MAP(BLACK),
1418         COLOR_MAP(BLUE),
1419         COLOR_MAP(CYAN),
1420         COLOR_MAP(GREEN),
1421         COLOR_MAP(MAGENTA),
1422         COLOR_MAP(RED),
1423         COLOR_MAP(WHITE),
1424         COLOR_MAP(YELLOW),
1425 };
1427 static const struct enum_map attr_map[] = {
1428 #define ATTR_MAP(name) ENUM_MAP(#name, A_##name)
1429         ATTR_MAP(NORMAL),
1430         ATTR_MAP(BLINK),
1431         ATTR_MAP(BOLD),
1432         ATTR_MAP(DIM),
1433         ATTR_MAP(REVERSE),
1434         ATTR_MAP(STANDOUT),
1435         ATTR_MAP(UNDERLINE),
1436 };
1438 #define set_attribute(attr, name)       map_enum(attr, attr_map, name)
1440 static int parse_step(double *opt, const char *arg)
1442         *opt = atoi(arg);
1443         if (!strchr(arg, '%'))
1444                 return OK;
1446         /* "Shift down" so 100% and 1 does not conflict. */
1447         *opt = (*opt - 1) / 100;
1448         if (*opt >= 1.0) {
1449                 *opt = 0.99;
1450                 config_msg = "Step value larger than 100%";
1451                 return ERR;
1452         }
1453         if (*opt < 0.0) {
1454                 *opt = 1;
1455                 config_msg = "Invalid step value";
1456                 return ERR;
1457         }
1458         return OK;
1461 static int
1462 parse_int(int *opt, const char *arg, int min, int max)
1464         int value = atoi(arg);
1466         if (min <= value && value <= max) {
1467                 *opt = value;
1468                 return OK;
1469         }
1471         config_msg = "Integer value out of bound";
1472         return ERR;
1475 static bool
1476 set_color(int *color, const char *name)
1478         if (map_enum(color, color_map, name))
1479                 return TRUE;
1480         if (!prefixcmp(name, "color"))
1481                 return parse_int(color, name + 5, 0, 255) == OK;
1482         return FALSE;
1485 /* Wants: object fgcolor bgcolor [attribute] */
1486 static int
1487 option_color_command(int argc, const char *argv[])
1489         struct line_info *info;
1491         if (argc != 3 && argc != 4) {
1492                 config_msg = "Wrong number of arguments given to color command";
1493                 return ERR;
1494         }
1496         info = get_line_info(argv[0]);
1497         if (!info) {
1498                 static const struct enum_map obsolete[] = {
1499                         ENUM_MAP("main-delim",  LINE_DELIMITER),
1500                         ENUM_MAP("main-date",   LINE_DATE),
1501                         ENUM_MAP("main-author", LINE_AUTHOR),
1502                 };
1503                 int index;
1505                 if (!map_enum(&index, obsolete, argv[0])) {
1506                         config_msg = "Unknown color name";
1507                         return ERR;
1508                 }
1509                 info = &line_info[index];
1510         }
1512         if (!set_color(&info->fg, argv[1]) ||
1513             !set_color(&info->bg, argv[2])) {
1514                 config_msg = "Unknown color";
1515                 return ERR;
1516         }
1518         if (argc == 4 && !set_attribute(&info->attr, argv[3])) {
1519                 config_msg = "Unknown attribute";
1520                 return ERR;
1521         }
1523         return OK;
1526 static int parse_bool(bool *opt, const char *arg)
1528         *opt = (!strcmp(arg, "1") || !strcmp(arg, "true") || !strcmp(arg, "yes"))
1529                 ? TRUE : FALSE;
1530         return OK;
1533 static int
1534 parse_string(char *opt, const char *arg, size_t optsize)
1536         int arglen = strlen(arg);
1538         switch (arg[0]) {
1539         case '\"':
1540         case '\'':
1541                 if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1542                         config_msg = "Unmatched quotation";
1543                         return ERR;
1544                 }
1545                 arg += 1; arglen -= 2;
1546         default:
1547                 string_ncopy_do(opt, optsize, arg, arglen);
1548                 return OK;
1549         }
1552 /* Wants: name = value */
1553 static int
1554 option_set_command(int argc, const char *argv[])
1556         if (argc != 3) {
1557                 config_msg = "Wrong number of arguments given to set command";
1558                 return ERR;
1559         }
1561         if (strcmp(argv[1], "=")) {
1562                 config_msg = "No value assigned";
1563                 return ERR;
1564         }
1566         if (!strcmp(argv[0], "show-author"))
1567                 return parse_bool(&opt_author, argv[2]);
1569         if (!strcmp(argv[0], "show-date"))
1570                 return parse_bool(&opt_date, argv[2]);
1572         if (!strcmp(argv[0], "show-rev-graph"))
1573                 return parse_bool(&opt_rev_graph, argv[2]);
1575         if (!strcmp(argv[0], "show-refs"))
1576                 return parse_bool(&opt_show_refs, argv[2]);
1578         if (!strcmp(argv[0], "show-line-numbers"))
1579                 return parse_bool(&opt_line_number, argv[2]);
1581         if (!strcmp(argv[0], "line-graphics"))
1582                 return parse_bool(&opt_line_graphics, argv[2]);
1584         if (!strcmp(argv[0], "line-number-interval"))
1585                 return parse_int(&opt_num_interval, argv[2], 1, 1024);
1587         if (!strcmp(argv[0], "author-width"))
1588                 return parse_int(&opt_author_cols, argv[2], 0, 1024);
1590         if (!strcmp(argv[0], "horizontal-scroll"))
1591                 return parse_step(&opt_hscroll, argv[2]);
1593         if (!strcmp(argv[0], "split-view-height"))
1594                 return parse_step(&opt_scale_split_view, argv[2]);
1596         if (!strcmp(argv[0], "tab-size"))
1597                 return parse_int(&opt_tab_size, argv[2], 1, 1024);
1599         if (!strcmp(argv[0], "commit-encoding"))
1600                 return parse_string(opt_encoding, argv[2], sizeof(opt_encoding));
1602         config_msg = "Unknown variable name";
1603         return ERR;
1606 /* Wants: mode request key */
1607 static int
1608 option_bind_command(int argc, const char *argv[])
1610         enum request request;
1611         int keymap;
1612         int key;
1614         if (argc < 3) {
1615                 config_msg = "Wrong number of arguments given to bind command";
1616                 return ERR;
1617         }
1619         if (set_keymap(&keymap, argv[0]) == ERR) {
1620                 config_msg = "Unknown key map";
1621                 return ERR;
1622         }
1624         key = get_key_value(argv[1]);
1625         if (key == ERR) {
1626                 config_msg = "Unknown key";
1627                 return ERR;
1628         }
1630         request = get_request(argv[2]);
1631         if (request == REQ_NONE) {
1632                 static const struct enum_map obsolete[] = {
1633                         ENUM_MAP("cherry-pick",         REQ_NONE),
1634                         ENUM_MAP("screen-resize",       REQ_NONE),
1635                         ENUM_MAP("tree-parent",         REQ_PARENT),
1636                 };
1637                 int alias;
1639                 if (map_enum(&alias, obsolete, argv[2])) {
1640                         if (alias != REQ_NONE)
1641                                 add_keybinding(keymap, alias, key);
1642                         config_msg = "Obsolete request name";
1643                         return ERR;
1644                 }
1645         }
1646         if (request == REQ_NONE && *argv[2]++ == '!')
1647                 request = add_run_request(keymap, key, argc - 2, argv + 2);
1648         if (request == REQ_NONE) {
1649                 config_msg = "Unknown request name";
1650                 return ERR;
1651         }
1653         add_keybinding(keymap, request, key);
1655         return OK;
1658 static int
1659 set_option(const char *opt, char *value)
1661         const char *argv[SIZEOF_ARG];
1662         int argc = 0;
1664         if (!argv_from_string(argv, &argc, value)) {
1665                 config_msg = "Too many option arguments";
1666                 return ERR;
1667         }
1669         if (!strcmp(opt, "color"))
1670                 return option_color_command(argc, argv);
1672         if (!strcmp(opt, "set"))
1673                 return option_set_command(argc, argv);
1675         if (!strcmp(opt, "bind"))
1676                 return option_bind_command(argc, argv);
1678         config_msg = "Unknown option command";
1679         return ERR;
1682 static int
1683 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
1685         int status = OK;
1687         config_lineno++;
1688         config_msg = "Internal error";
1690         /* Check for comment markers, since read_properties() will
1691          * only ensure opt and value are split at first " \t". */
1692         optlen = strcspn(opt, "#");
1693         if (optlen == 0)
1694                 return OK;
1696         if (opt[optlen] != 0) {
1697                 config_msg = "No option value";
1698                 status = ERR;
1700         }  else {
1701                 /* Look for comment endings in the value. */
1702                 size_t len = strcspn(value, "#");
1704                 if (len < valuelen) {
1705                         valuelen = len;
1706                         value[valuelen] = 0;
1707                 }
1709                 status = set_option(opt, value);
1710         }
1712         if (status == ERR) {
1713                 warn("Error on line %d, near '%.*s': %s",
1714                      config_lineno, (int) optlen, opt, config_msg);
1715                 config_errors = TRUE;
1716         }
1718         /* Always keep going if errors are encountered. */
1719         return OK;
1722 static void
1723 load_option_file(const char *path)
1725         struct io io = {};
1727         /* It's OK that the file doesn't exist. */
1728         if (!io_open(&io, path))
1729                 return;
1731         config_lineno = 0;
1732         config_errors = FALSE;
1734         if (io_load(&io, " \t", read_option) == ERR ||
1735             config_errors == TRUE)
1736                 warn("Errors while loading %s.", path);
1739 static int
1740 load_options(void)
1742         const char *home = getenv("HOME");
1743         const char *tigrc_user = getenv("TIGRC_USER");
1744         const char *tigrc_system = getenv("TIGRC_SYSTEM");
1745         char buf[SIZEOF_STR];
1747         add_builtin_run_requests();
1749         if (!tigrc_system)
1750                 tigrc_system = SYSCONFDIR "/tigrc";
1751         load_option_file(tigrc_system);
1753         if (!tigrc_user) {
1754                 if (!home || !string_format(buf, "%s/.tigrc", home))
1755                         return ERR;
1756                 tigrc_user = buf;
1757         }
1758         load_option_file(tigrc_user);
1760         return OK;
1764 /*
1765  * The viewer
1766  */
1768 struct view;
1769 struct view_ops;
1771 /* The display array of active views and the index of the current view. */
1772 static struct view *display[2];
1773 static unsigned int current_view;
1775 #define foreach_displayed_view(view, i) \
1776         for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1778 #define displayed_views()       (display[1] != NULL ? 2 : 1)
1780 /* Current head and commit ID */
1781 static char ref_blob[SIZEOF_REF]        = "";
1782 static char ref_commit[SIZEOF_REF]      = "HEAD";
1783 static char ref_head[SIZEOF_REF]        = "HEAD";
1785 struct view {
1786         const char *name;       /* View name */
1787         const char *cmd_env;    /* Command line set via environment */
1788         const char *id;         /* Points to either of ref_{head,commit,blob} */
1790         struct view_ops *ops;   /* View operations */
1792         enum keymap keymap;     /* What keymap does this view have */
1793         bool git_dir;           /* Whether the view requires a git directory. */
1795         char ref[SIZEOF_REF];   /* Hovered commit reference */
1796         char vid[SIZEOF_REF];   /* View ID. Set to id member when updating. */
1798         int height, width;      /* The width and height of the main window */
1799         WINDOW *win;            /* The main window */
1800         WINDOW *title;          /* The title window living below the main window */
1802         /* Navigation */
1803         unsigned long offset;   /* Offset of the window top */
1804         unsigned long yoffset;  /* Offset from the window side. */
1805         unsigned long lineno;   /* Current line number */
1806         unsigned long p_offset; /* Previous offset of the window top */
1807         unsigned long p_yoffset;/* Previous offset from the window side */
1808         unsigned long p_lineno; /* Previous current line number */
1809         bool p_restore;         /* Should the previous position be restored. */
1811         /* Searching */
1812         char grep[SIZEOF_STR];  /* Search string */
1813         regex_t *regex;         /* Pre-compiled regexp */
1815         /* If non-NULL, points to the view that opened this view. If this view
1816          * is closed tig will switch back to the parent view. */
1817         struct view *parent;
1819         /* Buffering */
1820         size_t lines;           /* Total number of lines */
1821         struct line *line;      /* Line index */
1822         unsigned int digits;    /* Number of digits in the lines member. */
1824         /* Drawing */
1825         struct line *curline;   /* Line currently being drawn. */
1826         enum line_type curtype; /* Attribute currently used for drawing. */
1827         unsigned long col;      /* Column when drawing. */
1828         bool has_scrolled;      /* View was scrolled. */
1830         /* Loading */
1831         struct io io;
1832         struct io *pipe;
1833         time_t start_time;
1834         time_t update_secs;
1835 };
1837 struct view_ops {
1838         /* What type of content being displayed. Used in the title bar. */
1839         const char *type;
1840         /* Default command arguments. */
1841         const char **argv;
1842         /* Open and reads in all view content. */
1843         bool (*open)(struct view *view);
1844         /* Read one line; updates view->line. */
1845         bool (*read)(struct view *view, char *data);
1846         /* Draw one line; @lineno must be < view->height. */
1847         bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
1848         /* Depending on view handle a special requests. */
1849         enum request (*request)(struct view *view, enum request request, struct line *line);
1850         /* Search for regexp in a line. */
1851         bool (*grep)(struct view *view, struct line *line);
1852         /* Select line */
1853         void (*select)(struct view *view, struct line *line);
1854 };
1856 static struct view_ops blame_ops;
1857 static struct view_ops blob_ops;
1858 static struct view_ops diff_ops;
1859 static struct view_ops help_ops;
1860 static struct view_ops log_ops;
1861 static struct view_ops main_ops;
1862 static struct view_ops pager_ops;
1863 static struct view_ops stage_ops;
1864 static struct view_ops status_ops;
1865 static struct view_ops tree_ops;
1866 static struct view_ops branch_ops;
1868 #define VIEW_STR(name, env, ref, ops, map, git) \
1869         { name, #env, ref, ops, map, git }
1871 #define VIEW_(id, name, ops, git, ref) \
1872         VIEW_STR(name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
1875 static struct view views[] = {
1876         VIEW_(MAIN,   "main",   &main_ops,   TRUE,  ref_head),
1877         VIEW_(DIFF,   "diff",   &diff_ops,   TRUE,  ref_commit),
1878         VIEW_(LOG,    "log",    &log_ops,    TRUE,  ref_head),
1879         VIEW_(TREE,   "tree",   &tree_ops,   TRUE,  ref_commit),
1880         VIEW_(BLOB,   "blob",   &blob_ops,   TRUE,  ref_blob),
1881         VIEW_(BLAME,  "blame",  &blame_ops,  TRUE,  ref_commit),
1882         VIEW_(BRANCH, "branch", &branch_ops, TRUE,  ref_head),
1883         VIEW_(HELP,   "help",   &help_ops,   FALSE, ""),
1884         VIEW_(PAGER,  "pager",  &pager_ops,  FALSE, "stdin"),
1885         VIEW_(STATUS, "status", &status_ops, TRUE,  ""),
1886         VIEW_(STAGE,  "stage",  &stage_ops,  TRUE,  ""),
1887 };
1889 #define VIEW(req)       (&views[(req) - REQ_OFFSET - 1])
1890 #define VIEW_REQ(view)  ((view) - views + REQ_OFFSET + 1)
1892 #define foreach_view(view, i) \
1893         for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
1895 #define view_is_displayed(view) \
1896         (view == display[0] || view == display[1])
1899 enum line_graphic {
1900         LINE_GRAPHIC_VLINE
1901 };
1903 static chtype line_graphics[] = {
1904         /* LINE_GRAPHIC_VLINE: */ '|'
1905 };
1907 static inline void
1908 set_view_attr(struct view *view, enum line_type type)
1910         if (!view->curline->selected && view->curtype != type) {
1911                 wattrset(view->win, get_line_attr(type));
1912                 wchgat(view->win, -1, 0, type, NULL);
1913                 view->curtype = type;
1914         }
1917 static int
1918 draw_chars(struct view *view, enum line_type type, const char *string,
1919            int max_len, bool use_tilde)
1921         int len = 0;
1922         int col = 0;
1923         int trimmed = FALSE;
1924         size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
1926         if (max_len <= 0)
1927                 return 0;
1929         if (opt_utf8) {
1930                 len = utf8_length(&string, skip, &col, max_len, &trimmed, use_tilde);
1931         } else {
1932                 col = len = strlen(string);
1933                 if (len > max_len) {
1934                         if (use_tilde) {
1935                                 max_len -= 1;
1936                         }
1937                         col = len = max_len;
1938                         trimmed = TRUE;
1939                 }
1940         }
1942         set_view_attr(view, type);
1943         if (len > 0)
1944                 waddnstr(view->win, string, len);
1945         if (trimmed && use_tilde) {
1946                 set_view_attr(view, LINE_DELIMITER);
1947                 waddch(view->win, '~');
1948                 col++;
1949         }
1951         return col;
1954 static int
1955 draw_space(struct view *view, enum line_type type, int max, int spaces)
1957         static char space[] = "                    ";
1958         int col = 0;
1960         spaces = MIN(max, spaces);
1962         while (spaces > 0) {
1963                 int len = MIN(spaces, sizeof(space) - 1);
1965                 col += draw_chars(view, type, space, len, FALSE);
1966                 spaces -= len;
1967         }
1969         return col;
1972 static bool
1973 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
1975         view->col += draw_chars(view, type, string, view->width + view->yoffset - view->col, trim);
1976         return view->width + view->yoffset <= view->col;
1979 static bool
1980 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
1982         size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
1983         int max = view->width + view->yoffset - view->col;
1984         int i;
1986         if (max < size)
1987                 size = max;
1989         set_view_attr(view, type);
1990         /* Using waddch() instead of waddnstr() ensures that
1991          * they'll be rendered correctly for the cursor line. */
1992         for (i = skip; i < size; i++)
1993                 waddch(view->win, graphic[i]);
1995         view->col += size;
1996         if (size < max && skip <= size)
1997                 waddch(view->win, ' ');
1998         view->col++;
2000         return view->width + view->yoffset <= view->col;
2003 static bool
2004 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
2006         int max = MIN(view->width + view->yoffset - view->col, len);
2007         int col;
2009         if (text)
2010                 col = draw_chars(view, type, text, max - 1, trim);
2011         else
2012                 col = draw_space(view, type, max - 1, max - 1);
2014         view->col += col;
2015         view->col += draw_space(view, LINE_DEFAULT, max - col, max - col);
2016         return view->width + view->yoffset <= view->col;
2019 static bool
2020 draw_date(struct view *view, time_t *time)
2022         const char *date = mkdate(time);
2024         return draw_field(view, LINE_DATE, date, DATE_COLS, FALSE);
2027 static bool
2028 draw_author(struct view *view, const char *author)
2030         bool trim = opt_author_cols == 0 || opt_author_cols > 5 || !author;
2032         if (!trim) {
2033                 static char initials[10];
2034                 size_t pos;
2036 #define is_initial_sep(c) (isspace(c) || ispunct(c) || (c) == '@')
2038                 memset(initials, 0, sizeof(initials));
2039                 for (pos = 0; *author && pos < opt_author_cols - 1; author++, pos++) {
2040                         while (is_initial_sep(*author))
2041                                 author++;
2042                         strncpy(&initials[pos], author, sizeof(initials) - 1 - pos);
2043                         while (*author && !is_initial_sep(author[1]))
2044                                 author++;
2045                 }
2047                 author = initials;
2048         }
2050         return draw_field(view, LINE_AUTHOR, author, opt_author_cols, trim);
2053 static bool
2054 draw_mode(struct view *view, mode_t mode)
2056         const char *str;
2058         if (S_ISDIR(mode))
2059                 str = "drwxr-xr-x";
2060         else if (S_ISLNK(mode))
2061                 str = "lrwxrwxrwx";
2062         else if (S_ISGITLINK(mode))
2063                 str = "m---------";
2064         else if (S_ISREG(mode) && mode & S_IXUSR)
2065                 str = "-rwxr-xr-x";
2066         else if (S_ISREG(mode))
2067                 str = "-rw-r--r--";
2068         else
2069                 str = "----------";
2071         return draw_field(view, LINE_MODE, str, STRING_SIZE("-rw-r--r-- "), FALSE);
2074 static bool
2075 draw_lineno(struct view *view, unsigned int lineno)
2077         char number[10];
2078         int digits3 = view->digits < 3 ? 3 : view->digits;
2079         int max = MIN(view->width + view->yoffset - view->col, digits3);
2080         char *text = NULL;
2082         lineno += view->offset + 1;
2083         if (lineno == 1 || (lineno % opt_num_interval) == 0) {
2084                 static char fmt[] = "%1ld";
2086                 fmt[1] = '0' + (view->digits <= 9 ? digits3 : 1);
2087                 if (string_format(number, fmt, lineno))
2088                         text = number;
2089         }
2090         if (text)
2091                 view->col += draw_chars(view, LINE_LINE_NUMBER, text, max, TRUE);
2092         else
2093                 view->col += draw_space(view, LINE_LINE_NUMBER, max, digits3);
2094         return draw_graphic(view, LINE_DEFAULT, &line_graphics[LINE_GRAPHIC_VLINE], 1);
2097 static bool
2098 draw_view_line(struct view *view, unsigned int lineno)
2100         struct line *line;
2101         bool selected = (view->offset + lineno == view->lineno);
2103         assert(view_is_displayed(view));
2105         if (view->offset + lineno >= view->lines)
2106                 return FALSE;
2108         line = &view->line[view->offset + lineno];
2110         wmove(view->win, lineno, 0);
2111         if (line->cleareol)
2112                 wclrtoeol(view->win);
2113         view->col = 0;
2114         view->curline = line;
2115         view->curtype = LINE_NONE;
2116         line->selected = FALSE;
2117         line->dirty = line->cleareol = 0;
2119         if (selected) {
2120                 set_view_attr(view, LINE_CURSOR);
2121                 line->selected = TRUE;
2122                 view->ops->select(view, line);
2123         }
2125         return view->ops->draw(view, line, lineno);
2128 static void
2129 redraw_view_dirty(struct view *view)
2131         bool dirty = FALSE;
2132         int lineno;
2134         for (lineno = 0; lineno < view->height; lineno++) {
2135                 if (view->offset + lineno >= view->lines)
2136                         break;
2137                 if (!view->line[view->offset + lineno].dirty)
2138                         continue;
2139                 dirty = TRUE;
2140                 if (!draw_view_line(view, lineno))
2141                         break;
2142         }
2144         if (!dirty)
2145                 return;
2146         wnoutrefresh(view->win);
2149 static void
2150 redraw_view_from(struct view *view, int lineno)
2152         assert(0 <= lineno && lineno < view->height);
2154         for (; lineno < view->height; lineno++) {
2155                 if (!draw_view_line(view, lineno))
2156                         break;
2157         }
2159         wnoutrefresh(view->win);
2162 static void
2163 redraw_view(struct view *view)
2165         werase(view->win);
2166         redraw_view_from(view, 0);
2170 static void
2171 update_view_title(struct view *view)
2173         char buf[SIZEOF_STR];
2174         char state[SIZEOF_STR];
2175         size_t bufpos = 0, statelen = 0;
2177         assert(view_is_displayed(view));
2179         if (view != VIEW(REQ_VIEW_STATUS) && view->lines) {
2180                 unsigned int view_lines = view->offset + view->height;
2181                 unsigned int lines = view->lines
2182                                    ? MIN(view_lines, view->lines) * 100 / view->lines
2183                                    : 0;
2185                 string_format_from(state, &statelen, " - %s %d of %d (%d%%)",
2186                                    view->ops->type,
2187                                    view->lineno + 1,
2188                                    view->lines,
2189                                    lines);
2191         }
2193         if (view->pipe) {
2194                 time_t secs = time(NULL) - view->start_time;
2196                 /* Three git seconds are a long time ... */
2197                 if (secs > 2)
2198                         string_format_from(state, &statelen, " loading %lds", secs);
2199         }
2201         string_format_from(buf, &bufpos, "[%s]", view->name);
2202         if (*view->ref && bufpos < view->width) {
2203                 size_t refsize = strlen(view->ref);
2204                 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2206                 if (minsize < view->width)
2207                         refsize = view->width - minsize + 7;
2208                 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2209         }
2211         if (statelen && bufpos < view->width) {
2212                 string_format_from(buf, &bufpos, "%s", state);
2213         }
2215         if (view == display[current_view])
2216                 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2217         else
2218                 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2220         mvwaddnstr(view->title, 0, 0, buf, bufpos);
2221         wclrtoeol(view->title);
2222         wnoutrefresh(view->title);
2225 static int
2226 apply_step(double step, int value)
2228         if (step >= 1)
2229                 return (int) step;
2230         value *= step + 0.01;
2231         return value ? value : 1;
2234 static void
2235 resize_display(void)
2237         int offset, i;
2238         struct view *base = display[0];
2239         struct view *view = display[1] ? display[1] : display[0];
2241         /* Setup window dimensions */
2243         getmaxyx(stdscr, base->height, base->width);
2245         /* Make room for the status window. */
2246         base->height -= 1;
2248         if (view != base) {
2249                 /* Horizontal split. */
2250                 view->width   = base->width;
2251                 view->height  = apply_step(opt_scale_split_view, base->height);
2252                 view->height  = MAX(view->height, MIN_VIEW_HEIGHT);
2253                 view->height  = MIN(view->height, base->height - MIN_VIEW_HEIGHT);
2254                 base->height -= view->height;
2256                 /* Make room for the title bar. */
2257                 view->height -= 1;
2258         }
2260         /* Make room for the title bar. */
2261         base->height -= 1;
2263         offset = 0;
2265         foreach_displayed_view (view, i) {
2266                 if (!view->win) {
2267                         view->win = newwin(view->height, 0, offset, 0);
2268                         if (!view->win)
2269                                 die("Failed to create %s view", view->name);
2271                         scrollok(view->win, FALSE);
2273                         view->title = newwin(1, 0, offset + view->height, 0);
2274                         if (!view->title)
2275                                 die("Failed to create title window");
2277                 } else {
2278                         wresize(view->win, view->height, view->width);
2279                         mvwin(view->win,   offset, 0);
2280                         mvwin(view->title, offset + view->height, 0);
2281                 }
2283                 offset += view->height + 1;
2284         }
2287 static void
2288 redraw_display(bool clear)
2290         struct view *view;
2291         int i;
2293         foreach_displayed_view (view, i) {
2294                 if (clear)
2295                         wclear(view->win);
2296                 redraw_view(view);
2297                 update_view_title(view);
2298         }
2301 static void
2302 toggle_view_option(bool *option, const char *help)
2304         *option = !*option;
2305         redraw_display(FALSE);
2306         report("%sabling %s", *option ? "En" : "Dis", help);
2309 static void
2310 open_option_menu(void)
2312         const struct menu_item menu[] = {
2313                 { '.', "line numbers", &opt_line_number },
2314                 { 'D', "date display", &opt_date },
2315                 { 'A', "author display", &opt_author },
2316                 { 'g', "revision graph display", &opt_rev_graph },
2317                 { 'F', "reference display", &opt_show_refs },
2318                 { 0 }
2319         };
2320         int selected = 0;
2322         if (prompt_menu("Toggle option", menu, &selected))
2323                 toggle_view_option(menu[selected].data, menu[selected].text);
2326 static void
2327 maximize_view(struct view *view)
2329         memset(display, 0, sizeof(display));
2330         current_view = 0;
2331         display[current_view] = view;
2332         resize_display();
2333         redraw_display(FALSE);
2334         report("");
2338 /*
2339  * Navigation
2340  */
2342 static bool
2343 goto_view_line(struct view *view, unsigned long offset, unsigned long lineno)
2345         if (lineno >= view->lines)
2346                 lineno = view->lines > 0 ? view->lines - 1 : 0;
2348         if (offset > lineno || offset + view->height <= lineno) {
2349                 unsigned long half = view->height / 2;
2351                 if (lineno > half)
2352                         offset = lineno - half;
2353                 else
2354                         offset = 0;
2355         }
2357         if (offset != view->offset || lineno != view->lineno) {
2358                 view->offset = offset;
2359                 view->lineno = lineno;
2360                 return TRUE;
2361         }
2363         return FALSE;
2366 /* Scrolling backend */
2367 static void
2368 do_scroll_view(struct view *view, int lines)
2370         bool redraw_current_line = FALSE;
2372         /* The rendering expects the new offset. */
2373         view->offset += lines;
2375         assert(0 <= view->offset && view->offset < view->lines);
2376         assert(lines);
2378         /* Move current line into the view. */
2379         if (view->lineno < view->offset) {
2380                 view->lineno = view->offset;
2381                 redraw_current_line = TRUE;
2382         } else if (view->lineno >= view->offset + view->height) {
2383                 view->lineno = view->offset + view->height - 1;
2384                 redraw_current_line = TRUE;
2385         }
2387         assert(view->offset <= view->lineno && view->lineno < view->lines);
2389         /* Redraw the whole screen if scrolling is pointless. */
2390         if (view->height < ABS(lines)) {
2391                 redraw_view(view);
2393         } else {
2394                 int line = lines > 0 ? view->height - lines : 0;
2395                 int end = line + ABS(lines);
2397                 scrollok(view->win, TRUE);
2398                 wscrl(view->win, lines);
2399                 scrollok(view->win, FALSE);
2401                 while (line < end && draw_view_line(view, line))
2402                         line++;
2404                 if (redraw_current_line)
2405                         draw_view_line(view, view->lineno - view->offset);
2406                 wnoutrefresh(view->win);
2407         }
2409         view->has_scrolled = TRUE;
2410         report("");
2413 /* Scroll frontend */
2414 static void
2415 scroll_view(struct view *view, enum request request)
2417         int lines = 1;
2419         assert(view_is_displayed(view));
2421         switch (request) {
2422         case REQ_SCROLL_LEFT:
2423                 if (view->yoffset == 0) {
2424                         report("Cannot scroll beyond the first column");
2425                         return;
2426                 }
2427                 if (view->yoffset <= apply_step(opt_hscroll, view->width))
2428                         view->yoffset = 0;
2429                 else
2430                         view->yoffset -= apply_step(opt_hscroll, view->width);
2431                 redraw_view_from(view, 0);
2432                 report("");
2433                 return;
2434         case REQ_SCROLL_RIGHT:
2435                 view->yoffset += apply_step(opt_hscroll, view->width);
2436                 redraw_view(view);
2437                 report("");
2438                 return;
2439         case REQ_SCROLL_PAGE_DOWN:
2440                 lines = view->height;
2441         case REQ_SCROLL_LINE_DOWN:
2442                 if (view->offset + lines > view->lines)
2443                         lines = view->lines - view->offset;
2445                 if (lines == 0 || view->offset + view->height >= view->lines) {
2446                         report("Cannot scroll beyond the last line");
2447                         return;
2448                 }
2449                 break;
2451         case REQ_SCROLL_PAGE_UP:
2452                 lines = view->height;
2453         case REQ_SCROLL_LINE_UP:
2454                 if (lines > view->offset)
2455                         lines = view->offset;
2457                 if (lines == 0) {
2458                         report("Cannot scroll beyond the first line");
2459                         return;
2460                 }
2462                 lines = -lines;
2463                 break;
2465         default:
2466                 die("request %d not handled in switch", request);
2467         }
2469         do_scroll_view(view, lines);
2472 /* Cursor moving */
2473 static void
2474 move_view(struct view *view, enum request request)
2476         int scroll_steps = 0;
2477         int steps;
2479         switch (request) {
2480         case REQ_MOVE_FIRST_LINE:
2481                 steps = -view->lineno;
2482                 break;
2484         case REQ_MOVE_LAST_LINE:
2485                 steps = view->lines - view->lineno - 1;
2486                 break;
2488         case REQ_MOVE_PAGE_UP:
2489                 steps = view->height > view->lineno
2490                       ? -view->lineno : -view->height;
2491                 break;
2493         case REQ_MOVE_PAGE_DOWN:
2494                 steps = view->lineno + view->height >= view->lines
2495                       ? view->lines - view->lineno - 1 : view->height;
2496                 break;
2498         case REQ_MOVE_UP:
2499                 steps = -1;
2500                 break;
2502         case REQ_MOVE_DOWN:
2503                 steps = 1;
2504                 break;
2506         default:
2507                 die("request %d not handled in switch", request);
2508         }
2510         if (steps <= 0 && view->lineno == 0) {
2511                 report("Cannot move beyond the first line");
2512                 return;
2514         } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2515                 report("Cannot move beyond the last line");
2516                 return;
2517         }
2519         /* Move the current line */
2520         view->lineno += steps;
2521         assert(0 <= view->lineno && view->lineno < view->lines);
2523         /* Check whether the view needs to be scrolled */
2524         if (view->lineno < view->offset ||
2525             view->lineno >= view->offset + view->height) {
2526                 scroll_steps = steps;
2527                 if (steps < 0 && -steps > view->offset) {
2528                         scroll_steps = -view->offset;
2530                 } else if (steps > 0) {
2531                         if (view->lineno == view->lines - 1 &&
2532                             view->lines > view->height) {
2533                                 scroll_steps = view->lines - view->offset - 1;
2534                                 if (scroll_steps >= view->height)
2535                                         scroll_steps -= view->height - 1;
2536                         }
2537                 }
2538         }
2540         if (!view_is_displayed(view)) {
2541                 view->offset += scroll_steps;
2542                 assert(0 <= view->offset && view->offset < view->lines);
2543                 view->ops->select(view, &view->line[view->lineno]);
2544                 return;
2545         }
2547         /* Repaint the old "current" line if we be scrolling */
2548         if (ABS(steps) < view->height)
2549                 draw_view_line(view, view->lineno - steps - view->offset);
2551         if (scroll_steps) {
2552                 do_scroll_view(view, scroll_steps);
2553                 return;
2554         }
2556         /* Draw the current line */
2557         draw_view_line(view, view->lineno - view->offset);
2559         wnoutrefresh(view->win);
2560         report("");
2564 /*
2565  * Searching
2566  */
2568 static void search_view(struct view *view, enum request request);
2570 static bool
2571 grep_text(struct view *view, const char *text[])
2573         regmatch_t pmatch;
2574         size_t i;
2576         for (i = 0; text[i]; i++)
2577                 if (*text[i] &&
2578                     regexec(view->regex, text[i], 1, &pmatch, 0) != REG_NOMATCH)
2579                         return TRUE;
2580         return FALSE;
2583 static void
2584 select_view_line(struct view *view, unsigned long lineno)
2586         unsigned long old_lineno = view->lineno;
2587         unsigned long old_offset = view->offset;
2589         if (goto_view_line(view, view->offset, lineno)) {
2590                 if (view_is_displayed(view)) {
2591                         if (old_offset != view->offset) {
2592                                 redraw_view(view);
2593                         } else {
2594                                 draw_view_line(view, old_lineno - view->offset);
2595                                 draw_view_line(view, view->lineno - view->offset);
2596                                 wnoutrefresh(view->win);
2597                         }
2598                 } else {
2599                         view->ops->select(view, &view->line[view->lineno]);
2600                 }
2601         }
2604 static void
2605 find_next(struct view *view, enum request request)
2607         unsigned long lineno = view->lineno;
2608         int direction;
2610         if (!*view->grep) {
2611                 if (!*opt_search)
2612                         report("No previous search");
2613                 else
2614                         search_view(view, request);
2615                 return;
2616         }
2618         switch (request) {
2619         case REQ_SEARCH:
2620         case REQ_FIND_NEXT:
2621                 direction = 1;
2622                 break;
2624         case REQ_SEARCH_BACK:
2625         case REQ_FIND_PREV:
2626                 direction = -1;
2627                 break;
2629         default:
2630                 return;
2631         }
2633         if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
2634                 lineno += direction;
2636         /* Note, lineno is unsigned long so will wrap around in which case it
2637          * will become bigger than view->lines. */
2638         for (; lineno < view->lines; lineno += direction) {
2639                 if (view->ops->grep(view, &view->line[lineno])) {
2640                         select_view_line(view, lineno);
2641                         report("Line %ld matches '%s'", lineno + 1, view->grep);
2642                         return;
2643                 }
2644         }
2646         report("No match found for '%s'", view->grep);
2649 static void
2650 search_view(struct view *view, enum request request)
2652         int regex_err;
2654         if (view->regex) {
2655                 regfree(view->regex);
2656                 *view->grep = 0;
2657         } else {
2658                 view->regex = calloc(1, sizeof(*view->regex));
2659                 if (!view->regex)
2660                         return;
2661         }
2663         regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
2664         if (regex_err != 0) {
2665                 char buf[SIZEOF_STR] = "unknown error";
2667                 regerror(regex_err, view->regex, buf, sizeof(buf));
2668                 report("Search failed: %s", buf);
2669                 return;
2670         }
2672         string_copy(view->grep, opt_search);
2674         find_next(view, request);
2677 /*
2678  * Incremental updating
2679  */
2681 static void
2682 reset_view(struct view *view)
2684         int i;
2686         for (i = 0; i < view->lines; i++)
2687                 free(view->line[i].data);
2688         free(view->line);
2690         view->p_offset = view->offset;
2691         view->p_yoffset = view->yoffset;
2692         view->p_lineno = view->lineno;
2694         view->line = NULL;
2695         view->offset = 0;
2696         view->yoffset = 0;
2697         view->lines  = 0;
2698         view->lineno = 0;
2699         view->vid[0] = 0;
2700         view->update_secs = 0;
2703 static void
2704 free_argv(const char *argv[])
2706         int argc;
2708         for (argc = 0; argv[argc]; argc++)
2709                 free((void *) argv[argc]);
2712 static bool
2713 format_argv(const char *dst_argv[], const char *src_argv[], enum format_flags flags)
2715         char buf[SIZEOF_STR];
2716         int argc;
2717         bool noreplace = flags == FORMAT_NONE;
2719         free_argv(dst_argv);
2721         for (argc = 0; src_argv[argc]; argc++) {
2722                 const char *arg = src_argv[argc];
2723                 size_t bufpos = 0;
2725                 while (arg) {
2726                         char *next = strstr(arg, "%(");
2727                         int len = next - arg;
2728                         const char *value;
2730                         if (!next || noreplace) {
2731                                 if (flags == FORMAT_DASH && !strcmp(arg, "--"))
2732                                         noreplace = TRUE;
2733                                 len = strlen(arg);
2734                                 value = "";
2736                         } else if (!prefixcmp(next, "%(directory)")) {
2737                                 value = opt_path;
2739                         } else if (!prefixcmp(next, "%(file)")) {
2740                                 value = opt_file;
2742                         } else if (!prefixcmp(next, "%(ref)")) {
2743                                 value = *opt_ref ? opt_ref : "HEAD";
2745                         } else if (!prefixcmp(next, "%(head)")) {
2746                                 value = ref_head;
2748                         } else if (!prefixcmp(next, "%(commit)")) {
2749                                 value = ref_commit;
2751                         } else if (!prefixcmp(next, "%(blob)")) {
2752                                 value = ref_blob;
2754                         } else {
2755                                 report("Unknown replacement: `%s`", next);
2756                                 return FALSE;
2757                         }
2759                         if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
2760                                 return FALSE;
2762                         arg = next && !noreplace ? strchr(next, ')') + 1 : NULL;
2763                 }
2765                 dst_argv[argc] = strdup(buf);
2766                 if (!dst_argv[argc])
2767                         break;
2768         }
2770         dst_argv[argc] = NULL;
2772         return src_argv[argc] == NULL;
2775 static bool
2776 restore_view_position(struct view *view)
2778         if (!view->p_restore || (view->pipe && view->lines <= view->p_lineno))
2779                 return FALSE;
2781         /* Changing the view position cancels the restoring. */
2782         /* FIXME: Changing back to the first line is not detected. */
2783         if (view->offset != 0 || view->lineno != 0) {
2784                 view->p_restore = FALSE;
2785                 return FALSE;
2786         }
2788         if (goto_view_line(view, view->p_offset, view->p_lineno) &&
2789             view_is_displayed(view))
2790                 werase(view->win);
2792         view->yoffset = view->p_yoffset;
2793         view->p_restore = FALSE;
2795         return TRUE;
2798 static void
2799 end_update(struct view *view, bool force)
2801         if (!view->pipe)
2802                 return;
2803         while (!view->ops->read(view, NULL))
2804                 if (!force)
2805                         return;
2806         set_nonblocking_input(FALSE);
2807         if (force)
2808                 kill_io(view->pipe);
2809         done_io(view->pipe);
2810         view->pipe = NULL;
2813 static void
2814 setup_update(struct view *view, const char *vid)
2816         set_nonblocking_input(TRUE);
2817         reset_view(view);
2818         string_copy_rev(view->vid, vid);
2819         view->pipe = &view->io;
2820         view->start_time = time(NULL);
2823 static bool
2824 prepare_update(struct view *view, const char *argv[], const char *dir,
2825                enum format_flags flags)
2827         if (view->pipe)
2828                 end_update(view, TRUE);
2829         return init_io_rd(&view->io, argv, dir, flags);
2832 static bool
2833 prepare_update_file(struct view *view, const char *name)
2835         if (view->pipe)
2836                 end_update(view, TRUE);
2837         return io_open(&view->io, name);
2840 static bool
2841 begin_update(struct view *view, bool refresh)
2843         if (view->pipe)
2844                 end_update(view, TRUE);
2846         if (refresh) {
2847                 if (!start_io(&view->io))
2848                         return FALSE;
2850         } else {
2851                 if (view == VIEW(REQ_VIEW_TREE) && strcmp(view->vid, view->id))
2852                         opt_path[0] = 0;
2854                 if (!run_io_rd(&view->io, view->ops->argv, FORMAT_ALL))
2855                         return FALSE;
2857                 /* Put the current ref_* value to the view title ref
2858                  * member. This is needed by the blob view. Most other
2859                  * views sets it automatically after loading because the
2860                  * first line is a commit line. */
2861                 string_copy_rev(view->ref, view->id);
2862         }
2864         setup_update(view, view->id);
2866         return TRUE;
2869 static bool
2870 update_view(struct view *view)
2872         char out_buffer[BUFSIZ * 2];
2873         char *line;
2874         /* Clear the view and redraw everything since the tree sorting
2875          * might have rearranged things. */
2876         bool redraw = view->lines == 0;
2877         bool can_read = TRUE;
2879         if (!view->pipe)
2880                 return TRUE;
2882         if (!io_can_read(view->pipe)) {
2883                 if (view->lines == 0 && view_is_displayed(view)) {
2884                         time_t secs = time(NULL) - view->start_time;
2886                         if (secs > 1 && secs > view->update_secs) {
2887                                 if (view->update_secs == 0)
2888                                         redraw_view(view);
2889                                 update_view_title(view);
2890                                 view->update_secs = secs;
2891                         }
2892                 }
2893                 return TRUE;
2894         }
2896         for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
2897                 if (opt_iconv != ICONV_NONE) {
2898                         ICONV_CONST char *inbuf = line;
2899                         size_t inlen = strlen(line) + 1;
2901                         char *outbuf = out_buffer;
2902                         size_t outlen = sizeof(out_buffer);
2904                         size_t ret;
2906                         ret = iconv(opt_iconv, &inbuf, &inlen, &outbuf, &outlen);
2907                         if (ret != (size_t) -1)
2908                                 line = out_buffer;
2909                 }
2911                 if (!view->ops->read(view, line)) {
2912                         report("Allocation failure");
2913                         end_update(view, TRUE);
2914                         return FALSE;
2915                 }
2916         }
2918         {
2919                 unsigned long lines = view->lines;
2920                 int digits;
2922                 for (digits = 0; lines; digits++)
2923                         lines /= 10;
2925                 /* Keep the displayed view in sync with line number scaling. */
2926                 if (digits != view->digits) {
2927                         view->digits = digits;
2928                         if (opt_line_number || view == VIEW(REQ_VIEW_BLAME))
2929                                 redraw = TRUE;
2930                 }
2931         }
2933         if (io_error(view->pipe)) {
2934                 report("Failed to read: %s", io_strerror(view->pipe));
2935                 end_update(view, TRUE);
2937         } else if (io_eof(view->pipe)) {
2938                 report("");
2939                 end_update(view, FALSE);
2940         }
2942         if (restore_view_position(view))
2943                 redraw = TRUE;
2945         if (!view_is_displayed(view))
2946                 return TRUE;
2948         if (redraw)
2949                 redraw_view_from(view, 0);
2950         else
2951                 redraw_view_dirty(view);
2953         /* Update the title _after_ the redraw so that if the redraw picks up a
2954          * commit reference in view->ref it'll be available here. */
2955         update_view_title(view);
2956         return TRUE;
2959 DEFINE_ALLOCATOR(realloc_lines, struct line, 256)
2961 static struct line *
2962 add_line_data(struct view *view, void *data, enum line_type type)
2964         struct line *line;
2966         if (!realloc_lines(&view->line, view->lines, 1))
2967                 return NULL;
2969         line = &view->line[view->lines++];
2970         memset(line, 0, sizeof(*line));
2971         line->type = type;
2972         line->data = data;
2973         line->dirty = 1;
2975         return line;
2978 static struct line *
2979 add_line_text(struct view *view, const char *text, enum line_type type)
2981         char *data = text ? strdup(text) : NULL;
2983         return data ? add_line_data(view, data, type) : NULL;
2986 static struct line *
2987 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
2989         char buf[SIZEOF_STR];
2990         va_list args;
2992         va_start(args, fmt);
2993         if (vsnprintf(buf, sizeof(buf), fmt, args) >= sizeof(buf))
2994                 buf[0] = 0;
2995         va_end(args);
2997         return buf[0] ? add_line_text(view, buf, type) : NULL;
3000 /*
3001  * View opening
3002  */
3004 enum open_flags {
3005         OPEN_DEFAULT = 0,       /* Use default view switching. */
3006         OPEN_SPLIT = 1,         /* Split current view. */
3007         OPEN_RELOAD = 4,        /* Reload view even if it is the current. */
3008         OPEN_REFRESH = 16,      /* Refresh view using previous command. */
3009         OPEN_PREPARED = 32,     /* Open already prepared command. */
3010 };
3012 static void
3013 open_view(struct view *prev, enum request request, enum open_flags flags)
3015         bool split = !!(flags & OPEN_SPLIT);
3016         bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
3017         bool nomaximize = !!(flags & OPEN_REFRESH);
3018         struct view *view = VIEW(request);
3019         int nviews = displayed_views();
3020         struct view *base_view = display[0];
3022         if (view == prev && nviews == 1 && !reload) {
3023                 report("Already in %s view", view->name);
3024                 return;
3025         }
3027         if (view->git_dir && !opt_git_dir[0]) {
3028                 report("The %s view is disabled in pager view", view->name);
3029                 return;
3030         }
3032         if (split) {
3033                 display[1] = view;
3034                 current_view = 1;
3035         } else if (!nomaximize) {
3036                 /* Maximize the current view. */
3037                 memset(display, 0, sizeof(display));
3038                 current_view = 0;
3039                 display[current_view] = view;
3040         }
3042         /* Resize the view when switching between split- and full-screen,
3043          * or when switching between two different full-screen views. */
3044         if (nviews != displayed_views() ||
3045             (nviews == 1 && base_view != display[0]))
3046                 resize_display();
3048         if (view->ops->open) {
3049                 if (view->pipe)
3050                         end_update(view, TRUE);
3051                 if (!view->ops->open(view)) {
3052                         report("Failed to load %s view", view->name);
3053                         return;
3054                 }
3055                 restore_view_position(view);
3057         } else if ((reload || strcmp(view->vid, view->id)) &&
3058                    !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
3059                 report("Failed to load %s view", view->name);
3060                 return;
3061         }
3063         if (split && prev->lineno - prev->offset >= prev->height) {
3064                 /* Take the title line into account. */
3065                 int lines = prev->lineno - prev->offset - prev->height + 1;
3067                 /* Scroll the view that was split if the current line is
3068                  * outside the new limited view. */
3069                 do_scroll_view(prev, lines);
3070         }
3072         if (prev && view != prev) {
3073                 if (split) {
3074                         /* "Blur" the previous view. */
3075                         update_view_title(prev);
3076                 }
3078                 view->parent = prev;
3079         }
3081         if (view->pipe && view->lines == 0) {
3082                 /* Clear the old view and let the incremental updating refill
3083                  * the screen. */
3084                 werase(view->win);
3085                 view->p_restore = flags & (OPEN_RELOAD | OPEN_REFRESH);
3086                 report("");
3087         } else if (view_is_displayed(view)) {
3088                 redraw_view(view);
3089                 report("");
3090         }
3093 static void
3094 open_external_viewer(const char *argv[], const char *dir)
3096         def_prog_mode();           /* save current tty modes */
3097         endwin();                  /* restore original tty modes */
3098         run_io_fg(argv, dir);
3099         fprintf(stderr, "Press Enter to continue");
3100         getc(opt_tty);
3101         reset_prog_mode();
3102         redraw_display(TRUE);
3105 static void
3106 open_mergetool(const char *file)
3108         const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
3110         open_external_viewer(mergetool_argv, opt_cdup);
3113 static void
3114 open_editor(bool from_root, const char *file)
3116         const char *editor_argv[] = { "vi", file, NULL };
3117         const char *editor;
3119         editor = getenv("GIT_EDITOR");
3120         if (!editor && *opt_editor)
3121                 editor = opt_editor;
3122         if (!editor)
3123                 editor = getenv("VISUAL");
3124         if (!editor)
3125                 editor = getenv("EDITOR");
3126         if (!editor)
3127                 editor = "vi";
3129         editor_argv[0] = editor;
3130         open_external_viewer(editor_argv, from_root ? opt_cdup : NULL);
3133 static void
3134 open_run_request(enum request request)
3136         struct run_request *req = get_run_request(request);
3137         const char *argv[ARRAY_SIZE(req->argv)] = { NULL };
3139         if (!req) {
3140                 report("Unknown run request");
3141                 return;
3142         }
3144         if (format_argv(argv, req->argv, FORMAT_ALL))
3145                 open_external_viewer(argv, NULL);
3146         free_argv(argv);
3149 /*
3150  * User request switch noodle
3151  */
3153 static int
3154 view_driver(struct view *view, enum request request)
3156         int i;
3158         if (request == REQ_NONE)
3159                 return TRUE;
3161         if (request > REQ_NONE) {
3162                 open_run_request(request);
3163                 /* FIXME: When all views can refresh always do this. */
3164                 if (view == VIEW(REQ_VIEW_STATUS) ||
3165                     view == VIEW(REQ_VIEW_MAIN) ||
3166                     view == VIEW(REQ_VIEW_LOG) ||
3167                     view == VIEW(REQ_VIEW_BRANCH) ||
3168                     view == VIEW(REQ_VIEW_STAGE))
3169                         request = REQ_REFRESH;
3170                 else
3171                         return TRUE;
3172         }
3174         if (view && view->lines) {
3175                 request = view->ops->request(view, request, &view->line[view->lineno]);
3176                 if (request == REQ_NONE)
3177                         return TRUE;
3178         }
3180         switch (request) {
3181         case REQ_MOVE_UP:
3182         case REQ_MOVE_DOWN:
3183         case REQ_MOVE_PAGE_UP:
3184         case REQ_MOVE_PAGE_DOWN:
3185         case REQ_MOVE_FIRST_LINE:
3186         case REQ_MOVE_LAST_LINE:
3187                 move_view(view, request);
3188                 break;
3190         case REQ_SCROLL_LEFT:
3191         case REQ_SCROLL_RIGHT:
3192         case REQ_SCROLL_LINE_DOWN:
3193         case REQ_SCROLL_LINE_UP:
3194         case REQ_SCROLL_PAGE_DOWN:
3195         case REQ_SCROLL_PAGE_UP:
3196                 scroll_view(view, request);
3197                 break;
3199         case REQ_VIEW_BLAME:
3200                 if (!opt_file[0]) {
3201                         report("No file chosen, press %s to open tree view",
3202                                get_key(REQ_VIEW_TREE));
3203                         break;
3204                 }
3205                 open_view(view, request, OPEN_DEFAULT);
3206                 break;
3208         case REQ_VIEW_BLOB:
3209                 if (!ref_blob[0]) {
3210                         report("No file chosen, press %s to open tree view",
3211                                get_key(REQ_VIEW_TREE));
3212                         break;
3213                 }
3214                 open_view(view, request, OPEN_DEFAULT);
3215                 break;
3217         case REQ_VIEW_PAGER:
3218                 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3219                         report("No pager content, press %s to run command from prompt",
3220                                get_key(REQ_PROMPT));
3221                         break;
3222                 }
3223                 open_view(view, request, OPEN_DEFAULT);
3224                 break;
3226         case REQ_VIEW_STAGE:
3227                 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3228                         report("No stage content, press %s to open the status view and choose file",
3229                                get_key(REQ_VIEW_STATUS));
3230                         break;
3231                 }
3232                 open_view(view, request, OPEN_DEFAULT);
3233                 break;
3235         case REQ_VIEW_STATUS:
3236                 if (opt_is_inside_work_tree == FALSE) {
3237                         report("The status view requires a working tree");
3238                         break;
3239                 }
3240                 open_view(view, request, OPEN_DEFAULT);
3241                 break;
3243         case REQ_VIEW_MAIN:
3244         case REQ_VIEW_DIFF:
3245         case REQ_VIEW_LOG:
3246         case REQ_VIEW_TREE:
3247         case REQ_VIEW_HELP:
3248         case REQ_VIEW_BRANCH:
3249                 open_view(view, request, OPEN_DEFAULT);
3250                 break;
3252         case REQ_NEXT:
3253         case REQ_PREVIOUS:
3254                 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3256                 if ((view == VIEW(REQ_VIEW_DIFF) &&
3257                      view->parent == VIEW(REQ_VIEW_MAIN)) ||
3258                    (view == VIEW(REQ_VIEW_DIFF) &&
3259                      view->parent == VIEW(REQ_VIEW_BLAME)) ||
3260                    (view == VIEW(REQ_VIEW_STAGE) &&
3261                      view->parent == VIEW(REQ_VIEW_STATUS)) ||
3262                    (view == VIEW(REQ_VIEW_BLOB) &&
3263                      view->parent == VIEW(REQ_VIEW_TREE)) ||
3264                    (view == VIEW(REQ_VIEW_MAIN) &&
3265                      view->parent == VIEW(REQ_VIEW_BRANCH))) {
3266                         int line;
3268                         view = view->parent;
3269                         line = view->lineno;
3270                         move_view(view, request);
3271                         if (view_is_displayed(view))
3272                                 update_view_title(view);
3273                         if (line != view->lineno)
3274                                 view->ops->request(view, REQ_ENTER,
3275                                                    &view->line[view->lineno]);
3277                 } else {
3278                         move_view(view, request);
3279                 }
3280                 break;
3282         case REQ_VIEW_NEXT:
3283         {
3284                 int nviews = displayed_views();
3285                 int next_view = (current_view + 1) % nviews;
3287                 if (next_view == current_view) {
3288                         report("Only one view is displayed");
3289                         break;
3290                 }
3292                 current_view = next_view;
3293                 /* Blur out the title of the previous view. */
3294                 update_view_title(view);
3295                 report("");
3296                 break;
3297         }
3298         case REQ_REFRESH:
3299                 report("Refreshing is not yet supported for the %s view", view->name);
3300                 break;
3302         case REQ_MAXIMIZE:
3303                 if (displayed_views() == 2)
3304                         maximize_view(view);
3305                 break;
3307         case REQ_OPTIONS:
3308                 open_option_menu();
3309                 break;
3311         case REQ_TOGGLE_LINENO:
3312                 toggle_view_option(&opt_line_number, "line numbers");
3313                 break;
3315         case REQ_TOGGLE_DATE:
3316                 toggle_view_option(&opt_date, "date display");
3317                 break;
3319         case REQ_TOGGLE_AUTHOR:
3320                 toggle_view_option(&opt_author, "author display");
3321                 break;
3323         case REQ_TOGGLE_REV_GRAPH:
3324                 toggle_view_option(&opt_rev_graph, "revision graph display");
3325                 break;
3327         case REQ_TOGGLE_REFS:
3328                 toggle_view_option(&opt_show_refs, "reference display");
3329                 break;
3331         case REQ_TOGGLE_SORT_FIELD:
3332         case REQ_TOGGLE_SORT_ORDER:
3333                 report("Sorting is not yet supported for the %s view", view->name);
3334                 break;
3336         case REQ_SEARCH:
3337         case REQ_SEARCH_BACK:
3338                 search_view(view, request);
3339                 break;
3341         case REQ_FIND_NEXT:
3342         case REQ_FIND_PREV:
3343                 find_next(view, request);
3344                 break;
3346         case REQ_STOP_LOADING:
3347                 for (i = 0; i < ARRAY_SIZE(views); i++) {
3348                         view = &views[i];
3349                         if (view->pipe)
3350                                 report("Stopped loading the %s view", view->name),
3351                         end_update(view, TRUE);
3352                 }
3353                 break;
3355         case REQ_SHOW_VERSION:
3356                 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3357                 return TRUE;
3359         case REQ_SCREEN_REDRAW:
3360                 redraw_display(TRUE);
3361                 break;
3363         case REQ_EDIT:
3364                 report("Nothing to edit");
3365                 break;
3367         case REQ_ENTER:
3368                 report("Nothing to enter");
3369                 break;
3371         case REQ_VIEW_CLOSE:
3372                 /* XXX: Mark closed views by letting view->parent point to the
3373                  * view itself. Parents to closed view should never be
3374                  * followed. */
3375                 if (view->parent &&
3376                     view->parent->parent != view->parent) {
3377                         maximize_view(view->parent);
3378                         view->parent = view;
3379                         break;
3380                 }
3381                 /* Fall-through */
3382         case REQ_QUIT:
3383                 return FALSE;
3385         default:
3386                 report("Unknown key, press 'h' for help");
3387                 return TRUE;
3388         }
3390         return TRUE;
3394 /*
3395  * View backend utilities
3396  */
3398 enum sort_field {
3399         ORDERBY_NAME,
3400         ORDERBY_DATE,
3401         ORDERBY_AUTHOR,
3402 };
3404 struct sort_state {
3405         const enum sort_field *fields;
3406         size_t size, current;
3407         bool reverse;
3408 };
3410 #define SORT_STATE(fields) { fields, ARRAY_SIZE(fields), 0 }
3411 #define get_sort_field(state) ((state).fields[(state).current])
3412 #define sort_order(state, result) ((state).reverse ? -(result) : (result))
3414 static void
3415 sort_view(struct view *view, enum request request, struct sort_state *state,
3416           int (*compare)(const void *, const void *))
3418         switch (request) {
3419         case REQ_TOGGLE_SORT_FIELD:
3420                 state->current = (state->current + 1) % state->size;
3421                 break;
3423         case REQ_TOGGLE_SORT_ORDER:
3424                 state->reverse = !state->reverse;
3425                 break;
3426         default:
3427                 die("Not a sort request");
3428         }
3430         qsort(view->line, view->lines, sizeof(*view->line), compare);
3431         redraw_view(view);
3434 DEFINE_ALLOCATOR(realloc_authors, const char *, 256)
3436 /* Small author cache to reduce memory consumption. It uses binary
3437  * search to lookup or find place to position new entries. No entries
3438  * are ever freed. */
3439 static const char *
3440 get_author(const char *name)
3442         static const char **authors;
3443         static size_t authors_size;
3444         int from = 0, to = authors_size - 1;
3446         while (from <= to) {
3447                 size_t pos = (to + from) / 2;
3448                 int cmp = strcmp(name, authors[pos]);
3450                 if (!cmp)
3451                         return authors[pos];
3453                 if (cmp < 0)
3454                         to = pos - 1;
3455                 else
3456                         from = pos + 1;
3457         }
3459         if (!realloc_authors(&authors, authors_size, 1))
3460                 return NULL;
3461         name = strdup(name);
3462         if (!name)
3463                 return NULL;
3465         memmove(authors + from + 1, authors + from, (authors_size - from) * sizeof(*authors));
3466         authors[from] = name;
3467         authors_size++;
3469         return name;
3472 static void
3473 parse_timezone(time_t *time, const char *zone)
3475         long tz;
3477         tz  = ('0' - zone[1]) * 60 * 60 * 10;
3478         tz += ('0' - zone[2]) * 60 * 60;
3479         tz += ('0' - zone[3]) * 60;
3480         tz += ('0' - zone[4]);
3482         if (zone[0] == '-')
3483                 tz = -tz;
3485         *time -= tz;
3488 /* Parse author lines where the name may be empty:
3489  *      author  <email@address.tld> 1138474660 +0100
3490  */
3491 static void
3492 parse_author_line(char *ident, const char **author, time_t *time)
3494         char *nameend = strchr(ident, '<');
3495         char *emailend = strchr(ident, '>');
3497         if (nameend && emailend)
3498                 *nameend = *emailend = 0;
3499         ident = chomp_string(ident);
3500         if (!*ident) {
3501                 if (nameend)
3502                         ident = chomp_string(nameend + 1);
3503                 if (!*ident)
3504                         ident = "Unknown";
3505         }
3507         *author = get_author(ident);
3509         /* Parse epoch and timezone */
3510         if (emailend && emailend[1] == ' ') {
3511                 char *secs = emailend + 2;
3512                 char *zone = strchr(secs, ' ');
3514                 *time = (time_t) atol(secs);
3516                 if (zone && strlen(zone) == STRING_SIZE(" +0700"))
3517                         parse_timezone(time, zone + 1);
3518         }
3521 static bool
3522 open_commit_parent_menu(char buf[SIZEOF_STR], int *parents)
3524         char rev[SIZEOF_REV];
3525         const char *revlist_argv[] = {
3526                 "git", "log", "--no-color", "-1", "--pretty=format:%s", rev, NULL
3527         };
3528         struct menu_item *items;
3529         char text[SIZEOF_STR];
3530         bool ok = TRUE;
3531         int i;
3533         items = calloc(*parents + 1, sizeof(*items));
3534         if (!items)
3535                 return FALSE;
3537         for (i = 0; i < *parents; i++) {
3538                 string_copy_rev(rev, &buf[SIZEOF_REV * i]);
3539                 if (!run_io_buf(revlist_argv, text, sizeof(text)) ||
3540                     !(items[i].text = strdup(text))) {
3541                         ok = FALSE;
3542                         break;
3543                 }
3544         }
3546         if (ok) {
3547                 *parents = 0;
3548                 ok = prompt_menu("Select parent", items, parents);
3549         }
3550         for (i = 0; items[i].text; i++)
3551                 free((char *) items[i].text);
3552         free(items);
3553         return ok;
3556 static bool
3557 select_commit_parent(const char *id, char rev[SIZEOF_REV], const char *path)
3559         char buf[SIZEOF_STR * 4];
3560         const char *revlist_argv[] = {
3561                 "git", "log", "--no-color", "-1",
3562                         "--pretty=format:%P", id, "--", path, NULL
3563         };
3564         int parents;
3566         if (!run_io_buf(revlist_argv, buf, sizeof(buf)) ||
3567             (parents = strlen(buf) / 40) < 0) {
3568                 report("Failed to get parent information");
3569                 return FALSE;
3571         } else if (parents == 0) {
3572                 if (path)
3573                         report("Path '%s' does not exist in the parent", path);
3574                 else
3575                         report("The selected commit has no parents");
3576                 return FALSE;
3577         }
3579         if (parents > 1 && !open_commit_parent_menu(buf, &parents))
3580                 return FALSE;
3582         string_copy_rev(rev, &buf[41 * parents]);
3583         return TRUE;
3586 /*
3587  * Pager backend
3588  */
3590 static bool
3591 pager_draw(struct view *view, struct line *line, unsigned int lineno)
3593         char text[SIZEOF_STR];
3595         if (opt_line_number && draw_lineno(view, lineno))
3596                 return TRUE;
3598         string_expand(text, sizeof(text), line->data, opt_tab_size);
3599         draw_text(view, line->type, text, TRUE);
3600         return TRUE;
3603 static bool
3604 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
3606         const char *describe_argv[] = { "git", "describe", commit_id, NULL };
3607         char ref[SIZEOF_STR];
3609         if (!run_io_buf(describe_argv, ref, sizeof(ref)) || !*ref)
3610                 return TRUE;
3612         /* This is the only fatal call, since it can "corrupt" the buffer. */
3613         if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
3614                 return FALSE;
3616         return TRUE;
3619 static void
3620 add_pager_refs(struct view *view, struct line *line)
3622         char buf[SIZEOF_STR];
3623         char *commit_id = (char *)line->data + STRING_SIZE("commit ");
3624         struct ref_list *list;
3625         size_t bufpos = 0, i;
3626         const char *sep = "Refs: ";
3627         bool is_tag = FALSE;
3629         assert(line->type == LINE_COMMIT);
3631         list = get_ref_list(commit_id);
3632         if (!list) {
3633                 if (view == VIEW(REQ_VIEW_DIFF))
3634                         goto try_add_describe_ref;
3635                 return;
3636         }
3638         for (i = 0; i < list->size; i++) {
3639                 struct ref *ref = list->refs[i];
3640                 const char *fmt = ref->tag    ? "%s[%s]" :
3641                                   ref->remote ? "%s<%s>" : "%s%s";
3643                 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
3644                         return;
3645                 sep = ", ";
3646                 if (ref->tag)
3647                         is_tag = TRUE;
3648         }
3650         if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
3651 try_add_describe_ref:
3652                 /* Add <tag>-g<commit_id> "fake" reference. */
3653                 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
3654                         return;
3655         }
3657         if (bufpos == 0)
3658                 return;
3660         add_line_text(view, buf, LINE_PP_REFS);
3663 static bool
3664 pager_read(struct view *view, char *data)
3666         struct line *line;
3668         if (!data)
3669                 return TRUE;
3671         line = add_line_text(view, data, get_line_type(data));
3672         if (!line)
3673                 return FALSE;
3675         if (line->type == LINE_COMMIT &&
3676             (view == VIEW(REQ_VIEW_DIFF) ||
3677              view == VIEW(REQ_VIEW_LOG)))
3678                 add_pager_refs(view, line);
3680         return TRUE;
3683 static enum request
3684 pager_request(struct view *view, enum request request, struct line *line)
3686         int split = 0;
3688         if (request != REQ_ENTER)
3689                 return request;
3691         if (line->type == LINE_COMMIT &&
3692            (view == VIEW(REQ_VIEW_LOG) ||
3693             view == VIEW(REQ_VIEW_PAGER))) {
3694                 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
3695                 split = 1;
3696         }
3698         /* Always scroll the view even if it was split. That way
3699          * you can use Enter to scroll through the log view and
3700          * split open each commit diff. */
3701         scroll_view(view, REQ_SCROLL_LINE_DOWN);
3703         /* FIXME: A minor workaround. Scrolling the view will call report("")
3704          * but if we are scrolling a non-current view this won't properly
3705          * update the view title. */
3706         if (split)
3707                 update_view_title(view);
3709         return REQ_NONE;
3712 static bool
3713 pager_grep(struct view *view, struct line *line)
3715         const char *text[] = { line->data, NULL };
3717         return grep_text(view, text);
3720 static void
3721 pager_select(struct view *view, struct line *line)
3723         if (line->type == LINE_COMMIT) {
3724                 char *text = (char *)line->data + STRING_SIZE("commit ");
3726                 if (view != VIEW(REQ_VIEW_PAGER))
3727                         string_copy_rev(view->ref, text);
3728                 string_copy_rev(ref_commit, text);
3729         }
3732 static struct view_ops pager_ops = {
3733         "line",
3734         NULL,
3735         NULL,
3736         pager_read,
3737         pager_draw,
3738         pager_request,
3739         pager_grep,
3740         pager_select,
3741 };
3743 static const char *log_argv[SIZEOF_ARG] = {
3744         "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
3745 };
3747 static enum request
3748 log_request(struct view *view, enum request request, struct line *line)
3750         switch (request) {
3751         case REQ_REFRESH:
3752                 load_refs();
3753                 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
3754                 return REQ_NONE;
3755         default:
3756                 return pager_request(view, request, line);
3757         }
3760 static struct view_ops log_ops = {
3761         "line",
3762         log_argv,
3763         NULL,
3764         pager_read,
3765         pager_draw,
3766         log_request,
3767         pager_grep,
3768         pager_select,
3769 };
3771 static const char *diff_argv[SIZEOF_ARG] = {
3772         "git", "show", "--pretty=fuller", "--no-color", "--root",
3773                 "--patch-with-stat", "--find-copies-harder", "-C", "%(commit)", NULL
3774 };
3776 static struct view_ops diff_ops = {
3777         "line",
3778         diff_argv,
3779         NULL,
3780         pager_read,
3781         pager_draw,
3782         pager_request,
3783         pager_grep,
3784         pager_select,
3785 };
3787 /*
3788  * Help backend
3789  */
3791 static bool
3792 help_open(struct view *view)
3794         char buf[SIZEOF_STR];
3795         size_t bufpos;
3796         int i;
3798         if (view->lines > 0)
3799                 return TRUE;
3801         add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
3803         for (i = 0; i < ARRAY_SIZE(req_info); i++) {
3804                 const char *key;
3806                 if (req_info[i].request == REQ_NONE)
3807                         continue;
3809                 if (!req_info[i].request) {
3810                         add_line_text(view, "", LINE_DEFAULT);
3811                         add_line_text(view, req_info[i].help, LINE_DEFAULT);
3812                         continue;
3813                 }
3815                 key = get_key(req_info[i].request);
3816                 if (!*key)
3817                         key = "(no key defined)";
3819                 for (bufpos = 0; bufpos <= req_info[i].namelen; bufpos++) {
3820                         buf[bufpos] = tolower(req_info[i].name[bufpos]);
3821                         if (buf[bufpos] == '_')
3822                                 buf[bufpos] = '-';
3823                 }
3825                 add_line_format(view, LINE_DEFAULT, "    %-25s %-20s %s",
3826                                 key, buf, req_info[i].help);
3827         }
3829         if (run_requests) {
3830                 add_line_text(view, "", LINE_DEFAULT);
3831                 add_line_text(view, "External commands:", LINE_DEFAULT);
3832         }
3834         for (i = 0; i < run_requests; i++) {
3835                 struct run_request *req = get_run_request(REQ_NONE + i + 1);
3836                 const char *key;
3837                 int argc;
3839                 if (!req)
3840                         continue;
3842                 key = get_key_name(req->key);
3843                 if (!*key)
3844                         key = "(no key defined)";
3846                 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
3847                         if (!string_format_from(buf, &bufpos, "%s%s",
3848                                                 argc ? " " : "", req->argv[argc]))
3849                                 return REQ_NONE;
3851                 add_line_format(view, LINE_DEFAULT, "    %-10s %-14s `%s`",
3852                                 keymap_table[req->keymap].name, key, buf);
3853         }
3855         return TRUE;
3858 static struct view_ops help_ops = {
3859         "line",
3860         NULL,
3861         help_open,
3862         NULL,
3863         pager_draw,
3864         pager_request,
3865         pager_grep,
3866         pager_select,
3867 };
3870 /*
3871  * Tree backend
3872  */
3874 struct tree_stack_entry {
3875         struct tree_stack_entry *prev;  /* Entry below this in the stack */
3876         unsigned long lineno;           /* Line number to restore */
3877         char *name;                     /* Position of name in opt_path */
3878 };
3880 /* The top of the path stack. */
3881 static struct tree_stack_entry *tree_stack = NULL;
3882 unsigned long tree_lineno = 0;
3884 static void
3885 pop_tree_stack_entry(void)
3887         struct tree_stack_entry *entry = tree_stack;
3889         tree_lineno = entry->lineno;
3890         entry->name[0] = 0;
3891         tree_stack = entry->prev;
3892         free(entry);
3895 static void
3896 push_tree_stack_entry(const char *name, unsigned long lineno)
3898         struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
3899         size_t pathlen = strlen(opt_path);
3901         if (!entry)
3902                 return;
3904         entry->prev = tree_stack;
3905         entry->name = opt_path + pathlen;
3906         tree_stack = entry;
3908         if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
3909                 pop_tree_stack_entry();
3910                 return;
3911         }
3913         /* Move the current line to the first tree entry. */
3914         tree_lineno = 1;
3915         entry->lineno = lineno;
3918 /* Parse output from git-ls-tree(1):
3919  *
3920  * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
3921  */
3923 #define SIZEOF_TREE_ATTR \
3924         STRING_SIZE("100644 blob f931e1d229c3e185caad4449bf5b66ed72462657\t")
3926 #define SIZEOF_TREE_MODE \
3927         STRING_SIZE("100644 ")
3929 #define TREE_ID_OFFSET \
3930         STRING_SIZE("100644 blob ")
3932 struct tree_entry {
3933         char id[SIZEOF_REV];
3934         mode_t mode;
3935         time_t time;                    /* Date from the author ident. */
3936         const char *author;             /* Author of the commit. */
3937         char name[1];
3938 };
3940 static const char *
3941 tree_path(const struct line *line)
3943         return ((struct tree_entry *) line->data)->name;
3946 static int
3947 tree_compare_entry(const struct line *line1, const struct line *line2)
3949         if (line1->type != line2->type)
3950                 return line1->type == LINE_TREE_DIR ? -1 : 1;
3951         return strcmp(tree_path(line1), tree_path(line2));
3954 static const enum sort_field tree_sort_fields[] = {
3955         ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
3956 };
3957 static struct sort_state tree_sort_state = SORT_STATE(tree_sort_fields);
3959 static int
3960 tree_compare(const void *l1, const void *l2)
3962         const struct line *line1 = (const struct line *) l1;
3963         const struct line *line2 = (const struct line *) l2;
3964         const struct tree_entry *entry1 = ((const struct line *) l1)->data;
3965         const struct tree_entry *entry2 = ((const struct line *) l2)->data;
3967         if (line1->type == LINE_TREE_HEAD)
3968                 return -1;
3969         if (line2->type == LINE_TREE_HEAD)
3970                 return 1;
3972         switch (get_sort_field(tree_sort_state)) {
3973         case ORDERBY_DATE:
3974                 return sort_order(tree_sort_state, entry1->time - entry2->time);
3976         case ORDERBY_AUTHOR:
3977                 return sort_order(tree_sort_state, strcmp(entry1->author, entry2->author));
3979         case ORDERBY_NAME:
3980         default:
3981                 return sort_order(tree_sort_state, tree_compare_entry(line1, line2));
3982         }
3986 static struct line *
3987 tree_entry(struct view *view, enum line_type type, const char *path,
3988            const char *mode, const char *id)
3990         struct tree_entry *entry = calloc(1, sizeof(*entry) + strlen(path));
3991         struct line *line = entry ? add_line_data(view, entry, type) : NULL;
3993         if (!entry || !line) {
3994                 free(entry);
3995                 return NULL;
3996         }
3998         strncpy(entry->name, path, strlen(path));
3999         if (mode)
4000                 entry->mode = strtoul(mode, NULL, 8);
4001         if (id)
4002                 string_copy_rev(entry->id, id);
4004         return line;
4007 static bool
4008 tree_read_date(struct view *view, char *text, bool *read_date)
4010         static const char *author_name;
4011         static time_t author_time;
4013         if (!text && *read_date) {
4014                 *read_date = FALSE;
4015                 return TRUE;
4017         } else if (!text) {
4018                 char *path = *opt_path ? opt_path : ".";
4019                 /* Find next entry to process */
4020                 const char *log_file[] = {
4021                         "git", "log", "--no-color", "--pretty=raw",
4022                                 "--cc", "--raw", view->id, "--", path, NULL
4023                 };
4024                 struct io io = {};
4026                 if (!view->lines) {
4027                         tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL);
4028                         report("Tree is empty");
4029                         return TRUE;
4030                 }
4032                 if (!run_io_rd(&io, log_file, FORMAT_NONE)) {
4033                         report("Failed to load tree data");
4034                         return TRUE;
4035                 }
4037                 done_io(view->pipe);
4038                 view->io = io;
4039                 *read_date = TRUE;
4040                 return FALSE;
4042         } else if (*text == 'a' && get_line_type(text) == LINE_AUTHOR) {
4043                 parse_author_line(text + STRING_SIZE("author "),
4044                                   &author_name, &author_time);
4046         } else if (*text == ':') {
4047                 char *pos;
4048                 size_t annotated = 1;
4049                 size_t i;
4051                 pos = strchr(text, '\t');
4052                 if (!pos)
4053                         return TRUE;
4054                 text = pos + 1;
4055                 if (*opt_prefix && !strncmp(text, opt_prefix, strlen(opt_prefix)))
4056                         text += strlen(opt_prefix);
4057                 if (*opt_path && !strncmp(text, opt_path, strlen(opt_path)))
4058                         text += strlen(opt_path);
4059                 pos = strchr(text, '/');
4060                 if (pos)
4061                         *pos = 0;
4063                 for (i = 1; i < view->lines; i++) {
4064                         struct line *line = &view->line[i];
4065                         struct tree_entry *entry = line->data;
4067                         annotated += !!entry->author;
4068                         if (entry->author || strcmp(entry->name, text))
4069                                 continue;
4071                         entry->author = author_name;
4072                         entry->time = author_time;
4073                         line->dirty = 1;
4074                         break;
4075                 }
4077                 if (annotated == view->lines)
4078                         kill_io(view->pipe);
4079         }
4080         return TRUE;
4083 static bool
4084 tree_read(struct view *view, char *text)
4086         static bool read_date = FALSE;
4087         struct tree_entry *data;
4088         struct line *entry, *line;
4089         enum line_type type;
4090         size_t textlen = text ? strlen(text) : 0;
4091         char *path = text + SIZEOF_TREE_ATTR;
4093         if (read_date || !text)
4094                 return tree_read_date(view, text, &read_date);
4096         if (textlen <= SIZEOF_TREE_ATTR)
4097                 return FALSE;
4098         if (view->lines == 0 &&
4099             !tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL))
4100                 return FALSE;
4102         /* Strip the path part ... */
4103         if (*opt_path) {
4104                 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
4105                 size_t striplen = strlen(opt_path);
4107                 if (pathlen > striplen)
4108                         memmove(path, path + striplen,
4109                                 pathlen - striplen + 1);
4111                 /* Insert "link" to parent directory. */
4112                 if (view->lines == 1 &&
4113                     !tree_entry(view, LINE_TREE_DIR, "..", "040000", view->ref))
4114                         return FALSE;
4115         }
4117         type = text[SIZEOF_TREE_MODE] == 't' ? LINE_TREE_DIR : LINE_TREE_FILE;
4118         entry = tree_entry(view, type, path, text, text + TREE_ID_OFFSET);
4119         if (!entry)
4120                 return FALSE;
4121         data = entry->data;
4123         /* Skip "Directory ..." and ".." line. */
4124         for (line = &view->line[1 + !!*opt_path]; line < entry; line++) {
4125                 if (tree_compare_entry(line, entry) <= 0)
4126                         continue;
4128                 memmove(line + 1, line, (entry - line) * sizeof(*entry));
4130                 line->data = data;
4131                 line->type = type;
4132                 for (; line <= entry; line++)
4133                         line->dirty = line->cleareol = 1;
4134                 return TRUE;
4135         }
4137         if (tree_lineno > view->lineno) {
4138                 view->lineno = tree_lineno;
4139                 tree_lineno = 0;
4140         }
4142         return TRUE;
4145 static bool
4146 tree_draw(struct view *view, struct line *line, unsigned int lineno)
4148         struct tree_entry *entry = line->data;
4150         if (line->type == LINE_TREE_HEAD) {
4151                 if (draw_text(view, line->type, "Directory path /", TRUE))
4152                         return TRUE;
4153         } else {
4154                 if (draw_mode(view, entry->mode))
4155                         return TRUE;
4157                 if (opt_author && draw_author(view, entry->author))
4158                         return TRUE;
4160                 if (opt_date && draw_date(view, entry->author ? &entry->time : NULL))
4161                         return TRUE;
4162         }
4163         if (draw_text(view, line->type, entry->name, TRUE))
4164                 return TRUE;
4165         return TRUE;
4168 static void
4169 open_blob_editor()
4171         char file[SIZEOF_STR] = "/tmp/tigblob.XXXXXX";
4172         int fd = mkstemp(file);
4174         if (fd == -1)
4175                 report("Failed to create temporary file");
4176         else if (!run_io_append(blob_ops.argv, FORMAT_ALL, fd))
4177                 report("Failed to save blob data to file");
4178         else
4179                 open_editor(FALSE, file);
4180         if (fd != -1)
4181                 unlink(file);
4184 static enum request
4185 tree_request(struct view *view, enum request request, struct line *line)
4187         enum open_flags flags;
4189         switch (request) {
4190         case REQ_VIEW_BLAME:
4191                 if (line->type != LINE_TREE_FILE) {
4192                         report("Blame only supported for files");
4193                         return REQ_NONE;
4194                 }
4196                 string_copy(opt_ref, view->vid);
4197                 return request;
4199         case REQ_EDIT:
4200                 if (line->type != LINE_TREE_FILE) {
4201                         report("Edit only supported for files");
4202                 } else if (!is_head_commit(view->vid)) {
4203                         open_blob_editor();
4204                 } else {
4205                         open_editor(TRUE, opt_file);
4206                 }
4207                 return REQ_NONE;
4209         case REQ_TOGGLE_SORT_FIELD:
4210         case REQ_TOGGLE_SORT_ORDER:
4211                 sort_view(view, request, &tree_sort_state, tree_compare);
4212                 return REQ_NONE;
4214         case REQ_PARENT:
4215                 if (!*opt_path) {
4216                         /* quit view if at top of tree */
4217                         return REQ_VIEW_CLOSE;
4218                 }
4219                 /* fake 'cd  ..' */
4220                 line = &view->line[1];
4221                 break;
4223         case REQ_ENTER:
4224                 break;
4226         default:
4227                 return request;
4228         }
4230         /* Cleanup the stack if the tree view is at a different tree. */
4231         while (!*opt_path && tree_stack)
4232                 pop_tree_stack_entry();
4234         switch (line->type) {
4235         case LINE_TREE_DIR:
4236                 /* Depending on whether it is a subdirectory or parent link
4237                  * mangle the path buffer. */
4238                 if (line == &view->line[1] && *opt_path) {
4239                         pop_tree_stack_entry();
4241                 } else {
4242                         const char *basename = tree_path(line);
4244                         push_tree_stack_entry(basename, view->lineno);
4245                 }
4247                 /* Trees and subtrees share the same ID, so they are not not
4248                  * unique like blobs. */
4249                 flags = OPEN_RELOAD;
4250                 request = REQ_VIEW_TREE;
4251                 break;
4253         case LINE_TREE_FILE:
4254                 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4255                 request = REQ_VIEW_BLOB;
4256                 break;
4258         default:
4259                 return REQ_NONE;
4260         }
4262         open_view(view, request, flags);
4263         if (request == REQ_VIEW_TREE)
4264                 view->lineno = tree_lineno;
4266         return REQ_NONE;
4269 static bool
4270 tree_grep(struct view *view, struct line *line)
4272         struct tree_entry *entry = line->data;
4273         const char *text[] = {
4274                 entry->name,
4275                 opt_author ? entry->author : "",
4276                 opt_date ? mkdate(&entry->time) : "",
4277                 NULL
4278         };
4280         return grep_text(view, text);
4283 static void
4284 tree_select(struct view *view, struct line *line)
4286         struct tree_entry *entry = line->data;
4288         if (line->type == LINE_TREE_FILE) {
4289                 string_copy_rev(ref_blob, entry->id);
4290                 string_format(opt_file, "%s%s", opt_path, tree_path(line));
4292         } else if (line->type != LINE_TREE_DIR) {
4293                 return;
4294         }
4296         string_copy_rev(view->ref, entry->id);
4299 static const char *tree_argv[SIZEOF_ARG] = {
4300         "git", "ls-tree", "%(commit)", "%(directory)", NULL
4301 };
4303 static struct view_ops tree_ops = {
4304         "file",
4305         tree_argv,
4306         NULL,
4307         tree_read,
4308         tree_draw,
4309         tree_request,
4310         tree_grep,
4311         tree_select,
4312 };
4314 static bool
4315 blob_read(struct view *view, char *line)
4317         if (!line)
4318                 return TRUE;
4319         return add_line_text(view, line, LINE_DEFAULT) != NULL;
4322 static enum request
4323 blob_request(struct view *view, enum request request, struct line *line)
4325         switch (request) {
4326         case REQ_EDIT:
4327                 open_blob_editor();
4328                 return REQ_NONE;
4329         default:
4330                 return pager_request(view, request, line);
4331         }
4334 static const char *blob_argv[SIZEOF_ARG] = {
4335         "git", "cat-file", "blob", "%(blob)", NULL
4336 };
4338 static struct view_ops blob_ops = {
4339         "line",
4340         blob_argv,
4341         NULL,
4342         blob_read,
4343         pager_draw,
4344         blob_request,
4345         pager_grep,
4346         pager_select,
4347 };
4349 /*
4350  * Blame backend
4351  *
4352  * Loading the blame view is a two phase job:
4353  *
4354  *  1. File content is read either using opt_file from the
4355  *     filesystem or using git-cat-file.
4356  *  2. Then blame information is incrementally added by
4357  *     reading output from git-blame.
4358  */
4360 static const char *blame_head_argv[] = {
4361         "git", "blame", "--incremental", "--", "%(file)", NULL
4362 };
4364 static const char *blame_ref_argv[] = {
4365         "git", "blame", "--incremental", "%(ref)", "--", "%(file)", NULL
4366 };
4368 static const char *blame_cat_file_argv[] = {
4369         "git", "cat-file", "blob", "%(ref):%(file)", NULL
4370 };
4372 struct blame_commit {
4373         char id[SIZEOF_REV];            /* SHA1 ID. */
4374         char title[128];                /* First line of the commit message. */
4375         const char *author;             /* Author of the commit. */
4376         time_t time;                    /* Date from the author ident. */
4377         char filename[128];             /* Name of file. */
4378         bool has_previous;              /* Was a "previous" line detected. */
4379 };
4381 struct blame {
4382         struct blame_commit *commit;
4383         unsigned long lineno;
4384         char text[1];
4385 };
4387 static bool
4388 blame_open(struct view *view)
4390         if (*opt_ref || !io_open(&view->io, opt_file)) {
4391                 if (!run_io_rd(&view->io, blame_cat_file_argv, FORMAT_ALL))
4392                         return FALSE;
4393         }
4395         setup_update(view, opt_file);
4396         string_format(view->ref, "%s ...", opt_file);
4398         return TRUE;
4401 static struct blame_commit *
4402 get_blame_commit(struct view *view, const char *id)
4404         size_t i;
4406         for (i = 0; i < view->lines; i++) {
4407                 struct blame *blame = view->line[i].data;
4409                 if (!blame->commit)
4410                         continue;
4412                 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
4413                         return blame->commit;
4414         }
4416         {
4417                 struct blame_commit *commit = calloc(1, sizeof(*commit));
4419                 if (commit)
4420                         string_ncopy(commit->id, id, SIZEOF_REV);
4421                 return commit;
4422         }
4425 static bool
4426 parse_number(const char **posref, size_t *number, size_t min, size_t max)
4428         const char *pos = *posref;
4430         *posref = NULL;
4431         pos = strchr(pos + 1, ' ');
4432         if (!pos || !isdigit(pos[1]))
4433                 return FALSE;
4434         *number = atoi(pos + 1);
4435         if (*number < min || *number > max)
4436                 return FALSE;
4438         *posref = pos;
4439         return TRUE;
4442 static struct blame_commit *
4443 parse_blame_commit(struct view *view, const char *text, int *blamed)
4445         struct blame_commit *commit;
4446         struct blame *blame;
4447         const char *pos = text + SIZEOF_REV - 2;
4448         size_t orig_lineno = 0;
4449         size_t lineno;
4450         size_t group;
4452         if (strlen(text) <= SIZEOF_REV || pos[1] != ' ')
4453                 return NULL;
4455         if (!parse_number(&pos, &orig_lineno, 1, 9999999) ||
4456             !parse_number(&pos, &lineno, 1, view->lines) ||
4457             !parse_number(&pos, &group, 1, view->lines - lineno + 1))
4458                 return NULL;
4460         commit = get_blame_commit(view, text);
4461         if (!commit)
4462                 return NULL;
4464         *blamed += group;
4465         while (group--) {
4466                 struct line *line = &view->line[lineno + group - 1];
4468                 blame = line->data;
4469                 blame->commit = commit;
4470                 blame->lineno = orig_lineno + group - 1;
4471                 line->dirty = 1;
4472         }
4474         return commit;
4477 static bool
4478 blame_read_file(struct view *view, const char *line, bool *read_file)
4480         if (!line) {
4481                 const char **argv = *opt_ref ? blame_ref_argv : blame_head_argv;
4482                 struct io io = {};
4484                 if (view->lines == 0 && !view->parent)
4485                         die("No blame exist for %s", view->vid);
4487                 if (view->lines == 0 || !run_io_rd(&io, argv, FORMAT_ALL)) {
4488                         report("Failed to load blame data");
4489                         return TRUE;
4490                 }
4492                 done_io(view->pipe);
4493                 view->io = io;
4494                 *read_file = FALSE;
4495                 return FALSE;
4497         } else {
4498                 size_t linelen = strlen(line);
4499                 struct blame *blame = malloc(sizeof(*blame) + linelen);
4501                 if (!blame)
4502                         return FALSE;
4504                 blame->commit = NULL;
4505                 strncpy(blame->text, line, linelen);
4506                 blame->text[linelen] = 0;
4507                 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
4508         }
4511 static bool
4512 match_blame_header(const char *name, char **line)
4514         size_t namelen = strlen(name);
4515         bool matched = !strncmp(name, *line, namelen);
4517         if (matched)
4518                 *line += namelen;
4520         return matched;
4523 static bool
4524 blame_read(struct view *view, char *line)
4526         static struct blame_commit *commit = NULL;
4527         static int blamed = 0;
4528         static bool read_file = TRUE;
4530         if (read_file)
4531                 return blame_read_file(view, line, &read_file);
4533         if (!line) {
4534                 /* Reset all! */
4535                 commit = NULL;
4536                 blamed = 0;
4537                 read_file = TRUE;
4538                 string_format(view->ref, "%s", view->vid);
4539                 if (view_is_displayed(view)) {
4540                         update_view_title(view);
4541                         redraw_view_from(view, 0);
4542                 }
4543                 return TRUE;
4544         }
4546         if (!commit) {
4547                 commit = parse_blame_commit(view, line, &blamed);
4548                 string_format(view->ref, "%s %2d%%", view->vid,
4549                               view->lines ? blamed * 100 / view->lines : 0);
4551         } else if (match_blame_header("author ", &line)) {
4552                 commit->author = get_author(line);
4554         } else if (match_blame_header("author-time ", &line)) {
4555                 commit->time = (time_t) atol(line);
4557         } else if (match_blame_header("author-tz ", &line)) {
4558                 parse_timezone(&commit->time, line);
4560         } else if (match_blame_header("summary ", &line)) {
4561                 string_ncopy(commit->title, line, strlen(line));
4563         } else if (match_blame_header("previous ", &line)) {
4564                 commit->has_previous = TRUE;
4566         } else if (match_blame_header("filename ", &line)) {
4567                 string_ncopy(commit->filename, line, strlen(line));
4568                 commit = NULL;
4569         }
4571         return TRUE;
4574 static bool
4575 blame_draw(struct view *view, struct line *line, unsigned int lineno)
4577         struct blame *blame = line->data;
4578         time_t *time = NULL;
4579         const char *id = NULL, *author = NULL;
4580         char text[SIZEOF_STR];
4582         if (blame->commit && *blame->commit->filename) {
4583                 id = blame->commit->id;
4584                 author = blame->commit->author;
4585                 time = &blame->commit->time;
4586         }
4588         if (opt_date && draw_date(view, time))
4589                 return TRUE;
4591         if (opt_author && draw_author(view, author))
4592                 return TRUE;
4594         if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
4595                 return TRUE;
4597         if (draw_lineno(view, lineno))
4598                 return TRUE;
4600         string_expand(text, sizeof(text), blame->text, opt_tab_size);
4601         draw_text(view, LINE_DEFAULT, text, TRUE);
4602         return TRUE;
4605 static bool
4606 check_blame_commit(struct blame *blame, bool check_null_id)
4608         if (!blame->commit)
4609                 report("Commit data not loaded yet");
4610         else if (check_null_id && !strcmp(blame->commit->id, NULL_ID))
4611                 report("No commit exist for the selected line");
4612         else
4613                 return TRUE;
4614         return FALSE;
4617 static void
4618 setup_blame_parent_line(struct view *view, struct blame *blame)
4620         const char *diff_tree_argv[] = {
4621                 "git", "diff-tree", "-U0", blame->commit->id,
4622                         "--", blame->commit->filename, NULL
4623         };
4624         struct io io = {};
4625         int parent_lineno = -1;
4626         int blamed_lineno = -1;
4627         char *line;
4629         if (!run_io(&io, diff_tree_argv, NULL, IO_RD))
4630                 return;
4632         while ((line = io_get(&io, '\n', TRUE))) {
4633                 if (*line == '@') {
4634                         char *pos = strchr(line, '+');
4636                         parent_lineno = atoi(line + 4);
4637                         if (pos)
4638                                 blamed_lineno = atoi(pos + 1);
4640                 } else if (*line == '+' && parent_lineno != -1) {
4641                         if (blame->lineno == blamed_lineno - 1 &&
4642                             !strcmp(blame->text, line + 1)) {
4643                                 view->lineno = parent_lineno ? parent_lineno - 1 : 0;
4644                                 break;
4645                         }
4646                         blamed_lineno++;
4647                 }
4648         }
4650         done_io(&io);
4653 static enum request
4654 blame_request(struct view *view, enum request request, struct line *line)
4656         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4657         struct blame *blame = line->data;
4659         switch (request) {
4660         case REQ_VIEW_BLAME:
4661                 if (check_blame_commit(blame, TRUE)) {
4662                         string_copy(opt_ref, blame->commit->id);
4663                         string_copy(opt_file, blame->commit->filename);
4664                         if (blame->lineno)
4665                                 view->lineno = blame->lineno;
4666                         open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4667                 }
4668                 break;
4670         case REQ_PARENT:
4671                 if (check_blame_commit(blame, TRUE) &&
4672                     select_commit_parent(blame->commit->id, opt_ref,
4673                                          blame->commit->filename)) {
4674                         string_copy(opt_file, blame->commit->filename);
4675                         setup_blame_parent_line(view, blame);
4676                         open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4677                 }
4678                 break;
4680         case REQ_ENTER:
4681                 if (!check_blame_commit(blame, FALSE))
4682                         break;
4684                 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
4685                     !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
4686                         break;
4688                 if (!strcmp(blame->commit->id, NULL_ID)) {
4689                         struct view *diff = VIEW(REQ_VIEW_DIFF);
4690                         const char *diff_index_argv[] = {
4691                                 "git", "diff-index", "--root", "--patch-with-stat",
4692                                         "-C", "-M", "HEAD", "--", view->vid, NULL
4693                         };
4695                         if (!blame->commit->has_previous) {
4696                                 diff_index_argv[1] = "diff";
4697                                 diff_index_argv[2] = "--no-color";
4698                                 diff_index_argv[6] = "--";
4699                                 diff_index_argv[7] = "/dev/null";
4700                         }
4702                         if (!prepare_update(diff, diff_index_argv, NULL, FORMAT_DASH)) {
4703                                 report("Failed to allocate diff command");
4704                                 break;
4705                         }
4706                         flags |= OPEN_PREPARED;
4707                 }
4709                 open_view(view, REQ_VIEW_DIFF, flags);
4710                 if (VIEW(REQ_VIEW_DIFF)->pipe && !strcmp(blame->commit->id, NULL_ID))
4711                         string_copy_rev(VIEW(REQ_VIEW_DIFF)->ref, NULL_ID);
4712                 break;
4714         default:
4715                 return request;
4716         }
4718         return REQ_NONE;
4721 static bool
4722 blame_grep(struct view *view, struct line *line)
4724         struct blame *blame = line->data;
4725         struct blame_commit *commit = blame->commit;
4726         const char *text[] = {
4727                 blame->text,
4728                 commit ? commit->title : "",
4729                 commit ? commit->id : "",
4730                 commit && opt_author ? commit->author : "",
4731                 commit && opt_date ? mkdate(&commit->time) : "",
4732                 NULL
4733         };
4735         return grep_text(view, text);
4738 static void
4739 blame_select(struct view *view, struct line *line)
4741         struct blame *blame = line->data;
4742         struct blame_commit *commit = blame->commit;
4744         if (!commit)
4745                 return;
4747         if (!strcmp(commit->id, NULL_ID))
4748                 string_ncopy(ref_commit, "HEAD", 4);
4749         else
4750                 string_copy_rev(ref_commit, commit->id);
4753 static struct view_ops blame_ops = {
4754         "line",
4755         NULL,
4756         blame_open,
4757         blame_read,
4758         blame_draw,
4759         blame_request,
4760         blame_grep,
4761         blame_select,
4762 };
4764 /*
4765  * Branch backend
4766  */
4768 struct branch {
4769         const char *author;             /* Author of the last commit. */
4770         time_t time;                    /* Date of the last activity. */
4771         struct ref *ref;                /* Name and commit ID information. */
4772 };
4774 static const enum sort_field branch_sort_fields[] = {
4775         ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
4776 };
4777 static struct sort_state branch_sort_state = SORT_STATE(branch_sort_fields);
4779 static int
4780 branch_compare(const void *l1, const void *l2)
4782         const struct branch *branch1 = ((const struct line *) l1)->data;
4783         const struct branch *branch2 = ((const struct line *) l2)->data;
4785         switch (get_sort_field(branch_sort_state)) {
4786         case ORDERBY_DATE:
4787                 return sort_order(branch_sort_state, branch1->time - branch2->time);
4789         case ORDERBY_AUTHOR:
4790                 return sort_order(branch_sort_state, strcmp(branch1->author, branch2->author));
4792         case ORDERBY_NAME:
4793         default:
4794                 return sort_order(branch_sort_state, strcmp(branch1->ref->name, branch2->ref->name));
4795         }
4798 static bool
4799 branch_draw(struct view *view, struct line *line, unsigned int lineno)
4801         struct branch *branch = line->data;
4802         enum line_type type = branch->ref->head ? LINE_MAIN_HEAD : LINE_DEFAULT;
4804         if (opt_date && draw_date(view, branch->author ? &branch->time : NULL))
4805                 return TRUE;
4807         if (opt_author && draw_author(view, branch->author))
4808                 return TRUE;
4810         draw_text(view, type, branch->ref->name, TRUE);
4811         return TRUE;
4814 static enum request
4815 branch_request(struct view *view, enum request request, struct line *line)
4817         switch (request) {
4818         case REQ_REFRESH:
4819                 load_refs();
4820                 open_view(view, REQ_VIEW_BRANCH, OPEN_REFRESH);
4821                 return REQ_NONE;
4823         case REQ_TOGGLE_SORT_FIELD:
4824         case REQ_TOGGLE_SORT_ORDER:
4825                 sort_view(view, request, &branch_sort_state, branch_compare);
4826                 return REQ_NONE;
4828         case REQ_ENTER:
4829                 open_view(view, REQ_VIEW_MAIN, OPEN_SPLIT);
4830                 return REQ_NONE;
4832         default:
4833                 return request;
4834         }
4837 static bool
4838 branch_read(struct view *view, char *line)
4840         static char id[SIZEOF_REV];
4841         struct branch *reference;
4842         size_t i;
4844         if (!line)
4845                 return TRUE;
4847         switch (get_line_type(line)) {
4848         case LINE_COMMIT:
4849                 string_copy_rev(id, line + STRING_SIZE("commit "));
4850                 return TRUE;
4852         case LINE_AUTHOR:
4853                 for (i = 0, reference = NULL; i < view->lines; i++) {
4854                         struct branch *branch = view->line[i].data;
4856                         if (strcmp(branch->ref->id, id))
4857                                 continue;
4859                         view->line[i].dirty = TRUE;
4860                         if (reference) {
4861                                 branch->author = reference->author;
4862                                 branch->time = reference->time;
4863                                 continue;
4864                         }
4866                         parse_author_line(line + STRING_SIZE("author "),
4867                                           &branch->author, &branch->time);
4868                         reference = branch;
4869                 }
4870                 return TRUE;
4872         default:
4873                 return TRUE;
4874         }
4878 static bool
4879 branch_open_visitor(void *data, struct ref *ref)
4881         struct view *view = data;
4882         struct branch *branch;
4884         if (ref->tag || ref->ltag || ref->remote)
4885                 return TRUE;
4887         branch = calloc(1, sizeof(*branch));
4888         if (!branch)
4889                 return FALSE;
4891         branch->ref = ref;
4892         return !!add_line_data(view, branch, LINE_DEFAULT);
4895 static bool
4896 branch_open(struct view *view)
4898         const char *branch_log[] = {
4899                 "git", "log", "--no-color", "--pretty=raw",
4900                         "--simplify-by-decoration", "--all", NULL
4901         };
4903         if (!run_io_rd(&view->io, branch_log, FORMAT_NONE)) {
4904                 report("Failed to load branch data");
4905                 return TRUE;
4906         }
4908         setup_update(view, view->id);
4909         foreach_ref(branch_open_visitor, view);
4910         view->p_restore = TRUE;
4912         return TRUE;
4915 static bool
4916 branch_grep(struct view *view, struct line *line)
4918         struct branch *branch = line->data;
4919         const char *text[] = {
4920                 branch->ref->name,
4921                 branch->author,
4922                 NULL
4923         };
4925         return grep_text(view, text);
4928 static void
4929 branch_select(struct view *view, struct line *line)
4931         struct branch *branch = line->data;
4933         string_copy_rev(view->ref, branch->ref->id);
4934         string_copy_rev(ref_commit, branch->ref->id);
4935         string_copy_rev(ref_head, branch->ref->id);
4938 static struct view_ops branch_ops = {
4939         "branch",
4940         NULL,
4941         branch_open,
4942         branch_read,
4943         branch_draw,
4944         branch_request,
4945         branch_grep,
4946         branch_select,
4947 };
4949 /*
4950  * Status backend
4951  */
4953 struct status {
4954         char status;
4955         struct {
4956                 mode_t mode;
4957                 char rev[SIZEOF_REV];
4958                 char name[SIZEOF_STR];
4959         } old;
4960         struct {
4961                 mode_t mode;
4962                 char rev[SIZEOF_REV];
4963                 char name[SIZEOF_STR];
4964         } new;
4965 };
4967 static char status_onbranch[SIZEOF_STR];
4968 static struct status stage_status;
4969 static enum line_type stage_line_type;
4970 static size_t stage_chunks;
4971 static int *stage_chunk;
4973 DEFINE_ALLOCATOR(realloc_ints, int, 32)
4975 /* This should work even for the "On branch" line. */
4976 static inline bool
4977 status_has_none(struct view *view, struct line *line)
4979         return line < view->line + view->lines && !line[1].data;
4982 /* Get fields from the diff line:
4983  * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
4984  */
4985 static inline bool
4986 status_get_diff(struct status *file, const char *buf, size_t bufsize)
4988         const char *old_mode = buf +  1;
4989         const char *new_mode = buf +  8;
4990         const char *old_rev  = buf + 15;
4991         const char *new_rev  = buf + 56;
4992         const char *status   = buf + 97;
4994         if (bufsize < 98 ||
4995             old_mode[-1] != ':' ||
4996             new_mode[-1] != ' ' ||
4997             old_rev[-1]  != ' ' ||
4998             new_rev[-1]  != ' ' ||
4999             status[-1]   != ' ')
5000                 return FALSE;
5002         file->status = *status;
5004         string_copy_rev(file->old.rev, old_rev);
5005         string_copy_rev(file->new.rev, new_rev);
5007         file->old.mode = strtoul(old_mode, NULL, 8);
5008         file->new.mode = strtoul(new_mode, NULL, 8);
5010         file->old.name[0] = file->new.name[0] = 0;
5012         return TRUE;
5015 static bool
5016 status_run(struct view *view, const char *argv[], char status, enum line_type type)
5018         struct status *unmerged = NULL;
5019         char *buf;
5020         struct io io = {};
5022         if (!run_io(&io, argv, NULL, IO_RD))
5023                 return FALSE;
5025         add_line_data(view, NULL, type);
5027         while ((buf = io_get(&io, 0, TRUE))) {
5028                 struct status *file = unmerged;
5030                 if (!file) {
5031                         file = calloc(1, sizeof(*file));
5032                         if (!file || !add_line_data(view, file, type))
5033                                 goto error_out;
5034                 }
5036                 /* Parse diff info part. */
5037                 if (status) {
5038                         file->status = status;
5039                         if (status == 'A')
5040                                 string_copy(file->old.rev, NULL_ID);
5042                 } else if (!file->status || file == unmerged) {
5043                         if (!status_get_diff(file, buf, strlen(buf)))
5044                                 goto error_out;
5046                         buf = io_get(&io, 0, TRUE);
5047                         if (!buf)
5048                                 break;
5050                         /* Collapse all modified entries that follow an
5051                          * associated unmerged entry. */
5052                         if (unmerged == file) {
5053                                 unmerged->status = 'U';
5054                                 unmerged = NULL;
5055                         } else if (file->status == 'U') {
5056                                 unmerged = file;
5057                         }
5058                 }
5060                 /* Grab the old name for rename/copy. */
5061                 if (!*file->old.name &&
5062                     (file->status == 'R' || file->status == 'C')) {
5063                         string_ncopy(file->old.name, buf, strlen(buf));
5065                         buf = io_get(&io, 0, TRUE);
5066                         if (!buf)
5067                                 break;
5068                 }
5070                 /* git-ls-files just delivers a NUL separated list of
5071                  * file names similar to the second half of the
5072                  * git-diff-* output. */
5073                 string_ncopy(file->new.name, buf, strlen(buf));
5074                 if (!*file->old.name)
5075                         string_copy(file->old.name, file->new.name);
5076                 file = NULL;
5077         }
5079         if (io_error(&io)) {
5080 error_out:
5081                 done_io(&io);
5082                 return FALSE;
5083         }
5085         if (!view->line[view->lines - 1].data)
5086                 add_line_data(view, NULL, LINE_STAT_NONE);
5088         done_io(&io);
5089         return TRUE;
5092 /* Don't show unmerged entries in the staged section. */
5093 static const char *status_diff_index_argv[] = {
5094         "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
5095                              "--cached", "-M", "HEAD", NULL
5096 };
5098 static const char *status_diff_files_argv[] = {
5099         "git", "diff-files", "-z", NULL
5100 };
5102 static const char *status_list_other_argv[] = {
5103         "git", "ls-files", "-z", "--others", "--exclude-standard", NULL
5104 };
5106 static const char *status_list_no_head_argv[] = {
5107         "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
5108 };
5110 static const char *update_index_argv[] = {
5111         "git", "update-index", "-q", "--unmerged", "--refresh", NULL
5112 };
5114 /* Restore the previous line number to stay in the context or select a
5115  * line with something that can be updated. */
5116 static void
5117 status_restore(struct view *view)
5119         if (view->p_lineno >= view->lines)
5120                 view->p_lineno = view->lines - 1;
5121         while (view->p_lineno < view->lines && !view->line[view->p_lineno].data)
5122                 view->p_lineno++;
5123         while (view->p_lineno > 0 && !view->line[view->p_lineno].data)
5124                 view->p_lineno--;
5126         /* If the above fails, always skip the "On branch" line. */
5127         if (view->p_lineno < view->lines)
5128                 view->lineno = view->p_lineno;
5129         else
5130                 view->lineno = 1;
5132         if (view->lineno < view->offset)
5133                 view->offset = view->lineno;
5134         else if (view->offset + view->height <= view->lineno)
5135                 view->offset = view->lineno - view->height + 1;
5137         view->p_restore = FALSE;
5140 static void
5141 status_update_onbranch(void)
5143         static const char *paths[][2] = {
5144                 { "rebase-apply/rebasing",      "Rebasing" },
5145                 { "rebase-apply/applying",      "Applying mailbox" },
5146                 { "rebase-apply/",              "Rebasing mailbox" },
5147                 { "rebase-merge/interactive",   "Interactive rebase" },
5148                 { "rebase-merge/",              "Rebase merge" },
5149                 { "MERGE_HEAD",                 "Merging" },
5150                 { "BISECT_LOG",                 "Bisecting" },
5151                 { "HEAD",                       "On branch" },
5152         };
5153         char buf[SIZEOF_STR];
5154         struct stat stat;
5155         int i;
5157         if (is_initial_commit()) {
5158                 string_copy(status_onbranch, "Initial commit");
5159                 return;
5160         }
5162         for (i = 0; i < ARRAY_SIZE(paths); i++) {
5163                 char *head = opt_head;
5165                 if (!string_format(buf, "%s/%s", opt_git_dir, paths[i][0]) ||
5166                     lstat(buf, &stat) < 0)
5167                         continue;
5169                 if (!*opt_head) {
5170                         struct io io = {};
5172                         if (string_format(buf, "%s/rebase-merge/head-name", opt_git_dir) &&
5173                             io_open(&io, buf) &&
5174                             io_read_buf(&io, buf, sizeof(buf))) {
5175                                 head = buf;
5176                                 if (!prefixcmp(head, "refs/heads/"))
5177                                         head += STRING_SIZE("refs/heads/");
5178                         }
5179                 }
5181                 if (!string_format(status_onbranch, "%s %s", paths[i][1], head))
5182                         string_copy(status_onbranch, opt_head);
5183                 return;
5184         }
5186         string_copy(status_onbranch, "Not currently on any branch");
5189 /* First parse staged info using git-diff-index(1), then parse unstaged
5190  * info using git-diff-files(1), and finally untracked files using
5191  * git-ls-files(1). */
5192 static bool
5193 status_open(struct view *view)
5195         reset_view(view);
5197         add_line_data(view, NULL, LINE_STAT_HEAD);
5198         status_update_onbranch();
5200         run_io_bg(update_index_argv);
5202         if (is_initial_commit()) {
5203                 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
5204                         return FALSE;
5205         } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
5206                 return FALSE;
5207         }
5209         if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
5210             !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
5211                 return FALSE;
5213         /* Restore the exact position or use the specialized restore
5214          * mode? */
5215         if (!view->p_restore)
5216                 status_restore(view);
5217         return TRUE;
5220 static bool
5221 status_draw(struct view *view, struct line *line, unsigned int lineno)
5223         struct status *status = line->data;
5224         enum line_type type;
5225         const char *text;
5227         if (!status) {
5228                 switch (line->type) {
5229                 case LINE_STAT_STAGED:
5230                         type = LINE_STAT_SECTION;
5231                         text = "Changes to be committed:";
5232                         break;
5234                 case LINE_STAT_UNSTAGED:
5235                         type = LINE_STAT_SECTION;
5236                         text = "Changed but not updated:";
5237                         break;
5239                 case LINE_STAT_UNTRACKED:
5240                         type = LINE_STAT_SECTION;
5241                         text = "Untracked files:";
5242                         break;
5244                 case LINE_STAT_NONE:
5245                         type = LINE_DEFAULT;
5246                         text = "  (no files)";
5247                         break;
5249                 case LINE_STAT_HEAD:
5250                         type = LINE_STAT_HEAD;
5251                         text = status_onbranch;
5252                         break;
5254                 default:
5255                         return FALSE;
5256                 }
5257         } else {
5258                 static char buf[] = { '?', ' ', ' ', ' ', 0 };
5260                 buf[0] = status->status;
5261                 if (draw_text(view, line->type, buf, TRUE))
5262                         return TRUE;
5263                 type = LINE_DEFAULT;
5264                 text = status->new.name;
5265         }
5267         draw_text(view, type, text, TRUE);
5268         return TRUE;
5271 static enum request
5272 status_load_error(struct view *view, struct view *stage, const char *path)
5274         if (displayed_views() == 2 || display[current_view] != view)
5275                 maximize_view(view);
5276         report("Failed to load '%s': %s", path, io_strerror(&stage->io));
5277         return REQ_NONE;
5280 static enum request
5281 status_enter(struct view *view, struct line *line)
5283         struct status *status = line->data;
5284         const char *oldpath = status ? status->old.name : NULL;
5285         /* Diffs for unmerged entries are empty when passing the new
5286          * path, so leave it empty. */
5287         const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
5288         const char *info;
5289         enum open_flags split;
5290         struct view *stage = VIEW(REQ_VIEW_STAGE);
5292         if (line->type == LINE_STAT_NONE ||
5293             (!status && line[1].type == LINE_STAT_NONE)) {
5294                 report("No file to diff");
5295                 return REQ_NONE;
5296         }
5298         switch (line->type) {
5299         case LINE_STAT_STAGED:
5300                 if (is_initial_commit()) {
5301                         const char *no_head_diff_argv[] = {
5302                                 "git", "diff", "--no-color", "--patch-with-stat",
5303                                         "--", "/dev/null", newpath, NULL
5304                         };
5306                         if (!prepare_update(stage, no_head_diff_argv, opt_cdup, FORMAT_DASH))
5307                                 return status_load_error(view, stage, newpath);
5308                 } else {
5309                         const char *index_show_argv[] = {
5310                                 "git", "diff-index", "--root", "--patch-with-stat",
5311                                         "-C", "-M", "--cached", "HEAD", "--",
5312                                         oldpath, newpath, NULL
5313                         };
5315                         if (!prepare_update(stage, index_show_argv, opt_cdup, FORMAT_DASH))
5316                                 return status_load_error(view, stage, newpath);
5317                 }
5319                 if (status)
5320                         info = "Staged changes to %s";
5321                 else
5322                         info = "Staged changes";
5323                 break;
5325         case LINE_STAT_UNSTAGED:
5326         {
5327                 const char *files_show_argv[] = {
5328                         "git", "diff-files", "--root", "--patch-with-stat",
5329                                 "-C", "-M", "--", oldpath, newpath, NULL
5330                 };
5332                 if (!prepare_update(stage, files_show_argv, opt_cdup, FORMAT_DASH))
5333                         return status_load_error(view, stage, newpath);
5334                 if (status)
5335                         info = "Unstaged changes to %s";
5336                 else
5337                         info = "Unstaged changes";
5338                 break;
5339         }
5340         case LINE_STAT_UNTRACKED:
5341                 if (!newpath) {
5342                         report("No file to show");
5343                         return REQ_NONE;
5344                 }
5346                 if (!suffixcmp(status->new.name, -1, "/")) {
5347                         report("Cannot display a directory");
5348                         return REQ_NONE;
5349                 }
5351                 if (!prepare_update_file(stage, newpath))
5352                         return status_load_error(view, stage, newpath);
5353                 info = "Untracked file %s";
5354                 break;
5356         case LINE_STAT_HEAD:
5357                 return REQ_NONE;
5359         default:
5360                 die("line type %d not handled in switch", line->type);
5361         }
5363         split = view_is_displayed(view) ? OPEN_SPLIT : 0;
5364         open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split);
5365         if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
5366                 if (status) {
5367                         stage_status = *status;
5368                 } else {
5369                         memset(&stage_status, 0, sizeof(stage_status));
5370                 }
5372                 stage_line_type = line->type;
5373                 stage_chunks = 0;
5374                 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
5375         }
5377         return REQ_NONE;
5380 static bool
5381 status_exists(struct status *status, enum line_type type)
5383         struct view *view = VIEW(REQ_VIEW_STATUS);
5384         unsigned long lineno;
5386         for (lineno = 0; lineno < view->lines; lineno++) {
5387                 struct line *line = &view->line[lineno];
5388                 struct status *pos = line->data;
5390                 if (line->type != type)
5391                         continue;
5392                 if (!pos && (!status || !status->status) && line[1].data) {
5393                         select_view_line(view, lineno);
5394                         return TRUE;
5395                 }
5396                 if (pos && !strcmp(status->new.name, pos->new.name)) {
5397                         select_view_line(view, lineno);
5398                         return TRUE;
5399                 }
5400         }
5402         return FALSE;
5406 static bool
5407 status_update_prepare(struct io *io, enum line_type type)
5409         const char *staged_argv[] = {
5410                 "git", "update-index", "-z", "--index-info", NULL
5411         };
5412         const char *others_argv[] = {
5413                 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
5414         };
5416         switch (type) {
5417         case LINE_STAT_STAGED:
5418                 return run_io(io, staged_argv, opt_cdup, IO_WR);
5420         case LINE_STAT_UNSTAGED:
5421                 return run_io(io, others_argv, opt_cdup, IO_WR);
5423         case LINE_STAT_UNTRACKED:
5424                 return run_io(io, others_argv, NULL, IO_WR);
5426         default:
5427                 die("line type %d not handled in switch", type);
5428                 return FALSE;
5429         }
5432 static bool
5433 status_update_write(struct io *io, struct status *status, enum line_type type)
5435         char buf[SIZEOF_STR];
5436         size_t bufsize = 0;
5438         switch (type) {
5439         case LINE_STAT_STAGED:
5440                 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
5441                                         status->old.mode,
5442                                         status->old.rev,
5443                                         status->old.name, 0))
5444                         return FALSE;
5445                 break;
5447         case LINE_STAT_UNSTAGED:
5448         case LINE_STAT_UNTRACKED:
5449                 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
5450                         return FALSE;
5451                 break;
5453         default:
5454                 die("line type %d not handled in switch", type);
5455         }
5457         return io_write(io, buf, bufsize);
5460 static bool
5461 status_update_file(struct status *status, enum line_type type)
5463         struct io io = {};
5464         bool result;
5466         if (!status_update_prepare(&io, type))
5467                 return FALSE;
5469         result = status_update_write(&io, status, type);
5470         return done_io(&io) && result;
5473 static bool
5474 status_update_files(struct view *view, struct line *line)
5476         char buf[sizeof(view->ref)];
5477         struct io io = {};
5478         bool result = TRUE;
5479         struct line *pos = view->line + view->lines;
5480         int files = 0;
5481         int file, done;
5482         int cursor_y, cursor_x;
5484         if (!status_update_prepare(&io, line->type))
5485                 return FALSE;
5487         for (pos = line; pos < view->line + view->lines && pos->data; pos++)
5488                 files++;
5490         string_copy(buf, view->ref);
5491         getsyx(cursor_y, cursor_x);
5492         for (file = 0, done = 5; result && file < files; line++, file++) {
5493                 int almost_done = file * 100 / files;
5495                 if (almost_done > done) {
5496                         done = almost_done;
5497                         string_format(view->ref, "updating file %u of %u (%d%% done)",
5498                                       file, files, done);
5499                         update_view_title(view);
5500                         setsyx(cursor_y, cursor_x);
5501                         doupdate();
5502                 }
5503                 result = status_update_write(&io, line->data, line->type);
5504         }
5505         string_copy(view->ref, buf);
5507         return done_io(&io) && result;
5510 static bool
5511 status_update(struct view *view)
5513         struct line *line = &view->line[view->lineno];
5515         assert(view->lines);
5517         if (!line->data) {
5518                 /* This should work even for the "On branch" line. */
5519                 if (line < view->line + view->lines && !line[1].data) {
5520                         report("Nothing to update");
5521                         return FALSE;
5522                 }
5524                 if (!status_update_files(view, line + 1)) {
5525                         report("Failed to update file status");
5526                         return FALSE;
5527                 }
5529         } else if (!status_update_file(line->data, line->type)) {
5530                 report("Failed to update file status");
5531                 return FALSE;
5532         }
5534         return TRUE;
5537 static bool
5538 status_revert(struct status *status, enum line_type type, bool has_none)
5540         if (!status || type != LINE_STAT_UNSTAGED) {
5541                 if (type == LINE_STAT_STAGED) {
5542                         report("Cannot revert changes to staged files");
5543                 } else if (type == LINE_STAT_UNTRACKED) {
5544                         report("Cannot revert changes to untracked files");
5545                 } else if (has_none) {
5546                         report("Nothing to revert");
5547                 } else {
5548                         report("Cannot revert changes to multiple files");
5549                 }
5550                 return FALSE;
5552         } else {
5553                 char mode[10] = "100644";
5554                 const char *reset_argv[] = {
5555                         "git", "update-index", "--cacheinfo", mode,
5556                                 status->old.rev, status->old.name, NULL
5557                 };
5558                 const char *checkout_argv[] = {
5559                         "git", "checkout", "--", status->old.name, NULL
5560                 };
5562                 if (!prompt_yesno("Are you sure you want to overwrite any changes?"))
5563                         return FALSE;
5564                 string_format(mode, "%o", status->old.mode);
5565                 return (status->status != 'U' || run_io_fg(reset_argv, opt_cdup)) &&
5566                         run_io_fg(checkout_argv, opt_cdup);
5567         }
5570 static enum request
5571 status_request(struct view *view, enum request request, struct line *line)
5573         struct status *status = line->data;
5575         switch (request) {
5576         case REQ_STATUS_UPDATE:
5577                 if (!status_update(view))
5578                         return REQ_NONE;
5579                 break;
5581         case REQ_STATUS_REVERT:
5582                 if (!status_revert(status, line->type, status_has_none(view, line)))
5583                         return REQ_NONE;
5584                 break;
5586         case REQ_STATUS_MERGE:
5587                 if (!status || status->status != 'U') {
5588                         report("Merging only possible for files with unmerged status ('U').");
5589                         return REQ_NONE;
5590                 }
5591                 open_mergetool(status->new.name);
5592                 break;
5594         case REQ_EDIT:
5595                 if (!status)
5596                         return request;
5597                 if (status->status == 'D') {
5598                         report("File has been deleted.");
5599                         return REQ_NONE;
5600                 }
5602                 open_editor(status->status != '?', status->new.name);
5603                 break;
5605         case REQ_VIEW_BLAME:
5606                 if (status) {
5607                         string_copy(opt_file, status->new.name);
5608                         opt_ref[0] = 0;
5609                 }
5610                 return request;
5612         case REQ_ENTER:
5613                 /* After returning the status view has been split to
5614                  * show the stage view. No further reloading is
5615                  * necessary. */
5616                 return status_enter(view, line);
5618         case REQ_REFRESH:
5619                 /* Simply reload the view. */
5620                 break;
5622         default:
5623                 return request;
5624         }
5626         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
5628         return REQ_NONE;
5631 static void
5632 status_select(struct view *view, struct line *line)
5634         struct status *status = line->data;
5635         char file[SIZEOF_STR] = "all files";
5636         const char *text;
5637         const char *key;
5639         if (status && !string_format(file, "'%s'", status->new.name))
5640                 return;
5642         if (!status && line[1].type == LINE_STAT_NONE)
5643                 line++;
5645         switch (line->type) {
5646         case LINE_STAT_STAGED:
5647                 text = "Press %s to unstage %s for commit";
5648                 break;
5650         case LINE_STAT_UNSTAGED:
5651                 text = "Press %s to stage %s for commit";
5652                 break;
5654         case LINE_STAT_UNTRACKED:
5655                 text = "Press %s to stage %s for addition";
5656                 break;
5658         case LINE_STAT_HEAD:
5659         case LINE_STAT_NONE:
5660                 text = "Nothing to update";
5661                 break;
5663         default:
5664                 die("line type %d not handled in switch", line->type);
5665         }
5667         if (status && status->status == 'U') {
5668                 text = "Press %s to resolve conflict in %s";
5669                 key = get_key(REQ_STATUS_MERGE);
5671         } else {
5672                 key = get_key(REQ_STATUS_UPDATE);
5673         }
5675         string_format(view->ref, text, key, file);
5678 static bool
5679 status_grep(struct view *view, struct line *line)
5681         struct status *status = line->data;
5683         if (status) {
5684                 const char buf[2] = { status->status, 0 };
5685                 const char *text[] = { status->new.name, buf, NULL };
5687                 return grep_text(view, text);
5688         }
5690         return FALSE;
5693 static struct view_ops status_ops = {
5694         "file",
5695         NULL,
5696         status_open,
5697         NULL,
5698         status_draw,
5699         status_request,
5700         status_grep,
5701         status_select,
5702 };
5705 static bool
5706 stage_diff_write(struct io *io, struct line *line, struct line *end)
5708         while (line < end) {
5709                 if (!io_write(io, line->data, strlen(line->data)) ||
5710                     !io_write(io, "\n", 1))
5711                         return FALSE;
5712                 line++;
5713                 if (line->type == LINE_DIFF_CHUNK ||
5714                     line->type == LINE_DIFF_HEADER)
5715                         break;
5716         }
5718         return TRUE;
5721 static struct line *
5722 stage_diff_find(struct view *view, struct line *line, enum line_type type)
5724         for (; view->line < line; line--)
5725                 if (line->type == type)
5726                         return line;
5728         return NULL;
5731 static bool
5732 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
5734         const char *apply_argv[SIZEOF_ARG] = {
5735                 "git", "apply", "--whitespace=nowarn", NULL
5736         };
5737         struct line *diff_hdr;
5738         struct io io = {};
5739         int argc = 3;
5741         diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
5742         if (!diff_hdr)
5743                 return FALSE;
5745         if (!revert)
5746                 apply_argv[argc++] = "--cached";
5747         if (revert || stage_line_type == LINE_STAT_STAGED)
5748                 apply_argv[argc++] = "-R";
5749         apply_argv[argc++] = "-";
5750         apply_argv[argc++] = NULL;
5751         if (!run_io(&io, apply_argv, opt_cdup, IO_WR))
5752                 return FALSE;
5754         if (!stage_diff_write(&io, diff_hdr, chunk) ||
5755             !stage_diff_write(&io, chunk, view->line + view->lines))
5756                 chunk = NULL;
5758         done_io(&io);
5759         run_io_bg(update_index_argv);
5761         return chunk ? TRUE : FALSE;
5764 static bool
5765 stage_update(struct view *view, struct line *line)
5767         struct line *chunk = NULL;
5769         if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
5770                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
5772         if (chunk) {
5773                 if (!stage_apply_chunk(view, chunk, FALSE)) {
5774                         report("Failed to apply chunk");
5775                         return FALSE;
5776                 }
5778         } else if (!stage_status.status) {
5779                 view = VIEW(REQ_VIEW_STATUS);
5781                 for (line = view->line; line < view->line + view->lines; line++)
5782                         if (line->type == stage_line_type)
5783                                 break;
5785                 if (!status_update_files(view, line + 1)) {
5786                         report("Failed to update files");
5787                         return FALSE;
5788                 }
5790         } else if (!status_update_file(&stage_status, stage_line_type)) {
5791                 report("Failed to update file");
5792                 return FALSE;
5793         }
5795         return TRUE;
5798 static bool
5799 stage_revert(struct view *view, struct line *line)
5801         struct line *chunk = NULL;
5803         if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
5804                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
5806         if (chunk) {
5807                 if (!prompt_yesno("Are you sure you want to revert changes?"))
5808                         return FALSE;
5810                 if (!stage_apply_chunk(view, chunk, TRUE)) {
5811                         report("Failed to revert chunk");
5812                         return FALSE;
5813                 }
5814                 return TRUE;
5816         } else {
5817                 return status_revert(stage_status.status ? &stage_status : NULL,
5818                                      stage_line_type, FALSE);
5819         }
5823 static void
5824 stage_next(struct view *view, struct line *line)
5826         int i;
5828         if (!stage_chunks) {
5829                 for (line = view->line; line < view->line + view->lines; line++) {
5830                         if (line->type != LINE_DIFF_CHUNK)
5831                                 continue;
5833                         if (!realloc_ints(&stage_chunk, stage_chunks, 1)) {
5834                                 report("Allocation failure");
5835                                 return;
5836                         }
5838                         stage_chunk[stage_chunks++] = line - view->line;
5839                 }
5840         }
5842         for (i = 0; i < stage_chunks; i++) {
5843                 if (stage_chunk[i] > view->lineno) {
5844                         do_scroll_view(view, stage_chunk[i] - view->lineno);
5845                         report("Chunk %d of %d", i + 1, stage_chunks);
5846                         return;
5847                 }
5848         }
5850         report("No next chunk found");
5853 static enum request
5854 stage_request(struct view *view, enum request request, struct line *line)
5856         switch (request) {
5857         case REQ_STATUS_UPDATE:
5858                 if (!stage_update(view, line))
5859                         return REQ_NONE;
5860                 break;
5862         case REQ_STATUS_REVERT:
5863                 if (!stage_revert(view, line))
5864                         return REQ_NONE;
5865                 break;
5867         case REQ_STAGE_NEXT:
5868                 if (stage_line_type == LINE_STAT_UNTRACKED) {
5869                         report("File is untracked; press %s to add",
5870                                get_key(REQ_STATUS_UPDATE));
5871                         return REQ_NONE;
5872                 }
5873                 stage_next(view, line);
5874                 return REQ_NONE;
5876         case REQ_EDIT:
5877                 if (!stage_status.new.name[0])
5878                         return request;
5879                 if (stage_status.status == 'D') {
5880                         report("File has been deleted.");
5881                         return REQ_NONE;
5882                 }
5884                 open_editor(stage_status.status != '?', stage_status.new.name);
5885                 break;
5887         case REQ_REFRESH:
5888                 /* Reload everything ... */
5889                 break;
5891         case REQ_VIEW_BLAME:
5892                 if (stage_status.new.name[0]) {
5893                         string_copy(opt_file, stage_status.new.name);
5894                         opt_ref[0] = 0;
5895                 }
5896                 return request;
5898         case REQ_ENTER:
5899                 return pager_request(view, request, line);
5901         default:
5902                 return request;
5903         }
5905         VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
5906         open_view(view, REQ_VIEW_STATUS, OPEN_REFRESH);
5908         /* Check whether the staged entry still exists, and close the
5909          * stage view if it doesn't. */
5910         if (!status_exists(&stage_status, stage_line_type)) {
5911                 status_restore(VIEW(REQ_VIEW_STATUS));
5912                 return REQ_VIEW_CLOSE;
5913         }
5915         if (stage_line_type == LINE_STAT_UNTRACKED) {
5916                 if (!suffixcmp(stage_status.new.name, -1, "/")) {
5917                         report("Cannot display a directory");
5918                         return REQ_NONE;
5919                 }
5921                 if (!prepare_update_file(view, stage_status.new.name)) {
5922                         report("Failed to open file: %s", strerror(errno));
5923                         return REQ_NONE;
5924                 }
5925         }
5926         open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
5928         return REQ_NONE;
5931 static struct view_ops stage_ops = {
5932         "line",
5933         NULL,
5934         NULL,
5935         pager_read,
5936         pager_draw,
5937         stage_request,
5938         pager_grep,
5939         pager_select,
5940 };
5943 /*
5944  * Revision graph
5945  */
5947 struct commit {
5948         char id[SIZEOF_REV];            /* SHA1 ID. */
5949         char title[128];                /* First line of the commit message. */
5950         const char *author;             /* Author of the commit. */
5951         time_t time;                    /* Date from the author ident. */
5952         struct ref_list *refs;          /* Repository references. */
5953         chtype graph[SIZEOF_REVGRAPH];  /* Ancestry chain graphics. */
5954         size_t graph_size;              /* The width of the graph array. */
5955         bool has_parents;               /* Rewritten --parents seen. */
5956 };
5958 /* Size of rev graph with no  "padding" columns */
5959 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
5961 struct rev_graph {
5962         struct rev_graph *prev, *next, *parents;
5963         char rev[SIZEOF_REVITEMS][SIZEOF_REV];
5964         size_t size;
5965         struct commit *commit;
5966         size_t pos;
5967         unsigned int boundary:1;
5968 };
5970 /* Parents of the commit being visualized. */
5971 static struct rev_graph graph_parents[4];
5973 /* The current stack of revisions on the graph. */
5974 static struct rev_graph graph_stacks[4] = {
5975         { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
5976         { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
5977         { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
5978         { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
5979 };
5981 static inline bool
5982 graph_parent_is_merge(struct rev_graph *graph)
5984         return graph->parents->size > 1;
5987 static inline void
5988 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
5990         struct commit *commit = graph->commit;
5992         if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
5993                 commit->graph[commit->graph_size++] = symbol;
5996 static void
5997 clear_rev_graph(struct rev_graph *graph)
5999         graph->boundary = 0;
6000         graph->size = graph->pos = 0;
6001         graph->commit = NULL;
6002         memset(graph->parents, 0, sizeof(*graph->parents));
6005 static void
6006 done_rev_graph(struct rev_graph *graph)
6008         if (graph_parent_is_merge(graph) &&
6009             graph->pos < graph->size - 1 &&
6010             graph->next->size == graph->size + graph->parents->size - 1) {
6011                 size_t i = graph->pos + graph->parents->size - 1;
6013                 graph->commit->graph_size = i * 2;
6014                 while (i < graph->next->size - 1) {
6015                         append_to_rev_graph(graph, ' ');
6016                         append_to_rev_graph(graph, '\\');
6017                         i++;
6018                 }
6019         }
6021         clear_rev_graph(graph);
6024 static void
6025 push_rev_graph(struct rev_graph *graph, const char *parent)
6027         int i;
6029         /* "Collapse" duplicate parents lines.
6030          *
6031          * FIXME: This needs to also update update the drawn graph but
6032          * for now it just serves as a method for pruning graph lines. */
6033         for (i = 0; i < graph->size; i++)
6034                 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
6035                         return;
6037         if (graph->size < SIZEOF_REVITEMS) {
6038                 string_copy_rev(graph->rev[graph->size++], parent);
6039         }
6042 static chtype
6043 get_rev_graph_symbol(struct rev_graph *graph)
6045         chtype symbol;
6047         if (graph->boundary)
6048                 symbol = REVGRAPH_BOUND;
6049         else if (graph->parents->size == 0)
6050                 symbol = REVGRAPH_INIT;
6051         else if (graph_parent_is_merge(graph))
6052                 symbol = REVGRAPH_MERGE;
6053         else if (graph->pos >= graph->size)
6054                 symbol = REVGRAPH_BRANCH;
6055         else
6056                 symbol = REVGRAPH_COMMIT;
6058         return symbol;
6061 static void
6062 draw_rev_graph(struct rev_graph *graph)
6064         struct rev_filler {
6065                 chtype separator, line;
6066         };
6067         enum { DEFAULT, RSHARP, RDIAG, LDIAG };
6068         static struct rev_filler fillers[] = {
6069                 { ' ',  '|' },
6070                 { '`',  '.' },
6071                 { '\'', ' ' },
6072                 { '/',  ' ' },
6073         };
6074         chtype symbol = get_rev_graph_symbol(graph);
6075         struct rev_filler *filler;
6076         size_t i;
6078         if (opt_line_graphics)
6079                 fillers[DEFAULT].line = line_graphics[LINE_GRAPHIC_VLINE];
6081         filler = &fillers[DEFAULT];
6083         for (i = 0; i < graph->pos; i++) {
6084                 append_to_rev_graph(graph, filler->line);
6085                 if (graph_parent_is_merge(graph->prev) &&
6086                     graph->prev->pos == i)
6087                         filler = &fillers[RSHARP];
6089                 append_to_rev_graph(graph, filler->separator);
6090         }
6092         /* Place the symbol for this revision. */
6093         append_to_rev_graph(graph, symbol);
6095         if (graph->prev->size > graph->size)
6096                 filler = &fillers[RDIAG];
6097         else
6098                 filler = &fillers[DEFAULT];
6100         i++;
6102         for (; i < graph->size; i++) {
6103                 append_to_rev_graph(graph, filler->separator);
6104                 append_to_rev_graph(graph, filler->line);
6105                 if (graph_parent_is_merge(graph->prev) &&
6106                     i < graph->prev->pos + graph->parents->size)
6107                         filler = &fillers[RSHARP];
6108                 if (graph->prev->size > graph->size)
6109                         filler = &fillers[LDIAG];
6110         }
6112         if (graph->prev->size > graph->size) {
6113                 append_to_rev_graph(graph, filler->separator);
6114                 if (filler->line != ' ')
6115                         append_to_rev_graph(graph, filler->line);
6116         }
6119 /* Prepare the next rev graph */
6120 static void
6121 prepare_rev_graph(struct rev_graph *graph)
6123         size_t i;
6125         /* First, traverse all lines of revisions up to the active one. */
6126         for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
6127                 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
6128                         break;
6130                 push_rev_graph(graph->next, graph->rev[graph->pos]);
6131         }
6133         /* Interleave the new revision parent(s). */
6134         for (i = 0; !graph->boundary && i < graph->parents->size; i++)
6135                 push_rev_graph(graph->next, graph->parents->rev[i]);
6137         /* Lastly, put any remaining revisions. */
6138         for (i = graph->pos + 1; i < graph->size; i++)
6139                 push_rev_graph(graph->next, graph->rev[i]);
6142 static void
6143 update_rev_graph(struct view *view, struct rev_graph *graph)
6145         /* If this is the finalizing update ... */
6146         if (graph->commit)
6147                 prepare_rev_graph(graph);
6149         /* Graph visualization needs a one rev look-ahead,
6150          * so the first update doesn't visualize anything. */
6151         if (!graph->prev->commit)
6152                 return;
6154         if (view->lines > 2)
6155                 view->line[view->lines - 3].dirty = 1;
6156         if (view->lines > 1)
6157                 view->line[view->lines - 2].dirty = 1;
6158         draw_rev_graph(graph->prev);
6159         done_rev_graph(graph->prev->prev);
6163 /*
6164  * Main view backend
6165  */
6167 static const char *main_argv[SIZEOF_ARG] = {
6168         "git", "log", "--no-color", "--pretty=raw", "--parents",
6169                       "--topo-order", "%(head)", NULL
6170 };
6172 static bool
6173 main_draw(struct view *view, struct line *line, unsigned int lineno)
6175         struct commit *commit = line->data;
6177         if (!commit->author)
6178                 return FALSE;
6180         if (opt_date && draw_date(view, &commit->time))
6181                 return TRUE;
6183         if (opt_author && draw_author(view, commit->author))
6184                 return TRUE;
6186         if (opt_rev_graph && commit->graph_size &&
6187             draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
6188                 return TRUE;
6190         if (opt_show_refs && commit->refs) {
6191                 size_t i;
6193                 for (i = 0; i < commit->refs->size; i++) {
6194                         struct ref *ref = commit->refs->refs[i];
6195                         enum line_type type;
6197                         if (ref->head)
6198                                 type = LINE_MAIN_HEAD;
6199                         else if (ref->ltag)
6200                                 type = LINE_MAIN_LOCAL_TAG;
6201                         else if (ref->tag)
6202                                 type = LINE_MAIN_TAG;
6203                         else if (ref->tracked)
6204                                 type = LINE_MAIN_TRACKED;
6205                         else if (ref->remote)
6206                                 type = LINE_MAIN_REMOTE;
6207                         else
6208                                 type = LINE_MAIN_REF;
6210                         if (draw_text(view, type, "[", TRUE) ||
6211                             draw_text(view, type, ref->name, TRUE) ||
6212                             draw_text(view, type, "]", TRUE))
6213                                 return TRUE;
6215                         if (draw_text(view, LINE_DEFAULT, " ", TRUE))
6216                                 return TRUE;
6217                 }
6218         }
6220         draw_text(view, LINE_DEFAULT, commit->title, TRUE);
6221         return TRUE;
6224 /* Reads git log --pretty=raw output and parses it into the commit struct. */
6225 static bool
6226 main_read(struct view *view, char *line)
6228         static struct rev_graph *graph = graph_stacks;
6229         enum line_type type;
6230         struct commit *commit;
6232         if (!line) {
6233                 int i;
6235                 if (!view->lines && !view->parent)
6236                         die("No revisions match the given arguments.");
6237                 if (view->lines > 0) {
6238                         commit = view->line[view->lines - 1].data;
6239                         view->line[view->lines - 1].dirty = 1;
6240                         if (!commit->author) {
6241                                 view->lines--;
6242                                 free(commit);
6243                                 graph->commit = NULL;
6244                         }
6245                 }
6246                 update_rev_graph(view, graph);
6248                 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
6249                         clear_rev_graph(&graph_stacks[i]);
6250                 return TRUE;
6251         }
6253         type = get_line_type(line);
6254         if (type == LINE_COMMIT) {
6255                 commit = calloc(1, sizeof(struct commit));
6256                 if (!commit)
6257                         return FALSE;
6259                 line += STRING_SIZE("commit ");
6260                 if (*line == '-') {
6261                         graph->boundary = 1;
6262                         line++;
6263                 }
6265                 string_copy_rev(commit->id, line);
6266                 commit->refs = get_ref_list(commit->id);
6267                 graph->commit = commit;
6268                 add_line_data(view, commit, LINE_MAIN_COMMIT);
6270                 while ((line = strchr(line, ' '))) {
6271                         line++;
6272                         push_rev_graph(graph->parents, line);
6273                         commit->has_parents = TRUE;
6274                 }
6275                 return TRUE;
6276         }
6278         if (!view->lines)
6279                 return TRUE;
6280         commit = view->line[view->lines - 1].data;
6282         switch (type) {
6283         case LINE_PARENT:
6284                 if (commit->has_parents)
6285                         break;
6286                 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
6287                 break;
6289         case LINE_AUTHOR:
6290                 parse_author_line(line + STRING_SIZE("author "),
6291                                   &commit->author, &commit->time);
6292                 update_rev_graph(view, graph);
6293                 graph = graph->next;
6294                 break;
6296         default:
6297                 /* Fill in the commit title if it has not already been set. */
6298                 if (commit->title[0])
6299                         break;
6301                 /* Require titles to start with a non-space character at the
6302                  * offset used by git log. */
6303                 if (strncmp(line, "    ", 4))
6304                         break;
6305                 line += 4;
6306                 /* Well, if the title starts with a whitespace character,
6307                  * try to be forgiving.  Otherwise we end up with no title. */
6308                 while (isspace(*line))
6309                         line++;
6310                 if (*line == '\0')
6311                         break;
6312                 /* FIXME: More graceful handling of titles; append "..." to
6313                  * shortened titles, etc. */
6315                 string_expand(commit->title, sizeof(commit->title), line, 1);
6316                 view->line[view->lines - 1].dirty = 1;
6317         }
6319         return TRUE;
6322 static enum request
6323 main_request(struct view *view, enum request request, struct line *line)
6325         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
6327         switch (request) {
6328         case REQ_ENTER:
6329                 open_view(view, REQ_VIEW_DIFF, flags);
6330                 break;
6331         case REQ_REFRESH:
6332                 load_refs();
6333                 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
6334                 break;
6335         default:
6336                 return request;
6337         }
6339         return REQ_NONE;
6342 static bool
6343 grep_refs(struct ref_list *list, regex_t *regex)
6345         regmatch_t pmatch;
6346         size_t i;
6348         if (!opt_show_refs || !list)
6349                 return FALSE;
6351         for (i = 0; i < list->size; i++) {
6352                 if (regexec(regex, list->refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
6353                         return TRUE;
6354         }
6356         return FALSE;
6359 static bool
6360 main_grep(struct view *view, struct line *line)
6362         struct commit *commit = line->data;
6363         const char *text[] = {
6364                 commit->title,
6365                 opt_author ? commit->author : "",
6366                 opt_date ? mkdate(&commit->time) : "",
6367                 NULL
6368         };
6370         return grep_text(view, text) || grep_refs(commit->refs, view->regex);
6373 static void
6374 main_select(struct view *view, struct line *line)
6376         struct commit *commit = line->data;
6378         string_copy_rev(view->ref, commit->id);
6379         string_copy_rev(ref_commit, view->ref);
6382 static struct view_ops main_ops = {
6383         "commit",
6384         main_argv,
6385         NULL,
6386         main_read,
6387         main_draw,
6388         main_request,
6389         main_grep,
6390         main_select,
6391 };
6394 /*
6395  * Unicode / UTF-8 handling
6396  *
6397  * NOTE: Much of the following code for dealing with Unicode is derived from
6398  * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
6399  * src/intl/charset.c from the UTF-8 branch commit elinks-0.11.0-g31f2c28.
6400  */
6402 static inline int
6403 unicode_width(unsigned long c)
6405         if (c >= 0x1100 &&
6406            (c <= 0x115f                         /* Hangul Jamo */
6407             || c == 0x2329
6408             || c == 0x232a
6409             || (c >= 0x2e80  && c <= 0xa4cf && c != 0x303f)
6410                                                 /* CJK ... Yi */
6411             || (c >= 0xac00  && c <= 0xd7a3)    /* Hangul Syllables */
6412             || (c >= 0xf900  && c <= 0xfaff)    /* CJK Compatibility Ideographs */
6413             || (c >= 0xfe30  && c <= 0xfe6f)    /* CJK Compatibility Forms */
6414             || (c >= 0xff00  && c <= 0xff60)    /* Fullwidth Forms */
6415             || (c >= 0xffe0  && c <= 0xffe6)
6416             || (c >= 0x20000 && c <= 0x2fffd)
6417             || (c >= 0x30000 && c <= 0x3fffd)))
6418                 return 2;
6420         if (c == '\t')
6421                 return opt_tab_size;
6423         return 1;
6426 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
6427  * Illegal bytes are set one. */
6428 static const unsigned char utf8_bytes[256] = {
6429         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,
6430         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,
6431         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,
6432         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,
6433         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,
6434         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,
6435         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,
6436         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,
6437 };
6439 /* Decode UTF-8 multi-byte representation into a Unicode character. */
6440 static inline unsigned long
6441 utf8_to_unicode(const char *string, size_t length)
6443         unsigned long unicode;
6445         switch (length) {
6446         case 1:
6447                 unicode  =   string[0];
6448                 break;
6449         case 2:
6450                 unicode  =  (string[0] & 0x1f) << 6;
6451                 unicode +=  (string[1] & 0x3f);
6452                 break;
6453         case 3:
6454                 unicode  =  (string[0] & 0x0f) << 12;
6455                 unicode += ((string[1] & 0x3f) << 6);
6456                 unicode +=  (string[2] & 0x3f);
6457                 break;
6458         case 4:
6459                 unicode  =  (string[0] & 0x0f) << 18;
6460                 unicode += ((string[1] & 0x3f) << 12);
6461                 unicode += ((string[2] & 0x3f) << 6);
6462                 unicode +=  (string[3] & 0x3f);
6463                 break;
6464         case 5:
6465                 unicode  =  (string[0] & 0x0f) << 24;
6466                 unicode += ((string[1] & 0x3f) << 18);
6467                 unicode += ((string[2] & 0x3f) << 12);
6468                 unicode += ((string[3] & 0x3f) << 6);
6469                 unicode +=  (string[4] & 0x3f);
6470                 break;
6471         case 6:
6472                 unicode  =  (string[0] & 0x01) << 30;
6473                 unicode += ((string[1] & 0x3f) << 24);
6474                 unicode += ((string[2] & 0x3f) << 18);
6475                 unicode += ((string[3] & 0x3f) << 12);
6476                 unicode += ((string[4] & 0x3f) << 6);
6477                 unicode +=  (string[5] & 0x3f);
6478                 break;
6479         default:
6480                 die("Invalid Unicode length");
6481         }
6483         /* Invalid characters could return the special 0xfffd value but NUL
6484          * should be just as good. */
6485         return unicode > 0xffff ? 0 : unicode;
6488 /* Calculates how much of string can be shown within the given maximum width
6489  * and sets trimmed parameter to non-zero value if all of string could not be
6490  * shown. If the reserve flag is TRUE, it will reserve at least one
6491  * trailing character, which can be useful when drawing a delimiter.
6492  *
6493  * Returns the number of bytes to output from string to satisfy max_width. */
6494 static size_t
6495 utf8_length(const char **start, size_t skip, int *width, size_t max_width, int *trimmed, bool reserve)
6497         const char *string = *start;
6498         const char *end = strchr(string, '\0');
6499         unsigned char last_bytes = 0;
6500         size_t last_ucwidth = 0;
6502         *width = 0;
6503         *trimmed = 0;
6505         while (string < end) {
6506                 int c = *(unsigned char *) string;
6507                 unsigned char bytes = utf8_bytes[c];
6508                 size_t ucwidth;
6509                 unsigned long unicode;
6511                 if (string + bytes > end)
6512                         break;
6514                 /* Change representation to figure out whether
6515                  * it is a single- or double-width character. */
6517                 unicode = utf8_to_unicode(string, bytes);
6518                 /* FIXME: Graceful handling of invalid Unicode character. */
6519                 if (!unicode)
6520                         break;
6522                 ucwidth = unicode_width(unicode);
6523                 if (skip > 0) {
6524                         skip -= ucwidth <= skip ? ucwidth : skip;
6525                         *start += bytes;
6526                 }
6527                 *width  += ucwidth;
6528                 if (*width > max_width) {
6529                         *trimmed = 1;
6530                         *width -= ucwidth;
6531                         if (reserve && *width == max_width) {
6532                                 string -= last_bytes;
6533                                 *width -= last_ucwidth;
6534                         }
6535                         break;
6536                 }
6538                 string  += bytes;
6539                 last_bytes = ucwidth ? bytes : 0;
6540                 last_ucwidth = ucwidth;
6541         }
6543         return string - *start;
6547 /*
6548  * Status management
6549  */
6551 /* Whether or not the curses interface has been initialized. */
6552 static bool cursed = FALSE;
6554 /* Terminal hacks and workarounds. */
6555 static bool use_scroll_redrawwin;
6556 static bool use_scroll_status_wclear;
6558 /* The status window is used for polling keystrokes. */
6559 static WINDOW *status_win;
6561 /* Reading from the prompt? */
6562 static bool input_mode = FALSE;
6564 static bool status_empty = FALSE;
6566 /* Update status and title window. */
6567 static void
6568 report(const char *msg, ...)
6570         struct view *view = display[current_view];
6572         if (input_mode)
6573                 return;
6575         if (!view) {
6576                 char buf[SIZEOF_STR];
6577                 va_list args;
6579                 va_start(args, msg);
6580                 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
6581                         buf[sizeof(buf) - 1] = 0;
6582                         buf[sizeof(buf) - 2] = '.';
6583                         buf[sizeof(buf) - 3] = '.';
6584                         buf[sizeof(buf) - 4] = '.';
6585                 }
6586                 va_end(args);
6587                 die("%s", buf);
6588         }
6590         if (!status_empty || *msg) {
6591                 va_list args;
6593                 va_start(args, msg);
6595                 wmove(status_win, 0, 0);
6596                 if (view->has_scrolled && use_scroll_status_wclear)
6597                         wclear(status_win);
6598                 if (*msg) {
6599                         vwprintw(status_win, msg, args);
6600                         status_empty = FALSE;
6601                 } else {
6602                         status_empty = TRUE;
6603                 }
6604                 wclrtoeol(status_win);
6605                 wnoutrefresh(status_win);
6607                 va_end(args);
6608         }
6610         update_view_title(view);
6613 /* Controls when nodelay should be in effect when polling user input. */
6614 static void
6615 set_nonblocking_input(bool loading)
6617         static unsigned int loading_views;
6619         if ((loading == FALSE && loading_views-- == 1) ||
6620             (loading == TRUE  && loading_views++ == 0))
6621                 nodelay(status_win, loading);
6624 static void
6625 init_display(void)
6627         const char *term;
6628         int x, y;
6630         /* Initialize the curses library */
6631         if (isatty(STDIN_FILENO)) {
6632                 cursed = !!initscr();
6633                 opt_tty = stdin;
6634         } else {
6635                 /* Leave stdin and stdout alone when acting as a pager. */
6636                 opt_tty = fopen("/dev/tty", "r+");
6637                 if (!opt_tty)
6638                         die("Failed to open /dev/tty");
6639                 cursed = !!newterm(NULL, opt_tty, opt_tty);
6640         }
6642         if (!cursed)
6643                 die("Failed to initialize curses");
6645         nonl();         /* Disable conversion and detect newlines from input. */
6646         cbreak();       /* Take input chars one at a time, no wait for \n */
6647         noecho();       /* Don't echo input */
6648         leaveok(stdscr, FALSE);
6650         if (has_colors())
6651                 init_colors();
6653         getmaxyx(stdscr, y, x);
6654         status_win = newwin(1, 0, y - 1, 0);
6655         if (!status_win)
6656                 die("Failed to create status window");
6658         /* Enable keyboard mapping */
6659         keypad(status_win, TRUE);
6660         wbkgdset(status_win, get_line_attr(LINE_STATUS));
6662         TABSIZE = opt_tab_size;
6663         if (opt_line_graphics) {
6664                 line_graphics[LINE_GRAPHIC_VLINE] = ACS_VLINE;
6665         }
6667         term = getenv("XTERM_VERSION") ? NULL : getenv("COLORTERM");
6668         if (term && !strcmp(term, "gnome-terminal")) {
6669                 /* In the gnome-terminal-emulator, the message from
6670                  * scrolling up one line when impossible followed by
6671                  * scrolling down one line causes corruption of the
6672                  * status line. This is fixed by calling wclear. */
6673                 use_scroll_status_wclear = TRUE;
6674                 use_scroll_redrawwin = FALSE;
6676         } else if (term && !strcmp(term, "xrvt-xpm")) {
6677                 /* No problems with full optimizations in xrvt-(unicode)
6678                  * and aterm. */
6679                 use_scroll_status_wclear = use_scroll_redrawwin = FALSE;
6681         } else {
6682                 /* When scrolling in (u)xterm the last line in the
6683                  * scrolling direction will update slowly. */
6684                 use_scroll_redrawwin = TRUE;
6685                 use_scroll_status_wclear = FALSE;
6686         }
6689 static int
6690 get_input(int prompt_position)
6692         struct view *view;
6693         int i, key, cursor_y, cursor_x;
6695         if (prompt_position)
6696                 input_mode = TRUE;
6698         while (TRUE) {
6699                 foreach_view (view, i) {
6700                         update_view(view);
6701                         if (view_is_displayed(view) && view->has_scrolled &&
6702                             use_scroll_redrawwin)
6703                                 redrawwin(view->win);
6704                         view->has_scrolled = FALSE;
6705                 }
6707                 /* Update the cursor position. */
6708                 if (prompt_position) {
6709                         getbegyx(status_win, cursor_y, cursor_x);
6710                         cursor_x = prompt_position;
6711                 } else {
6712                         view = display[current_view];
6713                         getbegyx(view->win, cursor_y, cursor_x);
6714                         cursor_x = view->width - 1;
6715                         cursor_y += view->lineno - view->offset;
6716                 }
6717                 setsyx(cursor_y, cursor_x);
6719                 /* Refresh, accept single keystroke of input */
6720                 doupdate();
6721                 key = wgetch(status_win);
6723                 /* wgetch() with nodelay() enabled returns ERR when
6724                  * there's no input. */
6725                 if (key == ERR) {
6727                 } else if (key == KEY_RESIZE) {
6728                         int height, width;
6730                         getmaxyx(stdscr, height, width);
6732                         wresize(status_win, 1, width);
6733                         mvwin(status_win, height - 1, 0);
6734                         wnoutrefresh(status_win);
6735                         resize_display();
6736                         redraw_display(TRUE);
6738                 } else {
6739                         input_mode = FALSE;
6740                         return key;
6741                 }
6742         }
6745 static char *
6746 prompt_input(const char *prompt, input_handler handler, void *data)
6748         enum input_status status = INPUT_OK;
6749         static char buf[SIZEOF_STR];
6750         size_t pos = 0;
6752         buf[pos] = 0;
6754         while (status == INPUT_OK || status == INPUT_SKIP) {
6755                 int key;
6757                 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
6758                 wclrtoeol(status_win);
6760                 key = get_input(pos + 1);
6761                 switch (key) {
6762                 case KEY_RETURN:
6763                 case KEY_ENTER:
6764                 case '\n':
6765                         status = pos ? INPUT_STOP : INPUT_CANCEL;
6766                         break;
6768                 case KEY_BACKSPACE:
6769                         if (pos > 0)
6770                                 buf[--pos] = 0;
6771                         else
6772                                 status = INPUT_CANCEL;
6773                         break;
6775                 case KEY_ESC:
6776                         status = INPUT_CANCEL;
6777                         break;
6779                 default:
6780                         if (pos >= sizeof(buf)) {
6781                                 report("Input string too long");
6782                                 return NULL;
6783                         }
6785                         status = handler(data, buf, key);
6786                         if (status == INPUT_OK)
6787                                 buf[pos++] = (char) key;
6788                 }
6789         }
6791         /* Clear the status window */
6792         status_empty = FALSE;
6793         report("");
6795         if (status == INPUT_CANCEL)
6796                 return NULL;
6798         buf[pos++] = 0;
6800         return buf;
6803 static enum input_status
6804 prompt_yesno_handler(void *data, char *buf, int c)
6806         if (c == 'y' || c == 'Y')
6807                 return INPUT_STOP;
6808         if (c == 'n' || c == 'N')
6809                 return INPUT_CANCEL;
6810         return INPUT_SKIP;
6813 static bool
6814 prompt_yesno(const char *prompt)
6816         char prompt2[SIZEOF_STR];
6818         if (!string_format(prompt2, "%s [Yy/Nn]", prompt))
6819                 return FALSE;
6821         return !!prompt_input(prompt2, prompt_yesno_handler, NULL);
6824 static enum input_status
6825 read_prompt_handler(void *data, char *buf, int c)
6827         return isprint(c) ? INPUT_OK : INPUT_SKIP;
6830 static char *
6831 read_prompt(const char *prompt)
6833         return prompt_input(prompt, read_prompt_handler, NULL);
6836 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected)
6838         enum input_status status = INPUT_OK;
6839         int size = 0;
6841         while (items[size].text)
6842                 size++;
6844         while (status == INPUT_OK) {
6845                 const struct menu_item *item = &items[*selected];
6846                 int key;
6847                 int i;
6849                 mvwprintw(status_win, 0, 0, "%s (%d of %d) ",
6850                           prompt, *selected + 1, size);
6851                 if (item->hotkey)
6852                         wprintw(status_win, "[%c] ", (char) item->hotkey);
6853                 wprintw(status_win, "%s", item->text);
6854                 wclrtoeol(status_win);
6856                 key = get_input(COLS - 1);
6857                 switch (key) {
6858                 case KEY_RETURN:
6859                 case KEY_ENTER:
6860                 case '\n':
6861                         status = INPUT_STOP;
6862                         break;
6864                 case KEY_LEFT:
6865                 case KEY_UP:
6866                         *selected = *selected - 1;
6867                         if (*selected < 0)
6868                                 *selected = size - 1;
6869                         break;
6871                 case KEY_RIGHT:
6872                 case KEY_DOWN:
6873                         *selected = (*selected + 1) % size;
6874                         break;
6876                 case KEY_ESC:
6877                         status = INPUT_CANCEL;
6878                         break;
6880                 default:
6881                         for (i = 0; items[i].text; i++)
6882                                 if (items[i].hotkey == key) {
6883                                         *selected = i;
6884                                         status = INPUT_STOP;
6885                                         break;
6886                                 }
6887                 }
6888         }
6890         /* Clear the status window */
6891         status_empty = FALSE;
6892         report("");
6894         return status != INPUT_CANCEL;
6897 /*
6898  * Repository properties
6899  */
6901 static struct ref **refs = NULL;
6902 static size_t refs_size = 0;
6904 static struct ref_list **ref_lists = NULL;
6905 static size_t ref_lists_size = 0;
6907 DEFINE_ALLOCATOR(realloc_refs, struct ref *, 256)
6908 DEFINE_ALLOCATOR(realloc_refs_list, struct ref *, 8)
6909 DEFINE_ALLOCATOR(realloc_ref_lists, struct ref_list *, 8)
6911 static int
6912 compare_refs(const void *ref1_, const void *ref2_)
6914         const struct ref *ref1 = *(const struct ref **)ref1_;
6915         const struct ref *ref2 = *(const struct ref **)ref2_;
6917         if (ref1->tag != ref2->tag)
6918                 return ref2->tag - ref1->tag;
6919         if (ref1->ltag != ref2->ltag)
6920                 return ref2->ltag - ref2->ltag;
6921         if (ref1->head != ref2->head)
6922                 return ref2->head - ref1->head;
6923         if (ref1->tracked != ref2->tracked)
6924                 return ref2->tracked - ref1->tracked;
6925         if (ref1->remote != ref2->remote)
6926                 return ref2->remote - ref1->remote;
6927         return strcmp(ref1->name, ref2->name);
6930 static void
6931 foreach_ref(bool (*visitor)(void *data, struct ref *ref), void *data)
6933         size_t i;
6935         for (i = 0; i < refs_size; i++)
6936                 if (!visitor(data, refs[i]))
6937                         break;
6940 static struct ref_list *
6941 get_ref_list(const char *id)
6943         struct ref_list *list;
6944         size_t i;
6946         for (i = 0; i < ref_lists_size; i++)
6947                 if (!strcmp(id, ref_lists[i]->id))
6948                         return ref_lists[i];
6950         if (!realloc_ref_lists(&ref_lists, ref_lists_size, 1))
6951                 return NULL;
6952         list = calloc(1, sizeof(*list));
6953         if (!list)
6954                 return NULL;
6956         for (i = 0; i < refs_size; i++) {
6957                 if (!strcmp(id, refs[i]->id) &&
6958                     realloc_refs_list(&list->refs, list->size, 1))
6959                         list->refs[list->size++] = refs[i];
6960         }
6962         if (!list->refs) {
6963                 free(list);
6964                 return NULL;
6965         }
6967         qsort(list->refs, list->size, sizeof(*list->refs), compare_refs);
6968         ref_lists[ref_lists_size++] = list;
6969         return list;
6972 static int
6973 read_ref(char *id, size_t idlen, char *name, size_t namelen)
6975         struct ref *ref = NULL;
6976         bool tag = FALSE;
6977         bool ltag = FALSE;
6978         bool remote = FALSE;
6979         bool tracked = FALSE;
6980         bool head = FALSE;
6981         int from = 0, to = refs_size - 1;
6983         if (!prefixcmp(name, "refs/tags/")) {
6984                 if (!suffixcmp(name, namelen, "^{}")) {
6985                         namelen -= 3;
6986                         name[namelen] = 0;
6987                 } else {
6988                         ltag = TRUE;
6989                 }
6991                 tag = TRUE;
6992                 namelen -= STRING_SIZE("refs/tags/");
6993                 name    += STRING_SIZE("refs/tags/");
6995         } else if (!prefixcmp(name, "refs/remotes/")) {
6996                 remote = TRUE;
6997                 namelen -= STRING_SIZE("refs/remotes/");
6998                 name    += STRING_SIZE("refs/remotes/");
6999                 tracked  = !strcmp(opt_remote, name);
7001         } else if (!prefixcmp(name, "refs/heads/")) {
7002                 namelen -= STRING_SIZE("refs/heads/");
7003                 name    += STRING_SIZE("refs/heads/");
7004                 head     = !strncmp(opt_head, name, namelen);
7006         } else if (!strcmp(name, "HEAD")) {
7007                 string_ncopy(opt_head_rev, id, idlen);
7008                 return OK;
7009         }
7011         /* If we are reloading or it's an annotated tag, replace the
7012          * previous SHA1 with the resolved commit id; relies on the fact
7013          * git-ls-remote lists the commit id of an annotated tag right
7014          * before the commit id it points to. */
7015         while (from <= to) {
7016                 size_t pos = (to + from) / 2;
7017                 int cmp = strcmp(name, refs[pos]->name);
7019                 if (!cmp) {
7020                         ref = refs[pos];
7021                         break;
7022                 }
7024                 if (cmp < 0)
7025                         to = pos - 1;
7026                 else
7027                         from = pos + 1;
7028         }
7030         if (!ref) {
7031                 if (!realloc_refs(&refs, refs_size, 1))
7032                         return ERR;
7033                 ref = calloc(1, sizeof(*ref) + namelen);
7034                 if (!ref)
7035                         return ERR;
7036                 memmove(refs + from + 1, refs + from,
7037                         (refs_size - from) * sizeof(*refs));
7038                 refs[from] = ref;
7039                 strncpy(ref->name, name, namelen);
7040                 refs_size++;
7041         }
7043         ref->head = head;
7044         ref->tag = tag;
7045         ref->ltag = ltag;
7046         ref->remote = remote;
7047         ref->tracked = tracked;
7048         string_copy_rev(ref->id, id);
7050         return OK;
7053 static int
7054 load_refs(void)
7056         const char *head_argv[] = {
7057                 "git", "symbolic-ref", "HEAD", NULL
7058         };
7059         static const char *ls_remote_argv[SIZEOF_ARG] = {
7060                 "git", "ls-remote", opt_git_dir, NULL
7061         };
7062         static bool init = FALSE;
7063         size_t i;
7065         if (!init) {
7066                 argv_from_env(ls_remote_argv, "TIG_LS_REMOTE");
7067                 init = TRUE;
7068         }
7070         if (!*opt_git_dir)
7071                 return OK;
7073         if (run_io_buf(head_argv, opt_head, sizeof(opt_head)) &&
7074             !prefixcmp(opt_head, "refs/heads/")) {
7075                 char *offset = opt_head + STRING_SIZE("refs/heads/");
7077                 memmove(opt_head, offset, strlen(offset) + 1);
7078         }
7080         for (i = 0; i < refs_size; i++)
7081                 refs[i]->id[0] = 0;
7083         if (run_io_load(ls_remote_argv, "\t", read_ref) == ERR)
7084                 return ERR;
7086         /* Update the ref lists to reflect changes. */
7087         for (i = 0; i < ref_lists_size; i++) {
7088                 struct ref_list *list = ref_lists[i];
7089                 size_t old, new;
7091                 for (old = new = 0; old < list->size; old++)
7092                         if (!strcmp(list->id, list->refs[old]->id))
7093                                 list->refs[new++] = list->refs[old];
7094                 list->size = new;
7095         }
7097         return OK;
7100 static void
7101 set_remote_branch(const char *name, const char *value, size_t valuelen)
7103         if (!strcmp(name, ".remote")) {
7104                 string_ncopy(opt_remote, value, valuelen);
7106         } else if (*opt_remote && !strcmp(name, ".merge")) {
7107                 size_t from = strlen(opt_remote);
7109                 if (!prefixcmp(value, "refs/heads/"))
7110                         value += STRING_SIZE("refs/heads/");
7112                 if (!string_format_from(opt_remote, &from, "/%s", value))
7113                         opt_remote[0] = 0;
7114         }
7117 static void
7118 set_repo_config_option(char *name, char *value, int (*cmd)(int, const char **))
7120         const char *argv[SIZEOF_ARG] = { name, "=" };
7121         int argc = 1 + (cmd == option_set_command);
7122         int error = ERR;
7124         if (!argv_from_string(argv, &argc, value))
7125                 config_msg = "Too many option arguments";
7126         else
7127                 error = cmd(argc, argv);
7129         if (error == ERR)
7130                 warn("Option 'tig.%s': %s", name, config_msg);
7133 static bool
7134 set_environment_variable(const char *name, const char *value)
7136         size_t len = strlen(name) + 1 + strlen(value) + 1;
7137         char *env = malloc(len);
7139         if (env &&
7140             string_nformat(env, len, NULL, "%s=%s", name, value) &&
7141             putenv(env) == 0)
7142                 return TRUE;
7143         free(env);
7144         return FALSE;
7147 static void
7148 set_work_tree(const char *value)
7150         char cwd[SIZEOF_STR];
7152         if (!getcwd(cwd, sizeof(cwd)))
7153                 die("Failed to get cwd path: %s", strerror(errno));
7154         if (chdir(opt_git_dir) < 0)
7155                 die("Failed to chdir(%s): %s", strerror(errno));
7156         if (!getcwd(opt_git_dir, sizeof(opt_git_dir)))
7157                 die("Failed to get git path: %s", strerror(errno));
7158         if (chdir(cwd) < 0)
7159                 die("Failed to chdir(%s): %s", cwd, strerror(errno));
7160         if (chdir(value) < 0)
7161                 die("Failed to chdir(%s): %s", value, strerror(errno));
7162         if (!getcwd(cwd, sizeof(cwd)))
7163                 die("Failed to get cwd path: %s", strerror(errno));
7164         if (!set_environment_variable("GIT_WORK_TREE", cwd))
7165                 die("Failed to set GIT_WORK_TREE to '%s'", cwd);
7166         if (!set_environment_variable("GIT_DIR", opt_git_dir))
7167                 die("Failed to set GIT_DIR to '%s'", opt_git_dir);
7168         opt_is_inside_work_tree = TRUE;
7171 static int
7172 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
7174         if (!strcmp(name, "i18n.commitencoding"))
7175                 string_ncopy(opt_encoding, value, valuelen);
7177         else if (!strcmp(name, "core.editor"))
7178                 string_ncopy(opt_editor, value, valuelen);
7180         else if (!strcmp(name, "core.worktree"))
7181                 set_work_tree(value);
7183         else if (!prefixcmp(name, "tig.color."))
7184                 set_repo_config_option(name + 10, value, option_color_command);
7186         else if (!prefixcmp(name, "tig.bind."))
7187                 set_repo_config_option(name + 9, value, option_bind_command);
7189         else if (!prefixcmp(name, "tig."))
7190                 set_repo_config_option(name + 4, value, option_set_command);
7192         else if (*opt_head && !prefixcmp(name, "branch.") &&
7193                  !strncmp(name + 7, opt_head, strlen(opt_head)))
7194                 set_remote_branch(name + 7 + strlen(opt_head), value, valuelen);
7196         return OK;
7199 static int
7200 load_git_config(void)
7202         const char *config_list_argv[] = { "git", GIT_CONFIG, "--list", NULL };
7204         return run_io_load(config_list_argv, "=", read_repo_config_option);
7207 static int
7208 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
7210         if (!opt_git_dir[0]) {
7211                 string_ncopy(opt_git_dir, name, namelen);
7213         } else if (opt_is_inside_work_tree == -1) {
7214                 /* This can be 3 different values depending on the
7215                  * version of git being used. If git-rev-parse does not
7216                  * understand --is-inside-work-tree it will simply echo
7217                  * the option else either "true" or "false" is printed.
7218                  * Default to true for the unknown case. */
7219                 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
7221         } else if (*name == '.') {
7222                 string_ncopy(opt_cdup, name, namelen);
7224         } else {
7225                 string_ncopy(opt_prefix, name, namelen);
7226         }
7228         return OK;
7231 static int
7232 load_repo_info(void)
7234         const char *rev_parse_argv[] = {
7235                 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
7236                         "--show-cdup", "--show-prefix", NULL
7237         };
7239         return run_io_load(rev_parse_argv, "=", read_repo_info);
7243 /*
7244  * Main
7245  */
7247 static const char usage[] =
7248 "tig " TIG_VERSION " (" __DATE__ ")\n"
7249 "\n"
7250 "Usage: tig        [options] [revs] [--] [paths]\n"
7251 "   or: tig show   [options] [revs] [--] [paths]\n"
7252 "   or: tig blame  [rev] path\n"
7253 "   or: tig status\n"
7254 "   or: tig <      [git command output]\n"
7255 "\n"
7256 "Options:\n"
7257 "  -v, --version   Show version and exit\n"
7258 "  -h, --help      Show help message and exit";
7260 static void __NORETURN
7261 quit(int sig)
7263         /* XXX: Restore tty modes and let the OS cleanup the rest! */
7264         if (cursed)
7265                 endwin();
7266         exit(0);
7269 static void __NORETURN
7270 die(const char *err, ...)
7272         va_list args;
7274         endwin();
7276         va_start(args, err);
7277         fputs("tig: ", stderr);
7278         vfprintf(stderr, err, args);
7279         fputs("\n", stderr);
7280         va_end(args);
7282         exit(1);
7285 static void
7286 warn(const char *msg, ...)
7288         va_list args;
7290         va_start(args, msg);
7291         fputs("tig warning: ", stderr);
7292         vfprintf(stderr, msg, args);
7293         fputs("\n", stderr);
7294         va_end(args);
7297 static enum request
7298 parse_options(int argc, const char *argv[])
7300         enum request request = REQ_VIEW_MAIN;
7301         const char *subcommand;
7302         bool seen_dashdash = FALSE;
7303         /* XXX: This is vulnerable to the user overriding options
7304          * required for the main view parser. */
7305         const char *custom_argv[SIZEOF_ARG] = {
7306                 "git", "log", "--no-color", "--pretty=raw", "--parents",
7307                         "--topo-order", NULL
7308         };
7309         int i, j = 6;
7311         if (!isatty(STDIN_FILENO)) {
7312                 io_open(&VIEW(REQ_VIEW_PAGER)->io, "");
7313                 return REQ_VIEW_PAGER;
7314         }
7316         if (argc <= 1)
7317                 return REQ_NONE;
7319         subcommand = argv[1];
7320         if (!strcmp(subcommand, "status")) {
7321                 if (argc > 2)
7322                         warn("ignoring arguments after `%s'", subcommand);
7323                 return REQ_VIEW_STATUS;
7325         } else if (!strcmp(subcommand, "blame")) {
7326                 if (argc <= 2 || argc > 4)
7327                         die("invalid number of options to blame\n\n%s", usage);
7329                 i = 2;
7330                 if (argc == 4) {
7331                         string_ncopy(opt_ref, argv[i], strlen(argv[i]));
7332                         i++;
7333                 }
7335                 string_ncopy(opt_file, argv[i], strlen(argv[i]));
7336                 return REQ_VIEW_BLAME;
7338         } else if (!strcmp(subcommand, "show")) {
7339                 request = REQ_VIEW_DIFF;
7341         } else {
7342                 subcommand = NULL;
7343         }
7345         if (subcommand) {
7346                 custom_argv[1] = subcommand;
7347                 j = 2;
7348         }
7350         for (i = 1 + !!subcommand; i < argc; i++) {
7351                 const char *opt = argv[i];
7353                 if (seen_dashdash || !strcmp(opt, "--")) {
7354                         seen_dashdash = TRUE;
7356                 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
7357                         printf("tig version %s\n", TIG_VERSION);
7358                         quit(0);
7360                 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
7361                         printf("%s\n", usage);
7362                         quit(0);
7363                 }
7365                 custom_argv[j++] = opt;
7366                 if (j >= ARRAY_SIZE(custom_argv))
7367                         die("command too long");
7368         }
7370         if (!prepare_update(VIEW(request), custom_argv, NULL, FORMAT_NONE))                                                                        
7371                 die("Failed to format arguments"); 
7373         return request;
7376 int
7377 main(int argc, const char *argv[])
7379         enum request request = parse_options(argc, argv);
7380         struct view *view;
7381         size_t i;
7383         signal(SIGINT, quit);
7384         signal(SIGPIPE, SIG_IGN);
7386         if (setlocale(LC_ALL, "")) {
7387                 char *codeset = nl_langinfo(CODESET);
7389                 string_ncopy(opt_codeset, codeset, strlen(codeset));
7390         }
7392         if (load_repo_info() == ERR)
7393                 die("Failed to load repo info.");
7395         if (load_options() == ERR)
7396                 die("Failed to load user config.");
7398         if (load_git_config() == ERR)
7399                 die("Failed to load repo config.");
7401         /* Require a git repository unless when running in pager mode. */
7402         if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
7403                 die("Not a git repository");
7405         if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
7406                 opt_utf8 = FALSE;
7408         if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
7409                 opt_iconv = iconv_open(opt_codeset, opt_encoding);
7410                 if (opt_iconv == ICONV_NONE)
7411                         die("Failed to initialize character set conversion");
7412         }
7414         if (load_refs() == ERR)
7415                 die("Failed to load refs.");
7417         foreach_view (view, i)
7418                 argv_from_env(view->ops->argv, view->cmd_env);
7420         init_display();
7422         if (request != REQ_NONE)
7423                 open_view(NULL, request, OPEN_PREPARED);
7424         request = request == REQ_NONE ? REQ_VIEW_MAIN : REQ_NONE;
7426         while (view_driver(display[current_view], request)) {
7427                 int key = get_input(0);
7429                 view = display[current_view];
7430                 request = get_keybinding(view->keymap, key);
7432                 /* Some low-level request handling. This keeps access to
7433                  * status_win restricted. */
7434                 switch (request) {
7435                 case REQ_PROMPT:
7436                 {
7437                         char *cmd = read_prompt(":");
7439                         if (cmd && isdigit(*cmd)) {
7440                                 int lineno = view->lineno + 1;
7442                                 if (parse_int(&lineno, cmd, 1, view->lines + 1) == OK) {
7443                                         select_view_line(view, lineno - 1);
7444                                         report("");
7445                                 } else {
7446                                         report("Unable to parse '%s' as a line number", cmd);
7447                                 }
7449                         } else if (cmd) {
7450                                 struct view *next = VIEW(REQ_VIEW_PAGER);
7451                                 const char *argv[SIZEOF_ARG] = { "git" };
7452                                 int argc = 1;
7454                                 /* When running random commands, initially show the
7455                                  * command in the title. However, it maybe later be
7456                                  * overwritten if a commit line is selected. */
7457                                 string_ncopy(next->ref, cmd, strlen(cmd));
7459                                 if (!argv_from_string(argv, &argc, cmd)) {
7460                                         report("Too many arguments");
7461                                 } else if (!prepare_update(next, argv, NULL, FORMAT_DASH)) {
7462                                         report("Failed to format command");
7463                                 } else {
7464                                         open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
7465                                 }
7466                         }
7468                         request = REQ_NONE;
7469                         break;
7470                 }
7471                 case REQ_SEARCH:
7472                 case REQ_SEARCH_BACK:
7473                 {
7474                         const char *prompt = request == REQ_SEARCH ? "/" : "?";
7475                         char *search = read_prompt(prompt);
7477                         if (search)
7478                                 string_ncopy(opt_search, search, strlen(search));
7479                         else if (*opt_search)
7480                                 request = request == REQ_SEARCH ?
7481                                         REQ_FIND_NEXT :
7482                                         REQ_FIND_PREV;
7483                         else
7484                                 request = REQ_NONE;
7485                         break;
7486                 }
7487                 default:
7488                         break;
7489                 }
7490         }
7492         quit(0);
7494         return 0;