Code

Update copyrights
[tig.git] / tig.c
1 /* Copyright (c) 2006-2010 Jonas Fonseca <fonseca@diku.dk>
2  *
3  * This program is free software; you can redistribute it and/or
4  * modify it under the terms of the GNU General Public License as
5  * published by the Free Software Foundation; either version 2 of
6  * the License, or (at your option) any later version.
7  *
8  * This program is distributed in the hope that it will be useful,
9  * but WITHOUT ANY WARRANTY; without even the implied warranty of
10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11  * GNU General Public License for more details.
12  */
14 #ifdef HAVE_CONFIG_H
15 #include "config.h"
16 #endif
18 #ifndef TIG_VERSION
19 #define TIG_VERSION "unknown-version"
20 #endif
22 #ifndef DEBUG
23 #define NDEBUG
24 #endif
26 #include <assert.h>
27 #include <errno.h>
28 #include <ctype.h>
29 #include <signal.h>
30 #include <stdarg.h>
31 #include <stdio.h>
32 #include <stdlib.h>
33 #include <string.h>
34 #include <sys/types.h>
35 #include <sys/wait.h>
36 #include <sys/stat.h>
37 #include <sys/select.h>
38 #include <unistd.h>
39 #include <sys/time.h>
40 #include <time.h>
41 #include <fcntl.h>
43 #include <regex.h>
45 #include <locale.h>
46 #include <langinfo.h>
47 #include <iconv.h>
49 /* ncurses(3): Must be defined to have extended wide-character functions. */
50 #define _XOPEN_SOURCE_EXTENDED
52 #ifdef HAVE_NCURSESW_NCURSES_H
53 #include <ncursesw/ncurses.h>
54 #else
55 #ifdef HAVE_NCURSES_NCURSES_H
56 #include <ncurses/ncurses.h>
57 #else
58 #include <ncurses.h>
59 #endif
60 #endif
62 #if __GNUC__ >= 3
63 #define __NORETURN __attribute__((__noreturn__))
64 #else
65 #define __NORETURN
66 #endif
68 static void __NORETURN die(const char *err, ...);
69 static void warn(const char *msg, ...);
70 static void report(const char *msg, ...);
71 static void set_nonblocking_input(bool loading);
72 static size_t utf8_length(const char **string, size_t col, int *width, size_t max_width, int *trimmed, bool reserve);
73 static inline unsigned char utf8_char_length(const char *string, const char *end);
75 #define ABS(x)          ((x) >= 0  ? (x) : -(x))
76 #define MIN(x, y)       ((x) < (y) ? (x) :  (y))
77 #define MAX(x, y)       ((x) > (y) ? (x) :  (y))
79 #define ARRAY_SIZE(x)   (sizeof(x) / sizeof(x[0]))
80 #define STRING_SIZE(x)  (sizeof(x) - 1)
82 #define SIZEOF_STR      1024    /* Default string size. */
83 #define SIZEOF_REF      256     /* Size of symbolic or SHA1 ID. */
84 #define SIZEOF_REV      41      /* Holds a SHA-1 and an ending NUL. */
85 #define SIZEOF_ARG      32      /* Default argument array size. */
87 /* Revision graph */
89 #define REVGRAPH_INIT   'I'
90 #define REVGRAPH_MERGE  'M'
91 #define REVGRAPH_BRANCH '+'
92 #define REVGRAPH_COMMIT '*'
93 #define REVGRAPH_BOUND  '^'
95 #define SIZEOF_REVGRAPH 19      /* Size of revision ancestry graphics. */
97 /* This color name can be used to refer to the default term colors. */
98 #define COLOR_DEFAULT   (-1)
100 #define ICONV_NONE      ((iconv_t) -1)
101 #ifndef ICONV_CONST
102 #define ICONV_CONST     /* nothing */
103 #endif
105 /* The format and size of the date column in the main view. */
106 #define DATE_FORMAT     "%Y-%m-%d %H:%M"
107 #define DATE_COLS       STRING_SIZE("2006-04-29 14:21 ")
108 #define DATE_SHORT_COLS STRING_SIZE("2006-04-29 ")
110 #define ID_COLS         8
111 #define AUTHOR_COLS     19
113 #define MIN_VIEW_HEIGHT 4
115 #define NULL_ID         "0000000000000000000000000000000000000000"
117 #define S_ISGITLINK(mode) (((mode) & S_IFMT) == 0160000)
119 /* Some ASCII-shorthands fitted into the ncurses namespace. */
120 #define KEY_TAB         '\t'
121 #define KEY_RETURN      '\r'
122 #define KEY_ESC         27
125 struct ref {
126         char id[SIZEOF_REV];    /* Commit SHA1 ID */
127         unsigned int head:1;    /* Is it the current HEAD? */
128         unsigned int tag:1;     /* Is it a tag? */
129         unsigned int ltag:1;    /* If so, is the tag local? */
130         unsigned int remote:1;  /* Is it a remote ref? */
131         unsigned int tracked:1; /* Is it the remote for the current HEAD? */
132         char name[1];           /* Ref name; tag or head names are shortened. */
133 };
135 struct ref_list {
136         char id[SIZEOF_REV];    /* Commit SHA1 ID */
137         size_t size;            /* Number of refs. */
138         struct ref **refs;      /* References for this ID. */
139 };
141 static struct ref_list *get_ref_list(const char *id);
142 static void foreach_ref(bool (*visitor)(void *data, const struct ref *ref), void *data);
143 static int load_refs(void);
145 enum format_flags {
146         FORMAT_ALL,             /* Perform replacement in all arguments. */
147         FORMAT_DASH,            /* Perform replacement up until "--". */
148         FORMAT_NONE             /* No replacement should be performed. */
149 };
151 static bool format_argv(const char *dst[], const char *src[], enum format_flags flags);
153 enum input_status {
154         INPUT_OK,
155         INPUT_SKIP,
156         INPUT_STOP,
157         INPUT_CANCEL
158 };
160 typedef enum input_status (*input_handler)(void *data, char *buf, int c);
162 static char *prompt_input(const char *prompt, input_handler handler, void *data);
163 static bool prompt_yesno(const char *prompt);
165 struct menu_item {
166         int hotkey;
167         const char *text;
168         void *data;
169 };
171 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected);
173 /*
174  * Allocation helpers ... Entering macro hell to never be seen again.
175  */
177 #define DEFINE_ALLOCATOR(name, type, chunk_size)                                \
178 static type *                                                                   \
179 name(type **mem, size_t size, size_t increase)                                  \
180 {                                                                               \
181         size_t num_chunks = (size + chunk_size - 1) / chunk_size;               \
182         size_t num_chunks_new = (size + increase + chunk_size - 1) / chunk_size;\
183         type *tmp = *mem;                                                       \
184                                                                                 \
185         if (mem == NULL || num_chunks != num_chunks_new) {                      \
186                 tmp = realloc(tmp, num_chunks_new * chunk_size * sizeof(type)); \
187                 if (tmp)                                                        \
188                         *mem = tmp;                                             \
189         }                                                                       \
190                                                                                 \
191         return tmp;                                                             \
194 /*
195  * String helpers
196  */
198 static inline void
199 string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t srclen)
201         if (srclen > dstlen - 1)
202                 srclen = dstlen - 1;
204         strncpy(dst, src, srclen);
205         dst[srclen] = 0;
208 /* Shorthands for safely copying into a fixed buffer. */
210 #define string_copy(dst, src) \
211         string_ncopy_do(dst, sizeof(dst), src, sizeof(src))
213 #define string_ncopy(dst, src, srclen) \
214         string_ncopy_do(dst, sizeof(dst), src, srclen)
216 #define string_copy_rev(dst, src) \
217         string_ncopy_do(dst, SIZEOF_REV, src, SIZEOF_REV - 1)
219 #define string_add(dst, from, src) \
220         string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src))
222 static void
223 string_expand(char *dst, size_t dstlen, const char *src, int tabsize)
225         size_t size, pos;
227         for (size = pos = 0; size < dstlen - 1 && src[pos]; pos++) {
228                 if (src[pos] == '\t') {
229                         size_t expanded = tabsize - (size % tabsize);
231                         if (expanded + size >= dstlen - 1)
232                                 expanded = dstlen - size - 1;
233                         memcpy(dst + size, "        ", expanded);
234                         size += expanded;
235                 } else {
236                         dst[size++] = src[pos];
237                 }
238         }
240         dst[size] = 0;
243 static char *
244 chomp_string(char *name)
246         int namelen;
248         while (isspace(*name))
249                 name++;
251         namelen = strlen(name) - 1;
252         while (namelen > 0 && isspace(name[namelen]))
253                 name[namelen--] = 0;
255         return name;
258 static bool
259 string_nformat(char *buf, size_t bufsize, size_t *bufpos, const char *fmt, ...)
261         va_list args;
262         size_t pos = bufpos ? *bufpos : 0;
264         va_start(args, fmt);
265         pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
266         va_end(args);
268         if (bufpos)
269                 *bufpos = pos;
271         return pos >= bufsize ? FALSE : TRUE;
274 #define string_format(buf, fmt, args...) \
275         string_nformat(buf, sizeof(buf), NULL, fmt, args)
277 #define string_format_from(buf, from, fmt, args...) \
278         string_nformat(buf, sizeof(buf), from, fmt, args)
280 static int
281 string_enum_compare(const char *str1, const char *str2, int len)
283         size_t i;
285 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
287         /* Diff-Header == DIFF_HEADER */
288         for (i = 0; i < len; i++) {
289                 if (toupper(str1[i]) == toupper(str2[i]))
290                         continue;
292                 if (string_enum_sep(str1[i]) &&
293                     string_enum_sep(str2[i]))
294                         continue;
296                 return str1[i] - str2[i];
297         }
299         return 0;
302 #define enum_equals(entry, str, len) \
303         ((entry).namelen == (len) && !string_enum_compare((entry).name, str, len))
305 struct enum_map {
306         const char *name;
307         int namelen;
308         int value;
309 };
311 #define ENUM_MAP(name, value) { name, STRING_SIZE(name), value }
313 static char *
314 enum_map_name(const char *name, size_t namelen)
316         static char buf[SIZEOF_STR];
317         int bufpos;
319         for (bufpos = 0; bufpos <= namelen; bufpos++) {
320                 buf[bufpos] = tolower(name[bufpos]);
321                 if (buf[bufpos] == '_')
322                         buf[bufpos] = '-';
323         }
325         buf[bufpos] = 0;
326         return buf;
329 #define enum_name(entry) enum_map_name((entry).name, (entry).namelen)
331 static bool
332 map_enum_do(const struct enum_map *map, size_t map_size, int *value, const char *name)
334         size_t namelen = strlen(name);
335         int i;
337         for (i = 0; i < map_size; i++)
338                 if (enum_equals(map[i], name, namelen)) {
339                         *value = map[i].value;
340                         return TRUE;
341                 }
343         return FALSE;
346 #define map_enum(attr, map, name) \
347         map_enum_do(map, ARRAY_SIZE(map), attr, name)
349 #define prefixcmp(str1, str2) \
350         strncmp(str1, str2, STRING_SIZE(str2))
352 static inline int
353 suffixcmp(const char *str, int slen, const char *suffix)
355         size_t len = slen >= 0 ? slen : strlen(str);
356         size_t suffixlen = strlen(suffix);
358         return suffixlen < len ? strcmp(str + len - suffixlen, suffix) : -1;
362 #define DATE_INFO \
363         DATE_(NO), \
364         DATE_(DEFAULT), \
365         DATE_(RELATIVE), \
366         DATE_(SHORT)
368 enum date {
369 #define DATE_(name) DATE_##name
370         DATE_INFO
371 #undef  DATE_
372 };
374 static const struct enum_map date_map[] = {
375 #define DATE_(name) ENUM_MAP(#name, DATE_##name)
376         DATE_INFO
377 #undef  DATE_
378 };
380 struct time {
381         time_t sec;
382         int tz;
383 };
385 static inline int timecmp(const struct time *t1, const struct time *t2)
387         return t1->sec - t2->sec;
390 static const char *
391 string_date(const struct time *time, enum date date)
393         static char buf[DATE_COLS + 1];
394         static const struct enum_map reldate[] = {
395                 { "second", 1,                  60 * 2 },
396                 { "minute", 60,                 60 * 60 * 2 },
397                 { "hour",   60 * 60,            60 * 60 * 24 * 2 },
398                 { "day",    60 * 60 * 24,       60 * 60 * 24 * 7 * 2 },
399                 { "week",   60 * 60 * 24 * 7,   60 * 60 * 24 * 7 * 5 },
400                 { "month",  60 * 60 * 24 * 30,  60 * 60 * 24 * 30 * 12 },
401         };
402         struct tm tm;
404         if (date == DATE_RELATIVE) {
405                 struct timeval now;
406                 time_t date = time->sec + time->tz;
407                 time_t seconds;
408                 int i;
410                 gettimeofday(&now, NULL);
411                 seconds = now.tv_sec < date ? date - now.tv_sec : now.tv_sec - date;
412                 for (i = 0; i < ARRAY_SIZE(reldate); i++) {
413                         if (seconds >= reldate[i].value)
414                                 continue;
416                         seconds /= reldate[i].namelen;
417                         if (!string_format(buf, "%ld %s%s %s",
418                                            seconds, reldate[i].name,
419                                            seconds > 1 ? "s" : "",
420                                            now.tv_sec >= date ? "ago" : "ahead"))
421                                 break;
422                         return buf;
423                 }
424         }
426         gmtime_r(&time->sec, &tm);
427         return strftime(buf, sizeof(buf), DATE_FORMAT, &tm) ? buf : NULL;
431 #define AUTHOR_VALUES \
432         AUTHOR_(NO), \
433         AUTHOR_(FULL), \
434         AUTHOR_(ABBREVIATED)
436 enum author {
437 #define AUTHOR_(name) AUTHOR_##name
438         AUTHOR_VALUES,
439 #undef  AUTHOR_
440         AUTHOR_DEFAULT = AUTHOR_FULL
441 };
443 static const struct enum_map author_map[] = {
444 #define AUTHOR_(name) ENUM_MAP(#name, AUTHOR_##name)
445         AUTHOR_VALUES
446 #undef  AUTHOR_
447 };
449 static const char *
450 get_author_initials(const char *author)
452         static char initials[AUTHOR_COLS * 6 + 1];
453         size_t pos = 0;
454         const char *end = strchr(author, '\0');
456 #define is_initial_sep(c) (isspace(c) || ispunct(c) || (c) == '@' || (c) == '-')
458         memset(initials, 0, sizeof(initials));
459         while (author < end) {
460                 unsigned char bytes;
461                 size_t i;
463                 while (is_initial_sep(*author))
464                         author++;
466                 bytes = utf8_char_length(author, end);
467                 if (bytes < sizeof(initials) - 1 - pos) {
468                         while (bytes--) {
469                                 initials[pos++] = *author++;
470                         }
471                 }
473                 for (i = pos; author < end && !is_initial_sep(*author); author++) {
474                         if (i < sizeof(initials) - 1)
475                                 initials[i++] = *author;
476                 }
478                 initials[i++] = 0;
479         }
481         return initials;
485 static bool
486 argv_from_string(const char *argv[SIZEOF_ARG], int *argc, char *cmd)
488         int valuelen;
490         while (*cmd && *argc < SIZEOF_ARG && (valuelen = strcspn(cmd, " \t"))) {
491                 bool advance = cmd[valuelen] != 0;
493                 cmd[valuelen] = 0;
494                 argv[(*argc)++] = chomp_string(cmd);
495                 cmd = chomp_string(cmd + valuelen + advance);
496         }
498         if (*argc < SIZEOF_ARG)
499                 argv[*argc] = NULL;
500         return *argc < SIZEOF_ARG;
503 static void
504 argv_from_env(const char **argv, const char *name)
506         char *env = argv ? getenv(name) : NULL;
507         int argc = 0;
509         if (env && *env)
510                 env = strdup(env);
511         if (env && !argv_from_string(argv, &argc, env))
512                 die("Too many arguments in the `%s` environment variable", name);
516 /*
517  * Executing external commands.
518  */
520 enum io_type {
521         IO_FD,                  /* File descriptor based IO. */
522         IO_BG,                  /* Execute command in the background. */
523         IO_FG,                  /* Execute command with same std{in,out,err}. */
524         IO_RD,                  /* Read only fork+exec IO. */
525         IO_WR,                  /* Write only fork+exec IO. */
526         IO_AP,                  /* Append fork+exec output to file. */
527 };
529 struct io {
530         enum io_type type;      /* The requested type of pipe. */
531         const char *dir;        /* Directory from which to execute. */
532         pid_t pid;              /* Pipe for reading or writing. */
533         int pipe;               /* Pipe end for reading or writing. */
534         int error;              /* Error status. */
535         const char *argv[SIZEOF_ARG];   /* Shell command arguments. */
536         char *buf;              /* Read buffer. */
537         size_t bufalloc;        /* Allocated buffer size. */
538         size_t bufsize;         /* Buffer content size. */
539         char *bufpos;           /* Current buffer position. */
540         unsigned int eof:1;     /* Has end of file been reached. */
541 };
543 static void
544 reset_io(struct io *io)
546         io->pipe = -1;
547         io->pid = 0;
548         io->buf = io->bufpos = NULL;
549         io->bufalloc = io->bufsize = 0;
550         io->error = 0;
551         io->eof = 0;
554 static void
555 init_io(struct io *io, const char *dir, enum io_type type)
557         reset_io(io);
558         io->type = type;
559         io->dir = dir;
562 static bool
563 init_io_rd(struct io *io, const char *argv[], const char *dir,
564                 enum format_flags flags)
566         init_io(io, dir, IO_RD);
567         return format_argv(io->argv, argv, flags);
570 static bool
571 io_open(struct io *io, const char *fmt, ...)
573         char name[SIZEOF_STR] = "";
574         bool fits;
575         va_list args;
577         init_io(io, NULL, IO_FD);
579         va_start(args, fmt);
580         fits = vsnprintf(name, sizeof(name), fmt, args) < sizeof(name);
581         va_end(args);
583         if (!fits) {
584                 io->error = ENAMETOOLONG;
585                 return FALSE;
586         }
587         io->pipe = *name ? open(name, O_RDONLY) : STDIN_FILENO;
588         if (io->pipe == -1)
589                 io->error = errno;
590         return io->pipe != -1;
593 static bool
594 kill_io(struct io *io)
596         return io->pid == 0 || kill(io->pid, SIGKILL) != -1;
599 static bool
600 done_io(struct io *io)
602         pid_t pid = io->pid;
604         if (io->pipe != -1)
605                 close(io->pipe);
606         free(io->buf);
607         reset_io(io);
609         while (pid > 0) {
610                 int status;
611                 pid_t waiting = waitpid(pid, &status, 0);
613                 if (waiting < 0) {
614                         if (errno == EINTR)
615                                 continue;
616                         report("waitpid failed (%s)", strerror(errno));
617                         return FALSE;
618                 }
620                 return waiting == pid &&
621                        !WIFSIGNALED(status) &&
622                        WIFEXITED(status) &&
623                        !WEXITSTATUS(status);
624         }
626         return TRUE;
629 static bool
630 start_io(struct io *io)
632         int pipefds[2] = { -1, -1 };
634         if (io->type == IO_FD)
635                 return TRUE;
637         if ((io->type == IO_RD || io->type == IO_WR) &&
638             pipe(pipefds) < 0)
639                 return FALSE;
640         else if (io->type == IO_AP)
641                 pipefds[1] = io->pipe;
643         if ((io->pid = fork())) {
644                 if (pipefds[!(io->type == IO_WR)] != -1)
645                         close(pipefds[!(io->type == IO_WR)]);
646                 if (io->pid != -1) {
647                         io->pipe = pipefds[!!(io->type == IO_WR)];
648                         return TRUE;
649                 }
651         } else {
652                 if (io->type != IO_FG) {
653                         int devnull = open("/dev/null", O_RDWR);
654                         int readfd  = io->type == IO_WR ? pipefds[0] : devnull;
655                         int writefd = (io->type == IO_RD || io->type == IO_AP)
656                                                         ? pipefds[1] : devnull;
658                         dup2(readfd,  STDIN_FILENO);
659                         dup2(writefd, STDOUT_FILENO);
660                         dup2(devnull, STDERR_FILENO);
662                         close(devnull);
663                         if (pipefds[0] != -1)
664                                 close(pipefds[0]);
665                         if (pipefds[1] != -1)
666                                 close(pipefds[1]);
667                 }
669                 if (io->dir && *io->dir && chdir(io->dir) == -1)
670                         die("Failed to change directory: %s", strerror(errno));
672                 execvp(io->argv[0], (char *const*) io->argv);
673                 die("Failed to execute program: %s", strerror(errno));
674         }
676         if (pipefds[!!(io->type == IO_WR)] != -1)
677                 close(pipefds[!!(io->type == IO_WR)]);
678         return FALSE;
681 static bool
682 run_io(struct io *io, const char **argv, const char *dir, enum io_type type)
684         init_io(io, dir, type);
685         if (!format_argv(io->argv, argv, FORMAT_NONE))
686                 return FALSE;
687         return start_io(io);
690 static int
691 run_io_do(struct io *io)
693         return start_io(io) && done_io(io);
696 static int
697 run_io_bg(const char **argv)
699         struct io io = {};
701         init_io(&io, NULL, IO_BG);
702         if (!format_argv(io.argv, argv, FORMAT_NONE))
703                 return FALSE;
704         return run_io_do(&io);
707 static bool
708 run_io_fg(const char **argv, const char *dir)
710         struct io io = {};
712         init_io(&io, dir, IO_FG);
713         if (!format_argv(io.argv, argv, FORMAT_NONE))
714                 return FALSE;
715         return run_io_do(&io);
718 static bool
719 run_io_append(const char **argv, enum format_flags flags, int fd)
721         struct io io = {};
723         init_io(&io, NULL, IO_AP);
724         io.pipe = fd;
725         if (format_argv(io.argv, argv, flags))
726                 return run_io_do(&io);
727         close(fd);
728         return FALSE;
731 static bool
732 run_io_rd(struct io *io, const char **argv, const char *dir, enum format_flags flags)
734         return init_io_rd(io, argv, dir, flags) && start_io(io);
737 static bool
738 io_eof(struct io *io)
740         return io->eof;
743 static int
744 io_error(struct io *io)
746         return io->error;
749 static char *
750 io_strerror(struct io *io)
752         return strerror(io->error);
755 static bool
756 io_can_read(struct io *io)
758         struct timeval tv = { 0, 500 };
759         fd_set fds;
761         FD_ZERO(&fds);
762         FD_SET(io->pipe, &fds);
764         return select(io->pipe + 1, &fds, NULL, NULL, &tv) > 0;
767 static ssize_t
768 io_read(struct io *io, void *buf, size_t bufsize)
770         do {
771                 ssize_t readsize = read(io->pipe, buf, bufsize);
773                 if (readsize < 0 && (errno == EAGAIN || errno == EINTR))
774                         continue;
775                 else if (readsize == -1)
776                         io->error = errno;
777                 else if (readsize == 0)
778                         io->eof = 1;
779                 return readsize;
780         } while (1);
783 DEFINE_ALLOCATOR(realloc_io_buf, char, BUFSIZ)
785 static char *
786 io_get(struct io *io, int c, bool can_read)
788         char *eol;
789         ssize_t readsize;
791         while (TRUE) {
792                 if (io->bufsize > 0) {
793                         eol = memchr(io->bufpos, c, io->bufsize);
794                         if (eol) {
795                                 char *line = io->bufpos;
797                                 *eol = 0;
798                                 io->bufpos = eol + 1;
799                                 io->bufsize -= io->bufpos - line;
800                                 return line;
801                         }
802                 }
804                 if (io_eof(io)) {
805                         if (io->bufsize) {
806                                 io->bufpos[io->bufsize] = 0;
807                                 io->bufsize = 0;
808                                 return io->bufpos;
809                         }
810                         return NULL;
811                 }
813                 if (!can_read)
814                         return NULL;
816                 if (io->bufsize > 0 && io->bufpos > io->buf)
817                         memmove(io->buf, io->bufpos, io->bufsize);
819                 if (io->bufalloc == io->bufsize) {
820                         if (!realloc_io_buf(&io->buf, io->bufalloc, BUFSIZ))
821                                 return NULL;
822                         io->bufalloc += BUFSIZ;
823                 }
825                 io->bufpos = io->buf;
826                 readsize = io_read(io, io->buf + io->bufsize, io->bufalloc - io->bufsize);
827                 if (io_error(io))
828                         return NULL;
829                 io->bufsize += readsize;
830         }
833 static bool
834 io_write(struct io *io, const void *buf, size_t bufsize)
836         size_t written = 0;
838         while (!io_error(io) && written < bufsize) {
839                 ssize_t size;
841                 size = write(io->pipe, buf + written, bufsize - written);
842                 if (size < 0 && (errno == EAGAIN || errno == EINTR))
843                         continue;
844                 else if (size == -1)
845                         io->error = errno;
846                 else
847                         written += size;
848         }
850         return written == bufsize;
853 static bool
854 io_read_buf(struct io *io, char buf[], size_t bufsize)
856         char *result = io_get(io, '\n', TRUE);
858         if (result) {
859                 result = chomp_string(result);
860                 string_ncopy_do(buf, bufsize, result, strlen(result));
861         }
863         return done_io(io) && result;
866 static bool
867 run_io_buf(const char **argv, char buf[], size_t bufsize)
869         struct io io = {};
871         return run_io_rd(&io, argv, NULL, FORMAT_NONE)
872             && io_read_buf(&io, buf, bufsize);
875 static int
876 io_load(struct io *io, const char *separators,
877         int (*read_property)(char *, size_t, char *, size_t))
879         char *name;
880         int state = OK;
882         if (!start_io(io))
883                 return ERR;
885         while (state == OK && (name = io_get(io, '\n', TRUE))) {
886                 char *value;
887                 size_t namelen;
888                 size_t valuelen;
890                 name = chomp_string(name);
891                 namelen = strcspn(name, separators);
893                 if (name[namelen]) {
894                         name[namelen] = 0;
895                         value = chomp_string(name + namelen + 1);
896                         valuelen = strlen(value);
898                 } else {
899                         value = "";
900                         valuelen = 0;
901                 }
903                 state = read_property(name, namelen, value, valuelen);
904         }
906         if (state != ERR && io_error(io))
907                 state = ERR;
908         done_io(io);
910         return state;
913 static int
914 run_io_load(const char **argv, const char *separators,
915             int (*read_property)(char *, size_t, char *, size_t))
917         struct io io = {};
919         return init_io_rd(&io, argv, NULL, FORMAT_NONE)
920                 ? io_load(&io, separators, read_property) : ERR;
924 /*
925  * User requests
926  */
928 #define REQ_INFO \
929         /* XXX: Keep the view request first and in sync with views[]. */ \
930         REQ_GROUP("View switching") \
931         REQ_(VIEW_MAIN,         "Show main view"), \
932         REQ_(VIEW_DIFF,         "Show diff view"), \
933         REQ_(VIEW_LOG,          "Show log view"), \
934         REQ_(VIEW_TREE,         "Show tree view"), \
935         REQ_(VIEW_BLOB,         "Show blob view"), \
936         REQ_(VIEW_BLAME,        "Show blame view"), \
937         REQ_(VIEW_BRANCH,       "Show branch view"), \
938         REQ_(VIEW_HELP,         "Show help page"), \
939         REQ_(VIEW_PAGER,        "Show pager view"), \
940         REQ_(VIEW_STATUS,       "Show status view"), \
941         REQ_(VIEW_STAGE,        "Show stage view"), \
942         \
943         REQ_GROUP("View manipulation") \
944         REQ_(ENTER,             "Enter current line and scroll"), \
945         REQ_(NEXT,              "Move to next"), \
946         REQ_(PREVIOUS,          "Move to previous"), \
947         REQ_(PARENT,            "Move to parent"), \
948         REQ_(VIEW_NEXT,         "Move focus to next view"), \
949         REQ_(REFRESH,           "Reload and refresh"), \
950         REQ_(MAXIMIZE,          "Maximize the current view"), \
951         REQ_(VIEW_CLOSE,        "Close the current view"), \
952         REQ_(QUIT,              "Close all views and quit"), \
953         \
954         REQ_GROUP("View specific requests") \
955         REQ_(STATUS_UPDATE,     "Update file status"), \
956         REQ_(STATUS_REVERT,     "Revert file changes"), \
957         REQ_(STATUS_MERGE,      "Merge file using external tool"), \
958         REQ_(STAGE_NEXT,        "Find next chunk to stage"), \
959         \
960         REQ_GROUP("Cursor navigation") \
961         REQ_(MOVE_UP,           "Move cursor one line up"), \
962         REQ_(MOVE_DOWN,         "Move cursor one line down"), \
963         REQ_(MOVE_PAGE_DOWN,    "Move cursor one page down"), \
964         REQ_(MOVE_PAGE_UP,      "Move cursor one page up"), \
965         REQ_(MOVE_FIRST_LINE,   "Move cursor to first line"), \
966         REQ_(MOVE_LAST_LINE,    "Move cursor to last line"), \
967         \
968         REQ_GROUP("Scrolling") \
969         REQ_(SCROLL_LEFT,       "Scroll two columns left"), \
970         REQ_(SCROLL_RIGHT,      "Scroll two columns right"), \
971         REQ_(SCROLL_LINE_UP,    "Scroll one line up"), \
972         REQ_(SCROLL_LINE_DOWN,  "Scroll one line down"), \
973         REQ_(SCROLL_PAGE_UP,    "Scroll one page up"), \
974         REQ_(SCROLL_PAGE_DOWN,  "Scroll one page down"), \
975         \
976         REQ_GROUP("Searching") \
977         REQ_(SEARCH,            "Search the view"), \
978         REQ_(SEARCH_BACK,       "Search backwards in the view"), \
979         REQ_(FIND_NEXT,         "Find next search match"), \
980         REQ_(FIND_PREV,         "Find previous search match"), \
981         \
982         REQ_GROUP("Option manipulation") \
983         REQ_(OPTIONS,           "Open option menu"), \
984         REQ_(TOGGLE_LINENO,     "Toggle line numbers"), \
985         REQ_(TOGGLE_DATE,       "Toggle date display"), \
986         REQ_(TOGGLE_DATE_SHORT, "Toggle short (date-only) dates"), \
987         REQ_(TOGGLE_AUTHOR,     "Toggle author display"), \
988         REQ_(TOGGLE_REV_GRAPH,  "Toggle revision graph visualization"), \
989         REQ_(TOGGLE_REFS,       "Toggle reference display (tags/branches)"), \
990         REQ_(TOGGLE_SORT_ORDER, "Toggle ascending/descending sort order"), \
991         REQ_(TOGGLE_SORT_FIELD, "Toggle field to sort by"), \
992         \
993         REQ_GROUP("Misc") \
994         REQ_(PROMPT,            "Bring up the prompt"), \
995         REQ_(SCREEN_REDRAW,     "Redraw the screen"), \
996         REQ_(SHOW_VERSION,      "Show version information"), \
997         REQ_(STOP_LOADING,      "Stop all loading views"), \
998         REQ_(EDIT,              "Open in editor"), \
999         REQ_(NONE,              "Do nothing")
1002 /* User action requests. */
1003 enum request {
1004 #define REQ_GROUP(help)
1005 #define REQ_(req, help) REQ_##req
1007         /* Offset all requests to avoid conflicts with ncurses getch values. */
1008         REQ_OFFSET = KEY_MAX + 1,
1009         REQ_INFO
1011 #undef  REQ_GROUP
1012 #undef  REQ_
1013 };
1015 struct request_info {
1016         enum request request;
1017         const char *name;
1018         int namelen;
1019         const char *help;
1020 };
1022 static const struct request_info req_info[] = {
1023 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
1024 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
1025         REQ_INFO
1026 #undef  REQ_GROUP
1027 #undef  REQ_
1028 };
1030 static enum request
1031 get_request(const char *name)
1033         int namelen = strlen(name);
1034         int i;
1036         for (i = 0; i < ARRAY_SIZE(req_info); i++)
1037                 if (enum_equals(req_info[i], name, namelen))
1038                         return req_info[i].request;
1040         return REQ_NONE;
1044 /*
1045  * Options
1046  */
1048 /* Option and state variables. */
1049 static enum date opt_date               = DATE_DEFAULT;
1050 static enum author opt_author           = AUTHOR_DEFAULT;
1051 static bool opt_line_number             = FALSE;
1052 static bool opt_line_graphics           = TRUE;
1053 static bool opt_rev_graph               = FALSE;
1054 static bool opt_show_refs               = TRUE;
1055 static int opt_num_interval             = 5;
1056 static double opt_hscroll               = 0.50;
1057 static double opt_scale_split_view      = 2.0 / 3.0;
1058 static int opt_tab_size                 = 8;
1059 static int opt_author_cols              = AUTHOR_COLS;
1060 static char opt_path[SIZEOF_STR]        = "";
1061 static char opt_file[SIZEOF_STR]        = "";
1062 static char opt_ref[SIZEOF_REF]         = "";
1063 static char opt_head[SIZEOF_REF]        = "";
1064 static char opt_head_rev[SIZEOF_REV]    = "";
1065 static char opt_remote[SIZEOF_REF]      = "";
1066 static char opt_encoding[20]            = "UTF-8";
1067 static char opt_codeset[20]             = "UTF-8";
1068 static iconv_t opt_iconv_in             = ICONV_NONE;
1069 static iconv_t opt_iconv_out            = ICONV_NONE;
1070 static char opt_search[SIZEOF_STR]      = "";
1071 static char opt_cdup[SIZEOF_STR]        = "";
1072 static char opt_prefix[SIZEOF_STR]      = "";
1073 static char opt_git_dir[SIZEOF_STR]     = "";
1074 static signed char opt_is_inside_work_tree      = -1; /* set to TRUE or FALSE */
1075 static char opt_editor[SIZEOF_STR]      = "";
1076 static FILE *opt_tty                    = NULL;
1078 #define is_initial_commit()     (!*opt_head_rev)
1079 #define is_head_commit(rev)     (!strcmp((rev), "HEAD") || !strcmp(opt_head_rev, (rev)))
1080 #define mkdate(time)            string_date(time, opt_date)
1083 /*
1084  * Line-oriented content detection.
1085  */
1087 #define LINE_INFO \
1088 LINE(DIFF_HEADER,  "diff --git ",       COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1089 LINE(DIFF_CHUNK,   "@@",                COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1090 LINE(DIFF_ADD,     "+",                 COLOR_GREEN,    COLOR_DEFAULT,  0), \
1091 LINE(DIFF_DEL,     "-",                 COLOR_RED,      COLOR_DEFAULT,  0), \
1092 LINE(DIFF_INDEX,        "index ",         COLOR_BLUE,   COLOR_DEFAULT,  0), \
1093 LINE(DIFF_OLDMODE,      "old file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
1094 LINE(DIFF_NEWMODE,      "new file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
1095 LINE(DIFF_COPY_FROM,    "copy from",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
1096 LINE(DIFF_COPY_TO,      "copy to",        COLOR_YELLOW, COLOR_DEFAULT,  0), \
1097 LINE(DIFF_RENAME_FROM,  "rename from",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
1098 LINE(DIFF_RENAME_TO,    "rename to",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
1099 LINE(DIFF_SIMILARITY,   "similarity ",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
1100 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
1101 LINE(DIFF_TREE,         "diff-tree ",     COLOR_BLUE,   COLOR_DEFAULT,  0), \
1102 LINE(PP_AUTHOR,    "Author: ",          COLOR_CYAN,     COLOR_DEFAULT,  0), \
1103 LINE(PP_COMMIT,    "Commit: ",          COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1104 LINE(PP_MERGE,     "Merge: ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
1105 LINE(PP_DATE,      "Date:   ",          COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1106 LINE(PP_ADATE,     "AuthorDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1107 LINE(PP_CDATE,     "CommitDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1108 LINE(PP_REFS,      "Refs: ",            COLOR_RED,      COLOR_DEFAULT,  0), \
1109 LINE(COMMIT,       "commit ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
1110 LINE(PARENT,       "parent ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
1111 LINE(TREE,         "tree ",             COLOR_BLUE,     COLOR_DEFAULT,  0), \
1112 LINE(AUTHOR,       "author ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
1113 LINE(COMMITTER,    "committer ",        COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1114 LINE(SIGNOFF,      "    Signed-off-by", COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1115 LINE(ACKED,        "    Acked-by",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1116 LINE(DEFAULT,      "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
1117 LINE(CURSOR,       "",                  COLOR_WHITE,    COLOR_GREEN,    A_BOLD), \
1118 LINE(STATUS,       "",                  COLOR_GREEN,    COLOR_DEFAULT,  0), \
1119 LINE(DELIMITER,    "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1120 LINE(DATE,         "",                  COLOR_BLUE,     COLOR_DEFAULT,  0), \
1121 LINE(MODE,         "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1122 LINE(LINE_NUMBER,  "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1123 LINE(TITLE_BLUR,   "",                  COLOR_WHITE,    COLOR_BLUE,     0), \
1124 LINE(TITLE_FOCUS,  "",                  COLOR_WHITE,    COLOR_BLUE,     A_BOLD), \
1125 LINE(MAIN_COMMIT,  "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
1126 LINE(MAIN_TAG,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  A_BOLD), \
1127 LINE(MAIN_LOCAL_TAG,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1128 LINE(MAIN_REMOTE,  "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1129 LINE(MAIN_TRACKED, "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_BOLD), \
1130 LINE(MAIN_REF,     "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1131 LINE(MAIN_HEAD,    "",                  COLOR_CYAN,     COLOR_DEFAULT,  A_BOLD), \
1132 LINE(MAIN_REVGRAPH,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1133 LINE(TREE_HEAD,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_BOLD), \
1134 LINE(TREE_DIR,     "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_NORMAL), \
1135 LINE(TREE_FILE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
1136 LINE(STAT_HEAD,    "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1137 LINE(STAT_SECTION, "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1138 LINE(STAT_NONE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
1139 LINE(STAT_STAGED,  "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1140 LINE(STAT_UNSTAGED,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1141 LINE(STAT_UNTRACKED,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1142 LINE(HELP_KEYMAP,  "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1143 LINE(HELP_GROUP,   "",                  COLOR_BLUE,     COLOR_DEFAULT,  0), \
1144 LINE(BLAME_ID,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0)
1146 enum line_type {
1147 #define LINE(type, line, fg, bg, attr) \
1148         LINE_##type
1149         LINE_INFO,
1150         LINE_NONE
1151 #undef  LINE
1152 };
1154 struct line_info {
1155         const char *name;       /* Option name. */
1156         int namelen;            /* Size of option name. */
1157         const char *line;       /* The start of line to match. */
1158         int linelen;            /* Size of string to match. */
1159         int fg, bg, attr;       /* Color and text attributes for the lines. */
1160 };
1162 static struct line_info line_info[] = {
1163 #define LINE(type, line, fg, bg, attr) \
1164         { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
1165         LINE_INFO
1166 #undef  LINE
1167 };
1169 static enum line_type
1170 get_line_type(const char *line)
1172         int linelen = strlen(line);
1173         enum line_type type;
1175         for (type = 0; type < ARRAY_SIZE(line_info); type++)
1176                 /* Case insensitive search matches Signed-off-by lines better. */
1177                 if (linelen >= line_info[type].linelen &&
1178                     !strncasecmp(line_info[type].line, line, line_info[type].linelen))
1179                         return type;
1181         return LINE_DEFAULT;
1184 static inline int
1185 get_line_attr(enum line_type type)
1187         assert(type < ARRAY_SIZE(line_info));
1188         return COLOR_PAIR(type) | line_info[type].attr;
1191 static struct line_info *
1192 get_line_info(const char *name)
1194         size_t namelen = strlen(name);
1195         enum line_type type;
1197         for (type = 0; type < ARRAY_SIZE(line_info); type++)
1198                 if (enum_equals(line_info[type], name, namelen))
1199                         return &line_info[type];
1201         return NULL;
1204 static void
1205 init_colors(void)
1207         int default_bg = line_info[LINE_DEFAULT].bg;
1208         int default_fg = line_info[LINE_DEFAULT].fg;
1209         enum line_type type;
1211         start_color();
1213         if (assume_default_colors(default_fg, default_bg) == ERR) {
1214                 default_bg = COLOR_BLACK;
1215                 default_fg = COLOR_WHITE;
1216         }
1218         for (type = 0; type < ARRAY_SIZE(line_info); type++) {
1219                 struct line_info *info = &line_info[type];
1220                 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
1221                 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
1223                 init_pair(type, fg, bg);
1224         }
1227 struct line {
1228         enum line_type type;
1230         /* State flags */
1231         unsigned int selected:1;
1232         unsigned int dirty:1;
1233         unsigned int cleareol:1;
1234         unsigned int other:16;
1236         void *data;             /* User data */
1237 };
1240 /*
1241  * Keys
1242  */
1244 struct keybinding {
1245         int alias;
1246         enum request request;
1247 };
1249 static const struct keybinding default_keybindings[] = {
1250         /* View switching */
1251         { 'm',          REQ_VIEW_MAIN },
1252         { 'd',          REQ_VIEW_DIFF },
1253         { 'l',          REQ_VIEW_LOG },
1254         { 't',          REQ_VIEW_TREE },
1255         { 'f',          REQ_VIEW_BLOB },
1256         { 'B',          REQ_VIEW_BLAME },
1257         { 'H',          REQ_VIEW_BRANCH },
1258         { 'p',          REQ_VIEW_PAGER },
1259         { 'h',          REQ_VIEW_HELP },
1260         { 'S',          REQ_VIEW_STATUS },
1261         { 'c',          REQ_VIEW_STAGE },
1263         /* View manipulation */
1264         { 'q',          REQ_VIEW_CLOSE },
1265         { KEY_TAB,      REQ_VIEW_NEXT },
1266         { KEY_RETURN,   REQ_ENTER },
1267         { KEY_UP,       REQ_PREVIOUS },
1268         { KEY_DOWN,     REQ_NEXT },
1269         { 'R',          REQ_REFRESH },
1270         { KEY_F(5),     REQ_REFRESH },
1271         { 'O',          REQ_MAXIMIZE },
1273         /* Cursor navigation */
1274         { 'k',          REQ_MOVE_UP },
1275         { 'j',          REQ_MOVE_DOWN },
1276         { KEY_HOME,     REQ_MOVE_FIRST_LINE },
1277         { KEY_END,      REQ_MOVE_LAST_LINE },
1278         { KEY_NPAGE,    REQ_MOVE_PAGE_DOWN },
1279         { ' ',          REQ_MOVE_PAGE_DOWN },
1280         { KEY_PPAGE,    REQ_MOVE_PAGE_UP },
1281         { 'b',          REQ_MOVE_PAGE_UP },
1282         { '-',          REQ_MOVE_PAGE_UP },
1284         /* Scrolling */
1285         { KEY_LEFT,     REQ_SCROLL_LEFT },
1286         { KEY_RIGHT,    REQ_SCROLL_RIGHT },
1287         { KEY_IC,       REQ_SCROLL_LINE_UP },
1288         { KEY_DC,       REQ_SCROLL_LINE_DOWN },
1289         { 'w',          REQ_SCROLL_PAGE_UP },
1290         { 's',          REQ_SCROLL_PAGE_DOWN },
1292         /* Searching */
1293         { '/',          REQ_SEARCH },
1294         { '?',          REQ_SEARCH_BACK },
1295         { 'n',          REQ_FIND_NEXT },
1296         { 'N',          REQ_FIND_PREV },
1298         /* Misc */
1299         { 'Q',          REQ_QUIT },
1300         { 'z',          REQ_STOP_LOADING },
1301         { 'v',          REQ_SHOW_VERSION },
1302         { 'r',          REQ_SCREEN_REDRAW },
1303         { 'o',          REQ_OPTIONS },
1304         { '.',          REQ_TOGGLE_LINENO },
1305         { 'D',          REQ_TOGGLE_DATE },
1306         { 'A',          REQ_TOGGLE_AUTHOR },
1307         { 'g',          REQ_TOGGLE_REV_GRAPH },
1308         { 'F',          REQ_TOGGLE_REFS },
1309         { 'I',          REQ_TOGGLE_SORT_ORDER },
1310         { 'i',          REQ_TOGGLE_SORT_FIELD },
1311         { ':',          REQ_PROMPT },
1312         { 'u',          REQ_STATUS_UPDATE },
1313         { '!',          REQ_STATUS_REVERT },
1314         { 'M',          REQ_STATUS_MERGE },
1315         { '@',          REQ_STAGE_NEXT },
1316         { ',',          REQ_PARENT },
1317         { 'e',          REQ_EDIT },
1318 };
1320 #define KEYMAP_INFO \
1321         KEYMAP_(GENERIC), \
1322         KEYMAP_(MAIN), \
1323         KEYMAP_(DIFF), \
1324         KEYMAP_(LOG), \
1325         KEYMAP_(TREE), \
1326         KEYMAP_(BLOB), \
1327         KEYMAP_(BLAME), \
1328         KEYMAP_(BRANCH), \
1329         KEYMAP_(PAGER), \
1330         KEYMAP_(HELP), \
1331         KEYMAP_(STATUS), \
1332         KEYMAP_(STAGE)
1334 enum keymap {
1335 #define KEYMAP_(name) KEYMAP_##name
1336         KEYMAP_INFO
1337 #undef  KEYMAP_
1338 };
1340 static const struct enum_map keymap_table[] = {
1341 #define KEYMAP_(name) ENUM_MAP(#name, KEYMAP_##name)
1342         KEYMAP_INFO
1343 #undef  KEYMAP_
1344 };
1346 #define set_keymap(map, name) map_enum(map, keymap_table, name)
1348 struct keybinding_table {
1349         struct keybinding *data;
1350         size_t size;
1351 };
1353 static struct keybinding_table keybindings[ARRAY_SIZE(keymap_table)];
1355 static void
1356 add_keybinding(enum keymap keymap, enum request request, int key)
1358         struct keybinding_table *table = &keybindings[keymap];
1360         table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data));
1361         if (!table->data)
1362                 die("Failed to allocate keybinding");
1363         table->data[table->size].alias = key;
1364         table->data[table->size++].request = request;
1367 /* Looks for a key binding first in the given map, then in the generic map, and
1368  * lastly in the default keybindings. */
1369 static enum request
1370 get_keybinding(enum keymap keymap, int key)
1372         size_t i;
1374         for (i = 0; i < keybindings[keymap].size; i++)
1375                 if (keybindings[keymap].data[i].alias == key)
1376                         return keybindings[keymap].data[i].request;
1378         for (i = 0; i < keybindings[KEYMAP_GENERIC].size; i++)
1379                 if (keybindings[KEYMAP_GENERIC].data[i].alias == key)
1380                         return keybindings[KEYMAP_GENERIC].data[i].request;
1382         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1383                 if (default_keybindings[i].alias == key)
1384                         return default_keybindings[i].request;
1386         return (enum request) key;
1390 struct key {
1391         const char *name;
1392         int value;
1393 };
1395 static const struct key key_table[] = {
1396         { "Enter",      KEY_RETURN },
1397         { "Space",      ' ' },
1398         { "Backspace",  KEY_BACKSPACE },
1399         { "Tab",        KEY_TAB },
1400         { "Escape",     KEY_ESC },
1401         { "Left",       KEY_LEFT },
1402         { "Right",      KEY_RIGHT },
1403         { "Up",         KEY_UP },
1404         { "Down",       KEY_DOWN },
1405         { "Insert",     KEY_IC },
1406         { "Delete",     KEY_DC },
1407         { "Hash",       '#' },
1408         { "Home",       KEY_HOME },
1409         { "End",        KEY_END },
1410         { "PageUp",     KEY_PPAGE },
1411         { "PageDown",   KEY_NPAGE },
1412         { "F1",         KEY_F(1) },
1413         { "F2",         KEY_F(2) },
1414         { "F3",         KEY_F(3) },
1415         { "F4",         KEY_F(4) },
1416         { "F5",         KEY_F(5) },
1417         { "F6",         KEY_F(6) },
1418         { "F7",         KEY_F(7) },
1419         { "F8",         KEY_F(8) },
1420         { "F9",         KEY_F(9) },
1421         { "F10",        KEY_F(10) },
1422         { "F11",        KEY_F(11) },
1423         { "F12",        KEY_F(12) },
1424 };
1426 static int
1427 get_key_value(const char *name)
1429         int i;
1431         for (i = 0; i < ARRAY_SIZE(key_table); i++)
1432                 if (!strcasecmp(key_table[i].name, name))
1433                         return key_table[i].value;
1435         if (strlen(name) == 1 && isprint(*name))
1436                 return (int) *name;
1438         return ERR;
1441 static const char *
1442 get_key_name(int key_value)
1444         static char key_char[] = "'X'";
1445         const char *seq = NULL;
1446         int key;
1448         for (key = 0; key < ARRAY_SIZE(key_table); key++)
1449                 if (key_table[key].value == key_value)
1450                         seq = key_table[key].name;
1452         if (seq == NULL &&
1453             key_value < 127 &&
1454             isprint(key_value)) {
1455                 key_char[1] = (char) key_value;
1456                 seq = key_char;
1457         }
1459         return seq ? seq : "(no key)";
1462 static bool
1463 append_key(char *buf, size_t *pos, const struct keybinding *keybinding)
1465         const char *sep = *pos > 0 ? ", " : "";
1466         const char *keyname = get_key_name(keybinding->alias);
1468         return string_nformat(buf, BUFSIZ, pos, "%s%s", sep, keyname);
1471 static bool
1472 append_keymap_request_keys(char *buf, size_t *pos, enum request request,
1473                            enum keymap keymap, bool all)
1475         int i;
1477         for (i = 0; i < keybindings[keymap].size; i++) {
1478                 if (keybindings[keymap].data[i].request == request) {
1479                         if (!append_key(buf, pos, &keybindings[keymap].data[i]))
1480                                 return FALSE;
1481                         if (!all)
1482                                 break;
1483                 }
1484         }
1486         return TRUE;
1489 #define get_key(keymap, request) get_keys(keymap, request, FALSE)
1491 static const char *
1492 get_keys(enum keymap keymap, enum request request, bool all)
1494         static char buf[BUFSIZ];
1495         size_t pos = 0;
1496         int i;
1498         buf[pos] = 0;
1500         if (!append_keymap_request_keys(buf, &pos, request, keymap, all))
1501                 return "Too many keybindings!";
1502         if (pos > 0 && !all)
1503                 return buf;
1505         if (keymap != KEYMAP_GENERIC) {
1506                 /* Only the generic keymap includes the default keybindings when
1507                  * listing all keys. */
1508                 if (all)
1509                         return buf;
1511                 if (!append_keymap_request_keys(buf, &pos, request, KEYMAP_GENERIC, all))
1512                         return "Too many keybindings!";
1513                 if (pos)
1514                         return buf;
1515         }
1517         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1518                 if (default_keybindings[i].request == request) {
1519                         if (!append_key(buf, &pos, &default_keybindings[i]))
1520                                 return "Too many keybindings!";
1521                         if (!all)
1522                                 return buf;
1523                 }
1524         }
1526         return buf;
1529 struct run_request {
1530         enum keymap keymap;
1531         int key;
1532         const char *argv[SIZEOF_ARG];
1533 };
1535 static struct run_request *run_request;
1536 static size_t run_requests;
1538 DEFINE_ALLOCATOR(realloc_run_requests, struct run_request, 8)
1540 static enum request
1541 add_run_request(enum keymap keymap, int key, int argc, const char **argv)
1543         struct run_request *req;
1545         if (argc >= ARRAY_SIZE(req->argv) - 1)
1546                 return REQ_NONE;
1548         if (!realloc_run_requests(&run_request, run_requests, 1))
1549                 return REQ_NONE;
1551         req = &run_request[run_requests];
1552         req->keymap = keymap;
1553         req->key = key;
1554         req->argv[0] = NULL;
1556         if (!format_argv(req->argv, argv, FORMAT_NONE))
1557                 return REQ_NONE;
1559         return REQ_NONE + ++run_requests;
1562 static struct run_request *
1563 get_run_request(enum request request)
1565         if (request <= REQ_NONE)
1566                 return NULL;
1567         return &run_request[request - REQ_NONE - 1];
1570 static void
1571 add_builtin_run_requests(void)
1573         const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1574         const char *commit[] = { "git", "commit", NULL };
1575         const char *gc[] = { "git", "gc", NULL };
1576         struct {
1577                 enum keymap keymap;
1578                 int key;
1579                 int argc;
1580                 const char **argv;
1581         } reqs[] = {
1582                 { KEYMAP_MAIN,    'C', ARRAY_SIZE(cherry_pick) - 1, cherry_pick },
1583                 { KEYMAP_STATUS,  'C', ARRAY_SIZE(commit) - 1, commit },
1584                 { KEYMAP_GENERIC, 'G', ARRAY_SIZE(gc) - 1, gc },
1585         };
1586         int i;
1588         for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1589                 enum request req;
1591                 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argc, reqs[i].argv);
1592                 if (req != REQ_NONE)
1593                         add_keybinding(reqs[i].keymap, req, reqs[i].key);
1594         }
1597 /*
1598  * User config file handling.
1599  */
1601 static int   config_lineno;
1602 static bool  config_errors;
1603 static const char *config_msg;
1605 static const struct enum_map color_map[] = {
1606 #define COLOR_MAP(name) ENUM_MAP(#name, COLOR_##name)
1607         COLOR_MAP(DEFAULT),
1608         COLOR_MAP(BLACK),
1609         COLOR_MAP(BLUE),
1610         COLOR_MAP(CYAN),
1611         COLOR_MAP(GREEN),
1612         COLOR_MAP(MAGENTA),
1613         COLOR_MAP(RED),
1614         COLOR_MAP(WHITE),
1615         COLOR_MAP(YELLOW),
1616 };
1618 static const struct enum_map attr_map[] = {
1619 #define ATTR_MAP(name) ENUM_MAP(#name, A_##name)
1620         ATTR_MAP(NORMAL),
1621         ATTR_MAP(BLINK),
1622         ATTR_MAP(BOLD),
1623         ATTR_MAP(DIM),
1624         ATTR_MAP(REVERSE),
1625         ATTR_MAP(STANDOUT),
1626         ATTR_MAP(UNDERLINE),
1627 };
1629 #define set_attribute(attr, name)       map_enum(attr, attr_map, name)
1631 static int parse_step(double *opt, const char *arg)
1633         *opt = atoi(arg);
1634         if (!strchr(arg, '%'))
1635                 return OK;
1637         /* "Shift down" so 100% and 1 does not conflict. */
1638         *opt = (*opt - 1) / 100;
1639         if (*opt >= 1.0) {
1640                 *opt = 0.99;
1641                 config_msg = "Step value larger than 100%";
1642                 return ERR;
1643         }
1644         if (*opt < 0.0) {
1645                 *opt = 1;
1646                 config_msg = "Invalid step value";
1647                 return ERR;
1648         }
1649         return OK;
1652 static int
1653 parse_int(int *opt, const char *arg, int min, int max)
1655         int value = atoi(arg);
1657         if (min <= value && value <= max) {
1658                 *opt = value;
1659                 return OK;
1660         }
1662         config_msg = "Integer value out of bound";
1663         return ERR;
1666 static bool
1667 set_color(int *color, const char *name)
1669         if (map_enum(color, color_map, name))
1670                 return TRUE;
1671         if (!prefixcmp(name, "color"))
1672                 return parse_int(color, name + 5, 0, 255) == OK;
1673         return FALSE;
1676 /* Wants: object fgcolor bgcolor [attribute] */
1677 static int
1678 option_color_command(int argc, const char *argv[])
1680         struct line_info *info;
1682         if (argc < 3) {
1683                 config_msg = "Wrong number of arguments given to color command";
1684                 return ERR;
1685         }
1687         info = get_line_info(argv[0]);
1688         if (!info) {
1689                 static const struct enum_map obsolete[] = {
1690                         ENUM_MAP("main-delim",  LINE_DELIMITER),
1691                         ENUM_MAP("main-date",   LINE_DATE),
1692                         ENUM_MAP("main-author", LINE_AUTHOR),
1693                 };
1694                 int index;
1696                 if (!map_enum(&index, obsolete, argv[0])) {
1697                         config_msg = "Unknown color name";
1698                         return ERR;
1699                 }
1700                 info = &line_info[index];
1701         }
1703         if (!set_color(&info->fg, argv[1]) ||
1704             !set_color(&info->bg, argv[2])) {
1705                 config_msg = "Unknown color";
1706                 return ERR;
1707         }
1709         info->attr = 0;
1710         while (argc-- > 3) {
1711                 int attr;
1713                 if (!set_attribute(&attr, argv[argc])) {
1714                         config_msg = "Unknown attribute";
1715                         return ERR;
1716                 }
1717                 info->attr |= attr;
1718         }
1720         return OK;
1723 static int parse_bool(bool *opt, const char *arg)
1725         *opt = (!strcmp(arg, "1") || !strcmp(arg, "true") || !strcmp(arg, "yes"))
1726                 ? TRUE : FALSE;
1727         return OK;
1730 static int parse_enum_do(unsigned int *opt, const char *arg,
1731                          const struct enum_map *map, size_t map_size)
1733         bool is_true;
1735         assert(map_size > 1);
1737         if (map_enum_do(map, map_size, (int *) opt, arg))
1738                 return OK;
1740         if (parse_bool(&is_true, arg) != OK)
1741                 return ERR;
1743         *opt = is_true ? map[1].value : map[0].value;
1744         return OK;
1747 #define parse_enum(opt, arg, map) \
1748         parse_enum_do(opt, arg, map, ARRAY_SIZE(map))
1750 static int
1751 parse_string(char *opt, const char *arg, size_t optsize)
1753         int arglen = strlen(arg);
1755         switch (arg[0]) {
1756         case '\"':
1757         case '\'':
1758                 if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1759                         config_msg = "Unmatched quotation";
1760                         return ERR;
1761                 }
1762                 arg += 1; arglen -= 2;
1763         default:
1764                 string_ncopy_do(opt, optsize, arg, arglen);
1765                 return OK;
1766         }
1769 /* Wants: name = value */
1770 static int
1771 option_set_command(int argc, const char *argv[])
1773         if (argc != 3) {
1774                 config_msg = "Wrong number of arguments given to set command";
1775                 return ERR;
1776         }
1778         if (strcmp(argv[1], "=")) {
1779                 config_msg = "No value assigned";
1780                 return ERR;
1781         }
1783         if (!strcmp(argv[0], "show-author"))
1784                 return parse_enum(&opt_author, argv[2], author_map);
1786         if (!strcmp(argv[0], "show-date"))
1787                 return parse_enum(&opt_date, argv[2], date_map);
1789         if (!strcmp(argv[0], "show-rev-graph"))
1790                 return parse_bool(&opt_rev_graph, argv[2]);
1792         if (!strcmp(argv[0], "show-refs"))
1793                 return parse_bool(&opt_show_refs, argv[2]);
1795         if (!strcmp(argv[0], "show-line-numbers"))
1796                 return parse_bool(&opt_line_number, argv[2]);
1798         if (!strcmp(argv[0], "line-graphics"))
1799                 return parse_bool(&opt_line_graphics, argv[2]);
1801         if (!strcmp(argv[0], "line-number-interval"))
1802                 return parse_int(&opt_num_interval, argv[2], 1, 1024);
1804         if (!strcmp(argv[0], "author-width"))
1805                 return parse_int(&opt_author_cols, argv[2], 0, 1024);
1807         if (!strcmp(argv[0], "horizontal-scroll"))
1808                 return parse_step(&opt_hscroll, argv[2]);
1810         if (!strcmp(argv[0], "split-view-height"))
1811                 return parse_step(&opt_scale_split_view, argv[2]);
1813         if (!strcmp(argv[0], "tab-size"))
1814                 return parse_int(&opt_tab_size, argv[2], 1, 1024);
1816         if (!strcmp(argv[0], "commit-encoding"))
1817                 return parse_string(opt_encoding, argv[2], sizeof(opt_encoding));
1819         config_msg = "Unknown variable name";
1820         return ERR;
1823 /* Wants: mode request key */
1824 static int
1825 option_bind_command(int argc, const char *argv[])
1827         enum request request;
1828         int keymap = -1;
1829         int key;
1831         if (argc < 3) {
1832                 config_msg = "Wrong number of arguments given to bind command";
1833                 return ERR;
1834         }
1836         if (set_keymap(&keymap, argv[0]) == ERR) {
1837                 config_msg = "Unknown key map";
1838                 return ERR;
1839         }
1841         key = get_key_value(argv[1]);
1842         if (key == ERR) {
1843                 config_msg = "Unknown key";
1844                 return ERR;
1845         }
1847         request = get_request(argv[2]);
1848         if (request == REQ_NONE) {
1849                 static const struct enum_map obsolete[] = {
1850                         ENUM_MAP("cherry-pick",         REQ_NONE),
1851                         ENUM_MAP("screen-resize",       REQ_NONE),
1852                         ENUM_MAP("tree-parent",         REQ_PARENT),
1853                 };
1854                 int alias;
1856                 if (map_enum(&alias, obsolete, argv[2])) {
1857                         if (alias != REQ_NONE)
1858                                 add_keybinding(keymap, alias, key);
1859                         config_msg = "Obsolete request name";
1860                         return ERR;
1861                 }
1862         }
1863         if (request == REQ_NONE && *argv[2]++ == '!')
1864                 request = add_run_request(keymap, key, argc - 2, argv + 2);
1865         if (request == REQ_NONE) {
1866                 config_msg = "Unknown request name";
1867                 return ERR;
1868         }
1870         add_keybinding(keymap, request, key);
1872         return OK;
1875 static int
1876 set_option(const char *opt, char *value)
1878         const char *argv[SIZEOF_ARG];
1879         int argc = 0;
1881         if (!argv_from_string(argv, &argc, value)) {
1882                 config_msg = "Too many option arguments";
1883                 return ERR;
1884         }
1886         if (!strcmp(opt, "color"))
1887                 return option_color_command(argc, argv);
1889         if (!strcmp(opt, "set"))
1890                 return option_set_command(argc, argv);
1892         if (!strcmp(opt, "bind"))
1893                 return option_bind_command(argc, argv);
1895         config_msg = "Unknown option command";
1896         return ERR;
1899 static int
1900 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
1902         int status = OK;
1904         config_lineno++;
1905         config_msg = "Internal error";
1907         /* Check for comment markers, since read_properties() will
1908          * only ensure opt and value are split at first " \t". */
1909         optlen = strcspn(opt, "#");
1910         if (optlen == 0)
1911                 return OK;
1913         if (opt[optlen] != 0) {
1914                 config_msg = "No option value";
1915                 status = ERR;
1917         }  else {
1918                 /* Look for comment endings in the value. */
1919                 size_t len = strcspn(value, "#");
1921                 if (len < valuelen) {
1922                         valuelen = len;
1923                         value[valuelen] = 0;
1924                 }
1926                 status = set_option(opt, value);
1927         }
1929         if (status == ERR) {
1930                 warn("Error on line %d, near '%.*s': %s",
1931                      config_lineno, (int) optlen, opt, config_msg);
1932                 config_errors = TRUE;
1933         }
1935         /* Always keep going if errors are encountered. */
1936         return OK;
1939 static void
1940 load_option_file(const char *path)
1942         struct io io = {};
1944         /* It's OK that the file doesn't exist. */
1945         if (!io_open(&io, "%s", path))
1946                 return;
1948         config_lineno = 0;
1949         config_errors = FALSE;
1951         if (io_load(&io, " \t", read_option) == ERR ||
1952             config_errors == TRUE)
1953                 warn("Errors while loading %s.", path);
1956 static int
1957 load_options(void)
1959         const char *home = getenv("HOME");
1960         const char *tigrc_user = getenv("TIGRC_USER");
1961         const char *tigrc_system = getenv("TIGRC_SYSTEM");
1962         char buf[SIZEOF_STR];
1964         add_builtin_run_requests();
1966         if (!tigrc_system)
1967                 tigrc_system = SYSCONFDIR "/tigrc";
1968         load_option_file(tigrc_system);
1970         if (!tigrc_user) {
1971                 if (!home || !string_format(buf, "%s/.tigrc", home))
1972                         return ERR;
1973                 tigrc_user = buf;
1974         }
1975         load_option_file(tigrc_user);
1977         return OK;
1981 /*
1982  * The viewer
1983  */
1985 struct view;
1986 struct view_ops;
1988 /* The display array of active views and the index of the current view. */
1989 static struct view *display[2];
1990 static unsigned int current_view;
1992 #define foreach_displayed_view(view, i) \
1993         for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1995 #define displayed_views()       (display[1] != NULL ? 2 : 1)
1997 /* Current head and commit ID */
1998 static char ref_blob[SIZEOF_REF]        = "";
1999 static char ref_commit[SIZEOF_REF]      = "HEAD";
2000 static char ref_head[SIZEOF_REF]        = "HEAD";
2002 struct view {
2003         const char *name;       /* View name */
2004         const char *cmd_env;    /* Command line set via environment */
2005         const char *id;         /* Points to either of ref_{head,commit,blob} */
2007         struct view_ops *ops;   /* View operations */
2009         enum keymap keymap;     /* What keymap does this view have */
2010         bool git_dir;           /* Whether the view requires a git directory. */
2012         char ref[SIZEOF_REF];   /* Hovered commit reference */
2013         char vid[SIZEOF_REF];   /* View ID. Set to id member when updating. */
2015         int height, width;      /* The width and height of the main window */
2016         WINDOW *win;            /* The main window */
2017         WINDOW *title;          /* The title window living below the main window */
2019         /* Navigation */
2020         unsigned long offset;   /* Offset of the window top */
2021         unsigned long yoffset;  /* Offset from the window side. */
2022         unsigned long lineno;   /* Current line number */
2023         unsigned long p_offset; /* Previous offset of the window top */
2024         unsigned long p_yoffset;/* Previous offset from the window side */
2025         unsigned long p_lineno; /* Previous current line number */
2026         bool p_restore;         /* Should the previous position be restored. */
2028         /* Searching */
2029         char grep[SIZEOF_STR];  /* Search string */
2030         regex_t *regex;         /* Pre-compiled regexp */
2032         /* If non-NULL, points to the view that opened this view. If this view
2033          * is closed tig will switch back to the parent view. */
2034         struct view *parent;
2036         /* Buffering */
2037         size_t lines;           /* Total number of lines */
2038         struct line *line;      /* Line index */
2039         unsigned int digits;    /* Number of digits in the lines member. */
2041         /* Drawing */
2042         struct line *curline;   /* Line currently being drawn. */
2043         enum line_type curtype; /* Attribute currently used for drawing. */
2044         unsigned long col;      /* Column when drawing. */
2045         bool has_scrolled;      /* View was scrolled. */
2047         /* Loading */
2048         struct io io;
2049         struct io *pipe;
2050         time_t start_time;
2051         time_t update_secs;
2052 };
2054 struct view_ops {
2055         /* What type of content being displayed. Used in the title bar. */
2056         const char *type;
2057         /* Default command arguments. */
2058         const char **argv;
2059         /* Open and reads in all view content. */
2060         bool (*open)(struct view *view);
2061         /* Read one line; updates view->line. */
2062         bool (*read)(struct view *view, char *data);
2063         /* Draw one line; @lineno must be < view->height. */
2064         bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
2065         /* Depending on view handle a special requests. */
2066         enum request (*request)(struct view *view, enum request request, struct line *line);
2067         /* Search for regexp in a line. */
2068         bool (*grep)(struct view *view, struct line *line);
2069         /* Select line */
2070         void (*select)(struct view *view, struct line *line);
2071         /* Prepare view for loading */
2072         bool (*prepare)(struct view *view);
2073 };
2075 static struct view_ops blame_ops;
2076 static struct view_ops blob_ops;
2077 static struct view_ops diff_ops;
2078 static struct view_ops help_ops;
2079 static struct view_ops log_ops;
2080 static struct view_ops main_ops;
2081 static struct view_ops pager_ops;
2082 static struct view_ops stage_ops;
2083 static struct view_ops status_ops;
2084 static struct view_ops tree_ops;
2085 static struct view_ops branch_ops;
2087 #define VIEW_STR(name, env, ref, ops, map, git) \
2088         { name, #env, ref, ops, map, git }
2090 #define VIEW_(id, name, ops, git, ref) \
2091         VIEW_STR(name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
2094 static struct view views[] = {
2095         VIEW_(MAIN,   "main",   &main_ops,   TRUE,  ref_head),
2096         VIEW_(DIFF,   "diff",   &diff_ops,   TRUE,  ref_commit),
2097         VIEW_(LOG,    "log",    &log_ops,    TRUE,  ref_head),
2098         VIEW_(TREE,   "tree",   &tree_ops,   TRUE,  ref_commit),
2099         VIEW_(BLOB,   "blob",   &blob_ops,   TRUE,  ref_blob),
2100         VIEW_(BLAME,  "blame",  &blame_ops,  TRUE,  ref_commit),
2101         VIEW_(BRANCH, "branch", &branch_ops, TRUE,  ref_head),
2102         VIEW_(HELP,   "help",   &help_ops,   FALSE, ""),
2103         VIEW_(PAGER,  "pager",  &pager_ops,  FALSE, "stdin"),
2104         VIEW_(STATUS, "status", &status_ops, TRUE,  ""),
2105         VIEW_(STAGE,  "stage",  &stage_ops,  TRUE,  ""),
2106 };
2108 #define VIEW(req)       (&views[(req) - REQ_OFFSET - 1])
2109 #define VIEW_REQ(view)  ((view) - views + REQ_OFFSET + 1)
2111 #define foreach_view(view, i) \
2112         for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
2114 #define view_is_displayed(view) \
2115         (view == display[0] || view == display[1])
2118 enum line_graphic {
2119         LINE_GRAPHIC_VLINE
2120 };
2122 static chtype line_graphics[] = {
2123         /* LINE_GRAPHIC_VLINE: */ '|'
2124 };
2126 static inline void
2127 set_view_attr(struct view *view, enum line_type type)
2129         if (!view->curline->selected && view->curtype != type) {
2130                 wattrset(view->win, get_line_attr(type));
2131                 wchgat(view->win, -1, 0, type, NULL);
2132                 view->curtype = type;
2133         }
2136 static int
2137 draw_chars(struct view *view, enum line_type type, const char *string,
2138            int max_len, bool use_tilde)
2140         static char out_buffer[BUFSIZ * 2];
2141         int len = 0;
2142         int col = 0;
2143         int trimmed = FALSE;
2144         size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2146         if (max_len <= 0)
2147                 return 0;
2149         len = utf8_length(&string, skip, &col, max_len, &trimmed, use_tilde);
2151         set_view_attr(view, type);
2152         if (len > 0) {
2153                 if (opt_iconv_out != ICONV_NONE) {
2154                         ICONV_CONST char *inbuf = (ICONV_CONST char *) string;
2155                         size_t inlen = len + 1;
2157                         char *outbuf = out_buffer;
2158                         size_t outlen = sizeof(out_buffer);
2160                         size_t ret;
2162                         ret = iconv(opt_iconv_out, &inbuf, &inlen, &outbuf, &outlen);
2163                         if (ret != (size_t) -1) {
2164                                 string = out_buffer;
2165                                 len = sizeof(out_buffer) - outlen;
2166                         }
2167                 }
2169                 waddnstr(view->win, string, len);
2170         }
2171         if (trimmed && use_tilde) {
2172                 set_view_attr(view, LINE_DELIMITER);
2173                 waddch(view->win, '~');
2174                 col++;
2175         }
2177         return col;
2180 static int
2181 draw_space(struct view *view, enum line_type type, int max, int spaces)
2183         static char space[] = "                    ";
2184         int col = 0;
2186         spaces = MIN(max, spaces);
2188         while (spaces > 0) {
2189                 int len = MIN(spaces, sizeof(space) - 1);
2191                 col += draw_chars(view, type, space, len, FALSE);
2192                 spaces -= len;
2193         }
2195         return col;
2198 static bool
2199 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
2201         view->col += draw_chars(view, type, string, view->width + view->yoffset - view->col, trim);
2202         return view->width + view->yoffset <= view->col;
2205 static bool
2206 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
2208         size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2209         int max = view->width + view->yoffset - view->col;
2210         int i;
2212         if (max < size)
2213                 size = max;
2215         set_view_attr(view, type);
2216         /* Using waddch() instead of waddnstr() ensures that
2217          * they'll be rendered correctly for the cursor line. */
2218         for (i = skip; i < size; i++)
2219                 waddch(view->win, graphic[i]);
2221         view->col += size;
2222         if (size < max && skip <= size)
2223                 waddch(view->win, ' ');
2224         view->col++;
2226         return view->width + view->yoffset <= view->col;
2229 static bool
2230 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
2232         int max = MIN(view->width + view->yoffset - view->col, len);
2233         int col;
2235         if (text)
2236                 col = draw_chars(view, type, text, max - 1, trim);
2237         else
2238                 col = draw_space(view, type, max - 1, max - 1);
2240         view->col += col;
2241         view->col += draw_space(view, LINE_DEFAULT, max - col, max - col);
2242         return view->width + view->yoffset <= view->col;
2245 static bool
2246 draw_date(struct view *view, struct time *time)
2248         const char *date = time && time->sec ? mkdate(time) : "";
2249         int cols = opt_date == DATE_SHORT ? DATE_SHORT_COLS : DATE_COLS;
2251         return draw_field(view, LINE_DATE, date, cols, FALSE);
2254 static bool
2255 draw_author(struct view *view, const char *author)
2257         bool trim = opt_author_cols == 0 || opt_author_cols > 5;
2258         bool abbreviate = opt_author == AUTHOR_ABBREVIATED || !trim;
2260         if (abbreviate && author)
2261                 author = get_author_initials(author);
2263         return draw_field(view, LINE_AUTHOR, author, opt_author_cols, trim);
2266 static bool
2267 draw_mode(struct view *view, mode_t mode)
2269         const char *str;
2271         if (S_ISDIR(mode))
2272                 str = "drwxr-xr-x";
2273         else if (S_ISLNK(mode))
2274                 str = "lrwxrwxrwx";
2275         else if (S_ISGITLINK(mode))
2276                 str = "m---------";
2277         else if (S_ISREG(mode) && mode & S_IXUSR)
2278                 str = "-rwxr-xr-x";
2279         else if (S_ISREG(mode))
2280                 str = "-rw-r--r--";
2281         else
2282                 str = "----------";
2284         return draw_field(view, LINE_MODE, str, STRING_SIZE("-rw-r--r-- "), FALSE);
2287 static bool
2288 draw_lineno(struct view *view, unsigned int lineno)
2290         char number[10];
2291         int digits3 = view->digits < 3 ? 3 : view->digits;
2292         int max = MIN(view->width + view->yoffset - view->col, digits3);
2293         char *text = NULL;
2295         lineno += view->offset + 1;
2296         if (lineno == 1 || (lineno % opt_num_interval) == 0) {
2297                 static char fmt[] = "%1ld";
2299                 fmt[1] = '0' + (view->digits <= 9 ? digits3 : 1);
2300                 if (string_format(number, fmt, lineno))
2301                         text = number;
2302         }
2303         if (text)
2304                 view->col += draw_chars(view, LINE_LINE_NUMBER, text, max, TRUE);
2305         else
2306                 view->col += draw_space(view, LINE_LINE_NUMBER, max, digits3);
2307         return draw_graphic(view, LINE_DEFAULT, &line_graphics[LINE_GRAPHIC_VLINE], 1);
2310 static bool
2311 draw_view_line(struct view *view, unsigned int lineno)
2313         struct line *line;
2314         bool selected = (view->offset + lineno == view->lineno);
2316         assert(view_is_displayed(view));
2318         if (view->offset + lineno >= view->lines)
2319                 return FALSE;
2321         line = &view->line[view->offset + lineno];
2323         wmove(view->win, lineno, 0);
2324         if (line->cleareol)
2325                 wclrtoeol(view->win);
2326         view->col = 0;
2327         view->curline = line;
2328         view->curtype = LINE_NONE;
2329         line->selected = FALSE;
2330         line->dirty = line->cleareol = 0;
2332         if (selected) {
2333                 set_view_attr(view, LINE_CURSOR);
2334                 line->selected = TRUE;
2335                 view->ops->select(view, line);
2336         }
2338         return view->ops->draw(view, line, lineno);
2341 static void
2342 redraw_view_dirty(struct view *view)
2344         bool dirty = FALSE;
2345         int lineno;
2347         for (lineno = 0; lineno < view->height; lineno++) {
2348                 if (view->offset + lineno >= view->lines)
2349                         break;
2350                 if (!view->line[view->offset + lineno].dirty)
2351                         continue;
2352                 dirty = TRUE;
2353                 if (!draw_view_line(view, lineno))
2354                         break;
2355         }
2357         if (!dirty)
2358                 return;
2359         wnoutrefresh(view->win);
2362 static void
2363 redraw_view_from(struct view *view, int lineno)
2365         assert(0 <= lineno && lineno < view->height);
2367         for (; lineno < view->height; lineno++) {
2368                 if (!draw_view_line(view, lineno))
2369                         break;
2370         }
2372         wnoutrefresh(view->win);
2375 static void
2376 redraw_view(struct view *view)
2378         werase(view->win);
2379         redraw_view_from(view, 0);
2383 static void
2384 update_view_title(struct view *view)
2386         char buf[SIZEOF_STR];
2387         char state[SIZEOF_STR];
2388         size_t bufpos = 0, statelen = 0;
2390         assert(view_is_displayed(view));
2392         if (view != VIEW(REQ_VIEW_STATUS) && view->lines) {
2393                 unsigned int view_lines = view->offset + view->height;
2394                 unsigned int lines = view->lines
2395                                    ? MIN(view_lines, view->lines) * 100 / view->lines
2396                                    : 0;
2398                 string_format_from(state, &statelen, " - %s %d of %d (%d%%)",
2399                                    view->ops->type,
2400                                    view->lineno + 1,
2401                                    view->lines,
2402                                    lines);
2404         }
2406         if (view->pipe) {
2407                 time_t secs = time(NULL) - view->start_time;
2409                 /* Three git seconds are a long time ... */
2410                 if (secs > 2)
2411                         string_format_from(state, &statelen, " loading %lds", secs);
2412         }
2414         string_format_from(buf, &bufpos, "[%s]", view->name);
2415         if (*view->ref && bufpos < view->width) {
2416                 size_t refsize = strlen(view->ref);
2417                 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2419                 if (minsize < view->width)
2420                         refsize = view->width - minsize + 7;
2421                 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2422         }
2424         if (statelen && bufpos < view->width) {
2425                 string_format_from(buf, &bufpos, "%s", state);
2426         }
2428         if (view == display[current_view])
2429                 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2430         else
2431                 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2433         mvwaddnstr(view->title, 0, 0, buf, bufpos);
2434         wclrtoeol(view->title);
2435         wnoutrefresh(view->title);
2438 static int
2439 apply_step(double step, int value)
2441         if (step >= 1)
2442                 return (int) step;
2443         value *= step + 0.01;
2444         return value ? value : 1;
2447 static void
2448 resize_display(void)
2450         int offset, i;
2451         struct view *base = display[0];
2452         struct view *view = display[1] ? display[1] : display[0];
2454         /* Setup window dimensions */
2456         getmaxyx(stdscr, base->height, base->width);
2458         /* Make room for the status window. */
2459         base->height -= 1;
2461         if (view != base) {
2462                 /* Horizontal split. */
2463                 view->width   = base->width;
2464                 view->height  = apply_step(opt_scale_split_view, base->height);
2465                 view->height  = MAX(view->height, MIN_VIEW_HEIGHT);
2466                 view->height  = MIN(view->height, base->height - MIN_VIEW_HEIGHT);
2467                 base->height -= view->height;
2469                 /* Make room for the title bar. */
2470                 view->height -= 1;
2471         }
2473         /* Make room for the title bar. */
2474         base->height -= 1;
2476         offset = 0;
2478         foreach_displayed_view (view, i) {
2479                 if (!view->win) {
2480                         view->win = newwin(view->height, 0, offset, 0);
2481                         if (!view->win)
2482                                 die("Failed to create %s view", view->name);
2484                         scrollok(view->win, FALSE);
2486                         view->title = newwin(1, 0, offset + view->height, 0);
2487                         if (!view->title)
2488                                 die("Failed to create title window");
2490                 } else {
2491                         wresize(view->win, view->height, view->width);
2492                         mvwin(view->win,   offset, 0);
2493                         mvwin(view->title, offset + view->height, 0);
2494                 }
2496                 offset += view->height + 1;
2497         }
2500 static void
2501 redraw_display(bool clear)
2503         struct view *view;
2504         int i;
2506         foreach_displayed_view (view, i) {
2507                 if (clear)
2508                         wclear(view->win);
2509                 redraw_view(view);
2510                 update_view_title(view);
2511         }
2514 static void
2515 toggle_enum_option_do(unsigned int *opt, const char *help,
2516                       const struct enum_map *map, size_t size)
2518         *opt = (*opt + 1) % size;
2519         redraw_display(FALSE);
2520         report("Displaying %s %s", enum_name(map[*opt]), help);
2523 #define toggle_enum_option(opt, help, map) \
2524         toggle_enum_option_do(opt, help, map, ARRAY_SIZE(map))
2526 #define toggle_date() toggle_enum_option(&opt_date, "dates", date_map)
2527 #define toggle_author() toggle_enum_option(&opt_author, "author names", author_map)
2529 static void
2530 toggle_view_option(bool *option, const char *help)
2532         *option = !*option;
2533         redraw_display(FALSE);
2534         report("%sabling %s", *option ? "En" : "Dis", help);
2537 static void
2538 open_option_menu(void)
2540         const struct menu_item menu[] = {
2541                 { '.', "line numbers", &opt_line_number },
2542                 { 'D', "date display", &opt_date },
2543                 { 'A', "author display", &opt_author },
2544                 { 'g', "revision graph display", &opt_rev_graph },
2545                 { 'F', "reference display", &opt_show_refs },
2546                 { 0 }
2547         };
2548         int selected = 0;
2550         if (prompt_menu("Toggle option", menu, &selected)) {
2551                 if (menu[selected].data == &opt_date)
2552                         toggle_date();
2553                 else if (menu[selected].data == &opt_author)
2554                         toggle_author();
2555                 else
2556                         toggle_view_option(menu[selected].data, menu[selected].text);
2557         }
2560 static void
2561 maximize_view(struct view *view)
2563         memset(display, 0, sizeof(display));
2564         current_view = 0;
2565         display[current_view] = view;
2566         resize_display();
2567         redraw_display(FALSE);
2568         report("");
2572 /*
2573  * Navigation
2574  */
2576 static bool
2577 goto_view_line(struct view *view, unsigned long offset, unsigned long lineno)
2579         if (lineno >= view->lines)
2580                 lineno = view->lines > 0 ? view->lines - 1 : 0;
2582         if (offset > lineno || offset + view->height <= lineno) {
2583                 unsigned long half = view->height / 2;
2585                 if (lineno > half)
2586                         offset = lineno - half;
2587                 else
2588                         offset = 0;
2589         }
2591         if (offset != view->offset || lineno != view->lineno) {
2592                 view->offset = offset;
2593                 view->lineno = lineno;
2594                 return TRUE;
2595         }
2597         return FALSE;
2600 /* Scrolling backend */
2601 static void
2602 do_scroll_view(struct view *view, int lines)
2604         bool redraw_current_line = FALSE;
2606         /* The rendering expects the new offset. */
2607         view->offset += lines;
2609         assert(0 <= view->offset && view->offset < view->lines);
2610         assert(lines);
2612         /* Move current line into the view. */
2613         if (view->lineno < view->offset) {
2614                 view->lineno = view->offset;
2615                 redraw_current_line = TRUE;
2616         } else if (view->lineno >= view->offset + view->height) {
2617                 view->lineno = view->offset + view->height - 1;
2618                 redraw_current_line = TRUE;
2619         }
2621         assert(view->offset <= view->lineno && view->lineno < view->lines);
2623         /* Redraw the whole screen if scrolling is pointless. */
2624         if (view->height < ABS(lines)) {
2625                 redraw_view(view);
2627         } else {
2628                 int line = lines > 0 ? view->height - lines : 0;
2629                 int end = line + ABS(lines);
2631                 scrollok(view->win, TRUE);
2632                 wscrl(view->win, lines);
2633                 scrollok(view->win, FALSE);
2635                 while (line < end && draw_view_line(view, line))
2636                         line++;
2638                 if (redraw_current_line)
2639                         draw_view_line(view, view->lineno - view->offset);
2640                 wnoutrefresh(view->win);
2641         }
2643         view->has_scrolled = TRUE;
2644         report("");
2647 /* Scroll frontend */
2648 static void
2649 scroll_view(struct view *view, enum request request)
2651         int lines = 1;
2653         assert(view_is_displayed(view));
2655         switch (request) {
2656         case REQ_SCROLL_LEFT:
2657                 if (view->yoffset == 0) {
2658                         report("Cannot scroll beyond the first column");
2659                         return;
2660                 }
2661                 if (view->yoffset <= apply_step(opt_hscroll, view->width))
2662                         view->yoffset = 0;
2663                 else
2664                         view->yoffset -= apply_step(opt_hscroll, view->width);
2665                 redraw_view_from(view, 0);
2666                 report("");
2667                 return;
2668         case REQ_SCROLL_RIGHT:
2669                 view->yoffset += apply_step(opt_hscroll, view->width);
2670                 redraw_view(view);
2671                 report("");
2672                 return;
2673         case REQ_SCROLL_PAGE_DOWN:
2674                 lines = view->height;
2675         case REQ_SCROLL_LINE_DOWN:
2676                 if (view->offset + lines > view->lines)
2677                         lines = view->lines - view->offset;
2679                 if (lines == 0 || view->offset + view->height >= view->lines) {
2680                         report("Cannot scroll beyond the last line");
2681                         return;
2682                 }
2683                 break;
2685         case REQ_SCROLL_PAGE_UP:
2686                 lines = view->height;
2687         case REQ_SCROLL_LINE_UP:
2688                 if (lines > view->offset)
2689                         lines = view->offset;
2691                 if (lines == 0) {
2692                         report("Cannot scroll beyond the first line");
2693                         return;
2694                 }
2696                 lines = -lines;
2697                 break;
2699         default:
2700                 die("request %d not handled in switch", request);
2701         }
2703         do_scroll_view(view, lines);
2706 /* Cursor moving */
2707 static void
2708 move_view(struct view *view, enum request request)
2710         int scroll_steps = 0;
2711         int steps;
2713         switch (request) {
2714         case REQ_MOVE_FIRST_LINE:
2715                 steps = -view->lineno;
2716                 break;
2718         case REQ_MOVE_LAST_LINE:
2719                 steps = view->lines - view->lineno - 1;
2720                 break;
2722         case REQ_MOVE_PAGE_UP:
2723                 steps = view->height > view->lineno
2724                       ? -view->lineno : -view->height;
2725                 break;
2727         case REQ_MOVE_PAGE_DOWN:
2728                 steps = view->lineno + view->height >= view->lines
2729                       ? view->lines - view->lineno - 1 : view->height;
2730                 break;
2732         case REQ_MOVE_UP:
2733                 steps = -1;
2734                 break;
2736         case REQ_MOVE_DOWN:
2737                 steps = 1;
2738                 break;
2740         default:
2741                 die("request %d not handled in switch", request);
2742         }
2744         if (steps <= 0 && view->lineno == 0) {
2745                 report("Cannot move beyond the first line");
2746                 return;
2748         } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2749                 report("Cannot move beyond the last line");
2750                 return;
2751         }
2753         /* Move the current line */
2754         view->lineno += steps;
2755         assert(0 <= view->lineno && view->lineno < view->lines);
2757         /* Check whether the view needs to be scrolled */
2758         if (view->lineno < view->offset ||
2759             view->lineno >= view->offset + view->height) {
2760                 scroll_steps = steps;
2761                 if (steps < 0 && -steps > view->offset) {
2762                         scroll_steps = -view->offset;
2764                 } else if (steps > 0) {
2765                         if (view->lineno == view->lines - 1 &&
2766                             view->lines > view->height) {
2767                                 scroll_steps = view->lines - view->offset - 1;
2768                                 if (scroll_steps >= view->height)
2769                                         scroll_steps -= view->height - 1;
2770                         }
2771                 }
2772         }
2774         if (!view_is_displayed(view)) {
2775                 view->offset += scroll_steps;
2776                 assert(0 <= view->offset && view->offset < view->lines);
2777                 view->ops->select(view, &view->line[view->lineno]);
2778                 return;
2779         }
2781         /* Repaint the old "current" line if we be scrolling */
2782         if (ABS(steps) < view->height)
2783                 draw_view_line(view, view->lineno - steps - view->offset);
2785         if (scroll_steps) {
2786                 do_scroll_view(view, scroll_steps);
2787                 return;
2788         }
2790         /* Draw the current line */
2791         draw_view_line(view, view->lineno - view->offset);
2793         wnoutrefresh(view->win);
2794         report("");
2798 /*
2799  * Searching
2800  */
2802 static void search_view(struct view *view, enum request request);
2804 static bool
2805 grep_text(struct view *view, const char *text[])
2807         regmatch_t pmatch;
2808         size_t i;
2810         for (i = 0; text[i]; i++)
2811                 if (*text[i] &&
2812                     regexec(view->regex, text[i], 1, &pmatch, 0) != REG_NOMATCH)
2813                         return TRUE;
2814         return FALSE;
2817 static void
2818 select_view_line(struct view *view, unsigned long lineno)
2820         unsigned long old_lineno = view->lineno;
2821         unsigned long old_offset = view->offset;
2823         if (goto_view_line(view, view->offset, lineno)) {
2824                 if (view_is_displayed(view)) {
2825                         if (old_offset != view->offset) {
2826                                 redraw_view(view);
2827                         } else {
2828                                 draw_view_line(view, old_lineno - view->offset);
2829                                 draw_view_line(view, view->lineno - view->offset);
2830                                 wnoutrefresh(view->win);
2831                         }
2832                 } else {
2833                         view->ops->select(view, &view->line[view->lineno]);
2834                 }
2835         }
2838 static void
2839 find_next(struct view *view, enum request request)
2841         unsigned long lineno = view->lineno;
2842         int direction;
2844         if (!*view->grep) {
2845                 if (!*opt_search)
2846                         report("No previous search");
2847                 else
2848                         search_view(view, request);
2849                 return;
2850         }
2852         switch (request) {
2853         case REQ_SEARCH:
2854         case REQ_FIND_NEXT:
2855                 direction = 1;
2856                 break;
2858         case REQ_SEARCH_BACK:
2859         case REQ_FIND_PREV:
2860                 direction = -1;
2861                 break;
2863         default:
2864                 return;
2865         }
2867         if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
2868                 lineno += direction;
2870         /* Note, lineno is unsigned long so will wrap around in which case it
2871          * will become bigger than view->lines. */
2872         for (; lineno < view->lines; lineno += direction) {
2873                 if (view->ops->grep(view, &view->line[lineno])) {
2874                         select_view_line(view, lineno);
2875                         report("Line %ld matches '%s'", lineno + 1, view->grep);
2876                         return;
2877                 }
2878         }
2880         report("No match found for '%s'", view->grep);
2883 static void
2884 search_view(struct view *view, enum request request)
2886         int regex_err;
2888         if (view->regex) {
2889                 regfree(view->regex);
2890                 *view->grep = 0;
2891         } else {
2892                 view->regex = calloc(1, sizeof(*view->regex));
2893                 if (!view->regex)
2894                         return;
2895         }
2897         regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
2898         if (regex_err != 0) {
2899                 char buf[SIZEOF_STR] = "unknown error";
2901                 regerror(regex_err, view->regex, buf, sizeof(buf));
2902                 report("Search failed: %s", buf);
2903                 return;
2904         }
2906         string_copy(view->grep, opt_search);
2908         find_next(view, request);
2911 /*
2912  * Incremental updating
2913  */
2915 static void
2916 reset_view(struct view *view)
2918         int i;
2920         for (i = 0; i < view->lines; i++)
2921                 free(view->line[i].data);
2922         free(view->line);
2924         view->p_offset = view->offset;
2925         view->p_yoffset = view->yoffset;
2926         view->p_lineno = view->lineno;
2928         view->line = NULL;
2929         view->offset = 0;
2930         view->yoffset = 0;
2931         view->lines  = 0;
2932         view->lineno = 0;
2933         view->vid[0] = 0;
2934         view->update_secs = 0;
2937 static void
2938 free_argv(const char *argv[])
2940         int argc;
2942         for (argc = 0; argv[argc]; argc++)
2943                 free((void *) argv[argc]);
2946 static const char *
2947 format_arg(const char *name)
2949         static struct {
2950                 const char *name;
2951                 size_t namelen;
2952                 const char *value;
2953                 const char *value_if_empty;
2954         } vars[] = {
2955 #define FORMAT_VAR(name, value, value_if_empty) \
2956         { name, STRING_SIZE(name), value, value_if_empty }
2957                 FORMAT_VAR("%(directory)",      opt_path,       ""),
2958                 FORMAT_VAR("%(file)",           opt_file,       ""),
2959                 FORMAT_VAR("%(ref)",            opt_ref,        "HEAD"),
2960                 FORMAT_VAR("%(head)",           ref_head,       ""),
2961                 FORMAT_VAR("%(commit)",         ref_commit,     ""),
2962                 FORMAT_VAR("%(blob)",           ref_blob,       ""),
2963         };
2964         int i;
2966         for (i = 0; i < ARRAY_SIZE(vars); i++)
2967                 if (!strncmp(name, vars[i].name, vars[i].namelen))
2968                         return *vars[i].value ? vars[i].value : vars[i].value_if_empty;
2970         return NULL;
2972 static bool
2973 format_argv(const char *dst_argv[], const char *src_argv[], enum format_flags flags)
2975         char buf[SIZEOF_STR];
2976         int argc;
2977         bool noreplace = flags == FORMAT_NONE;
2979         free_argv(dst_argv);
2981         for (argc = 0; src_argv[argc]; argc++) {
2982                 const char *arg = src_argv[argc];
2983                 size_t bufpos = 0;
2985                 while (arg) {
2986                         char *next = strstr(arg, "%(");
2987                         int len = next - arg;
2988                         const char *value;
2990                         if (!next || noreplace) {
2991                                 if (flags == FORMAT_DASH && !strcmp(arg, "--"))
2992                                         noreplace = TRUE;
2993                                 len = strlen(arg);
2994                                 value = "";
2996                         } else {
2997                                 value = format_arg(next);
2999                                 if (!value) {
3000                                         report("Unknown replacement: `%s`", next);
3001                                         return FALSE;
3002                                 }
3003                         }
3005                         if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
3006                                 return FALSE;
3008                         arg = next && !noreplace ? strchr(next, ')') + 1 : NULL;
3009                 }
3011                 dst_argv[argc] = strdup(buf);
3012                 if (!dst_argv[argc])
3013                         break;
3014         }
3016         dst_argv[argc] = NULL;
3018         return src_argv[argc] == NULL;
3021 static bool
3022 restore_view_position(struct view *view)
3024         if (!view->p_restore || (view->pipe && view->lines <= view->p_lineno))
3025                 return FALSE;
3027         /* Changing the view position cancels the restoring. */
3028         /* FIXME: Changing back to the first line is not detected. */
3029         if (view->offset != 0 || view->lineno != 0) {
3030                 view->p_restore = FALSE;
3031                 return FALSE;
3032         }
3034         if (goto_view_line(view, view->p_offset, view->p_lineno) &&
3035             view_is_displayed(view))
3036                 werase(view->win);
3038         view->yoffset = view->p_yoffset;
3039         view->p_restore = FALSE;
3041         return TRUE;
3044 static void
3045 end_update(struct view *view, bool force)
3047         if (!view->pipe)
3048                 return;
3049         while (!view->ops->read(view, NULL))
3050                 if (!force)
3051                         return;
3052         set_nonblocking_input(FALSE);
3053         if (force)
3054                 kill_io(view->pipe);
3055         done_io(view->pipe);
3056         view->pipe = NULL;
3059 static void
3060 setup_update(struct view *view, const char *vid)
3062         set_nonblocking_input(TRUE);
3063         reset_view(view);
3064         string_copy_rev(view->vid, vid);
3065         view->pipe = &view->io;
3066         view->start_time = time(NULL);
3069 static bool
3070 prepare_update(struct view *view, const char *argv[], const char *dir,
3071                enum format_flags flags)
3073         if (view->pipe)
3074                 end_update(view, TRUE);
3075         return init_io_rd(&view->io, argv, dir, flags);
3078 static bool
3079 prepare_update_file(struct view *view, const char *name)
3081         if (view->pipe)
3082                 end_update(view, TRUE);
3083         return io_open(&view->io, "%s/%s", opt_cdup[0] ? opt_cdup : ".", name);
3086 static bool
3087 begin_update(struct view *view, bool refresh)
3089         if (view->pipe)
3090                 end_update(view, TRUE);
3092         if (!refresh) {
3093                 if (view->ops->prepare) {
3094                         if (!view->ops->prepare(view))
3095                                 return FALSE;
3096                 } else if (!init_io_rd(&view->io, view->ops->argv, NULL, FORMAT_ALL)) {
3097                         return FALSE;
3098                 }
3100                 /* Put the current ref_* value to the view title ref
3101                  * member. This is needed by the blob view. Most other
3102                  * views sets it automatically after loading because the
3103                  * first line is a commit line. */
3104                 string_copy_rev(view->ref, view->id);
3105         }
3107         if (!start_io(&view->io))
3108                 return FALSE;
3110         setup_update(view, view->id);
3112         return TRUE;
3115 static bool
3116 update_view(struct view *view)
3118         char out_buffer[BUFSIZ * 2];
3119         char *line;
3120         /* Clear the view and redraw everything since the tree sorting
3121          * might have rearranged things. */
3122         bool redraw = view->lines == 0;
3123         bool can_read = TRUE;
3125         if (!view->pipe)
3126                 return TRUE;
3128         if (!io_can_read(view->pipe)) {
3129                 if (view->lines == 0 && view_is_displayed(view)) {
3130                         time_t secs = time(NULL) - view->start_time;
3132                         if (secs > 1 && secs > view->update_secs) {
3133                                 if (view->update_secs == 0)
3134                                         redraw_view(view);
3135                                 update_view_title(view);
3136                                 view->update_secs = secs;
3137                         }
3138                 }
3139                 return TRUE;
3140         }
3142         for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
3143                 if (opt_iconv_in != ICONV_NONE) {
3144                         ICONV_CONST char *inbuf = line;
3145                         size_t inlen = strlen(line) + 1;
3147                         char *outbuf = out_buffer;
3148                         size_t outlen = sizeof(out_buffer);
3150                         size_t ret;
3152                         ret = iconv(opt_iconv_in, &inbuf, &inlen, &outbuf, &outlen);
3153                         if (ret != (size_t) -1)
3154                                 line = out_buffer;
3155                 }
3157                 if (!view->ops->read(view, line)) {
3158                         report("Allocation failure");
3159                         end_update(view, TRUE);
3160                         return FALSE;
3161                 }
3162         }
3164         {
3165                 unsigned long lines = view->lines;
3166                 int digits;
3168                 for (digits = 0; lines; digits++)
3169                         lines /= 10;
3171                 /* Keep the displayed view in sync with line number scaling. */
3172                 if (digits != view->digits) {
3173                         view->digits = digits;
3174                         if (opt_line_number || view == VIEW(REQ_VIEW_BLAME))
3175                                 redraw = TRUE;
3176                 }
3177         }
3179         if (io_error(view->pipe)) {
3180                 report("Failed to read: %s", io_strerror(view->pipe));
3181                 end_update(view, TRUE);
3183         } else if (io_eof(view->pipe)) {
3184                 report("");
3185                 end_update(view, FALSE);
3186         }
3188         if (restore_view_position(view))
3189                 redraw = TRUE;
3191         if (!view_is_displayed(view))
3192                 return TRUE;
3194         if (redraw)
3195                 redraw_view_from(view, 0);
3196         else
3197                 redraw_view_dirty(view);
3199         /* Update the title _after_ the redraw so that if the redraw picks up a
3200          * commit reference in view->ref it'll be available here. */
3201         update_view_title(view);
3202         return TRUE;
3205 DEFINE_ALLOCATOR(realloc_lines, struct line, 256)
3207 static struct line *
3208 add_line_data(struct view *view, void *data, enum line_type type)
3210         struct line *line;
3212         if (!realloc_lines(&view->line, view->lines, 1))
3213                 return NULL;
3215         line = &view->line[view->lines++];
3216         memset(line, 0, sizeof(*line));
3217         line->type = type;
3218         line->data = data;
3219         line->dirty = 1;
3221         return line;
3224 static struct line *
3225 add_line_text(struct view *view, const char *text, enum line_type type)
3227         char *data = text ? strdup(text) : NULL;
3229         return data ? add_line_data(view, data, type) : NULL;
3232 static struct line *
3233 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
3235         char buf[SIZEOF_STR];
3236         va_list args;
3238         va_start(args, fmt);
3239         if (vsnprintf(buf, sizeof(buf), fmt, args) >= sizeof(buf))
3240                 buf[0] = 0;
3241         va_end(args);
3243         return buf[0] ? add_line_text(view, buf, type) : NULL;
3246 /*
3247  * View opening
3248  */
3250 enum open_flags {
3251         OPEN_DEFAULT = 0,       /* Use default view switching. */
3252         OPEN_SPLIT = 1,         /* Split current view. */
3253         OPEN_RELOAD = 4,        /* Reload view even if it is the current. */
3254         OPEN_REFRESH = 16,      /* Refresh view using previous command. */
3255         OPEN_PREPARED = 32,     /* Open already prepared command. */
3256 };
3258 static void
3259 open_view(struct view *prev, enum request request, enum open_flags flags)
3261         bool split = !!(flags & OPEN_SPLIT);
3262         bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
3263         bool nomaximize = !!(flags & OPEN_REFRESH);
3264         struct view *view = VIEW(request);
3265         int nviews = displayed_views();
3266         struct view *base_view = display[0];
3268         if (view == prev && nviews == 1 && !reload) {
3269                 report("Already in %s view", view->name);
3270                 return;
3271         }
3273         if (view->git_dir && !opt_git_dir[0]) {
3274                 report("The %s view is disabled in pager view", view->name);
3275                 return;
3276         }
3278         if (split) {
3279                 display[1] = view;
3280                 current_view = 1;
3281         } else if (!nomaximize) {
3282                 /* Maximize the current view. */
3283                 memset(display, 0, sizeof(display));
3284                 current_view = 0;
3285                 display[current_view] = view;
3286         }
3288         /* No parent signals that this is the first loaded view. */
3289         if (prev && view != prev) {
3290                 view->parent = prev;
3291         }
3293         /* Resize the view when switching between split- and full-screen,
3294          * or when switching between two different full-screen views. */
3295         if (nviews != displayed_views() ||
3296             (nviews == 1 && base_view != display[0]))
3297                 resize_display();
3299         if (view->ops->open) {
3300                 if (view->pipe)
3301                         end_update(view, TRUE);
3302                 if (!view->ops->open(view)) {
3303                         report("Failed to load %s view", view->name);
3304                         return;
3305                 }
3306                 restore_view_position(view);
3308         } else if ((reload || strcmp(view->vid, view->id)) &&
3309                    !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
3310                 report("Failed to load %s view", view->name);
3311                 return;
3312         }
3314         if (split && prev->lineno - prev->offset >= prev->height) {
3315                 /* Take the title line into account. */
3316                 int lines = prev->lineno - prev->offset - prev->height + 1;
3318                 /* Scroll the view that was split if the current line is
3319                  * outside the new limited view. */
3320                 do_scroll_view(prev, lines);
3321         }
3323         if (prev && view != prev && split && view_is_displayed(prev)) {
3324                 /* "Blur" the previous view. */
3325                 update_view_title(prev);
3326         }
3328         if (view->pipe && view->lines == 0) {
3329                 /* Clear the old view and let the incremental updating refill
3330                  * the screen. */
3331                 werase(view->win);
3332                 view->p_restore = flags & (OPEN_RELOAD | OPEN_REFRESH);
3333                 report("");
3334         } else if (view_is_displayed(view)) {
3335                 redraw_view(view);
3336                 report("");
3337         }
3340 static void
3341 open_external_viewer(const char *argv[], const char *dir)
3343         def_prog_mode();           /* save current tty modes */
3344         endwin();                  /* restore original tty modes */
3345         run_io_fg(argv, dir);
3346         fprintf(stderr, "Press Enter to continue");
3347         getc(opt_tty);
3348         reset_prog_mode();
3349         redraw_display(TRUE);
3352 static void
3353 open_mergetool(const char *file)
3355         const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
3357         open_external_viewer(mergetool_argv, opt_cdup);
3360 static void
3361 open_editor(const char *file)
3363         const char *editor_argv[] = { "vi", file, NULL };
3364         const char *editor;
3366         editor = getenv("GIT_EDITOR");
3367         if (!editor && *opt_editor)
3368                 editor = opt_editor;
3369         if (!editor)
3370                 editor = getenv("VISUAL");
3371         if (!editor)
3372                 editor = getenv("EDITOR");
3373         if (!editor)
3374                 editor = "vi";
3376         editor_argv[0] = editor;
3377         open_external_viewer(editor_argv, opt_cdup);
3380 static void
3381 open_run_request(enum request request)
3383         struct run_request *req = get_run_request(request);
3384         const char *argv[ARRAY_SIZE(req->argv)] = { NULL };
3386         if (!req) {
3387                 report("Unknown run request");
3388                 return;
3389         }
3391         if (format_argv(argv, req->argv, FORMAT_ALL))
3392                 open_external_viewer(argv, NULL);
3393         free_argv(argv);
3396 /*
3397  * User request switch noodle
3398  */
3400 static int
3401 view_driver(struct view *view, enum request request)
3403         int i;
3405         if (request == REQ_NONE)
3406                 return TRUE;
3408         if (request > REQ_NONE) {
3409                 open_run_request(request);
3410                 /* FIXME: When all views can refresh always do this. */
3411                 if (view == VIEW(REQ_VIEW_STATUS) ||
3412                     view == VIEW(REQ_VIEW_MAIN) ||
3413                     view == VIEW(REQ_VIEW_LOG) ||
3414                     view == VIEW(REQ_VIEW_BRANCH) ||
3415                     view == VIEW(REQ_VIEW_STAGE))
3416                         request = REQ_REFRESH;
3417                 else
3418                         return TRUE;
3419         }
3421         if (view && view->lines) {
3422                 request = view->ops->request(view, request, &view->line[view->lineno]);
3423                 if (request == REQ_NONE)
3424                         return TRUE;
3425         }
3427         switch (request) {
3428         case REQ_MOVE_UP:
3429         case REQ_MOVE_DOWN:
3430         case REQ_MOVE_PAGE_UP:
3431         case REQ_MOVE_PAGE_DOWN:
3432         case REQ_MOVE_FIRST_LINE:
3433         case REQ_MOVE_LAST_LINE:
3434                 move_view(view, request);
3435                 break;
3437         case REQ_SCROLL_LEFT:
3438         case REQ_SCROLL_RIGHT:
3439         case REQ_SCROLL_LINE_DOWN:
3440         case REQ_SCROLL_LINE_UP:
3441         case REQ_SCROLL_PAGE_DOWN:
3442         case REQ_SCROLL_PAGE_UP:
3443                 scroll_view(view, request);
3444                 break;
3446         case REQ_VIEW_BLAME:
3447                 if (!opt_file[0]) {
3448                         report("No file chosen, press %s to open tree view",
3449                                get_key(view->keymap, REQ_VIEW_TREE));
3450                         break;
3451                 }
3452                 open_view(view, request, OPEN_DEFAULT);
3453                 break;
3455         case REQ_VIEW_BLOB:
3456                 if (!ref_blob[0]) {
3457                         report("No file chosen, press %s to open tree view",
3458                                get_key(view->keymap, REQ_VIEW_TREE));
3459                         break;
3460                 }
3461                 open_view(view, request, OPEN_DEFAULT);
3462                 break;
3464         case REQ_VIEW_PAGER:
3465                 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3466                         report("No pager content, press %s to run command from prompt",
3467                                get_key(view->keymap, REQ_PROMPT));
3468                         break;
3469                 }
3470                 open_view(view, request, OPEN_DEFAULT);
3471                 break;
3473         case REQ_VIEW_STAGE:
3474                 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3475                         report("No stage content, press %s to open the status view and choose file",
3476                                get_key(view->keymap, REQ_VIEW_STATUS));
3477                         break;
3478                 }
3479                 open_view(view, request, OPEN_DEFAULT);
3480                 break;
3482         case REQ_VIEW_STATUS:
3483                 if (opt_is_inside_work_tree == FALSE) {
3484                         report("The status view requires a working tree");
3485                         break;
3486                 }
3487                 open_view(view, request, OPEN_DEFAULT);
3488                 break;
3490         case REQ_VIEW_MAIN:
3491         case REQ_VIEW_DIFF:
3492         case REQ_VIEW_LOG:
3493         case REQ_VIEW_TREE:
3494         case REQ_VIEW_HELP:
3495         case REQ_VIEW_BRANCH:
3496                 open_view(view, request, OPEN_DEFAULT);
3497                 break;
3499         case REQ_NEXT:
3500         case REQ_PREVIOUS:
3501                 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3503                 if ((view == VIEW(REQ_VIEW_DIFF) &&
3504                      view->parent == VIEW(REQ_VIEW_MAIN)) ||
3505                    (view == VIEW(REQ_VIEW_DIFF) &&
3506                      view->parent == VIEW(REQ_VIEW_BLAME)) ||
3507                    (view == VIEW(REQ_VIEW_STAGE) &&
3508                      view->parent == VIEW(REQ_VIEW_STATUS)) ||
3509                    (view == VIEW(REQ_VIEW_BLOB) &&
3510                      view->parent == VIEW(REQ_VIEW_TREE)) ||
3511                    (view == VIEW(REQ_VIEW_MAIN) &&
3512                      view->parent == VIEW(REQ_VIEW_BRANCH))) {
3513                         int line;
3515                         view = view->parent;
3516                         line = view->lineno;
3517                         move_view(view, request);
3518                         if (view_is_displayed(view))
3519                                 update_view_title(view);
3520                         if (line != view->lineno)
3521                                 view->ops->request(view, REQ_ENTER,
3522                                                    &view->line[view->lineno]);
3524                 } else {
3525                         move_view(view, request);
3526                 }
3527                 break;
3529         case REQ_VIEW_NEXT:
3530         {
3531                 int nviews = displayed_views();
3532                 int next_view = (current_view + 1) % nviews;
3534                 if (next_view == current_view) {
3535                         report("Only one view is displayed");
3536                         break;
3537                 }
3539                 current_view = next_view;
3540                 /* Blur out the title of the previous view. */
3541                 update_view_title(view);
3542                 report("");
3543                 break;
3544         }
3545         case REQ_REFRESH:
3546                 report("Refreshing is not yet supported for the %s view", view->name);
3547                 break;
3549         case REQ_MAXIMIZE:
3550                 if (displayed_views() == 2)
3551                         maximize_view(view);
3552                 break;
3554         case REQ_OPTIONS:
3555                 open_option_menu();
3556                 break;
3558         case REQ_TOGGLE_LINENO:
3559                 toggle_view_option(&opt_line_number, "line numbers");
3560                 break;
3562         case REQ_TOGGLE_DATE:
3563                 toggle_date();
3564                 break;
3566         case REQ_TOGGLE_AUTHOR:
3567                 toggle_author();
3568                 break;
3570         case REQ_TOGGLE_REV_GRAPH:
3571                 toggle_view_option(&opt_rev_graph, "revision graph display");
3572                 break;
3574         case REQ_TOGGLE_REFS:
3575                 toggle_view_option(&opt_show_refs, "reference display");
3576                 break;
3578         case REQ_TOGGLE_SORT_FIELD:
3579         case REQ_TOGGLE_SORT_ORDER:
3580                 report("Sorting is not yet supported for the %s view", view->name);
3581                 break;
3583         case REQ_SEARCH:
3584         case REQ_SEARCH_BACK:
3585                 search_view(view, request);
3586                 break;
3588         case REQ_FIND_NEXT:
3589         case REQ_FIND_PREV:
3590                 find_next(view, request);
3591                 break;
3593         case REQ_STOP_LOADING:
3594                 for (i = 0; i < ARRAY_SIZE(views); i++) {
3595                         view = &views[i];
3596                         if (view->pipe)
3597                                 report("Stopped loading the %s view", view->name),
3598                         end_update(view, TRUE);
3599                 }
3600                 break;
3602         case REQ_SHOW_VERSION:
3603                 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3604                 return TRUE;
3606         case REQ_SCREEN_REDRAW:
3607                 redraw_display(TRUE);
3608                 break;
3610         case REQ_EDIT:
3611                 report("Nothing to edit");
3612                 break;
3614         case REQ_ENTER:
3615                 report("Nothing to enter");
3616                 break;
3618         case REQ_VIEW_CLOSE:
3619                 /* XXX: Mark closed views by letting view->parent point to the
3620                  * view itself. Parents to closed view should never be
3621                  * followed. */
3622                 if (view->parent &&
3623                     view->parent->parent != view->parent) {
3624                         maximize_view(view->parent);
3625                         view->parent = view;
3626                         break;
3627                 }
3628                 /* Fall-through */
3629         case REQ_QUIT:
3630                 return FALSE;
3632         default:
3633                 report("Unknown key, press %s for help",
3634                        get_key(view->keymap, REQ_VIEW_HELP));
3635                 return TRUE;
3636         }
3638         return TRUE;
3642 /*
3643  * View backend utilities
3644  */
3646 enum sort_field {
3647         ORDERBY_NAME,
3648         ORDERBY_DATE,
3649         ORDERBY_AUTHOR,
3650 };
3652 struct sort_state {
3653         const enum sort_field *fields;
3654         size_t size, current;
3655         bool reverse;
3656 };
3658 #define SORT_STATE(fields) { fields, ARRAY_SIZE(fields), 0 }
3659 #define get_sort_field(state) ((state).fields[(state).current])
3660 #define sort_order(state, result) ((state).reverse ? -(result) : (result))
3662 static void
3663 sort_view(struct view *view, enum request request, struct sort_state *state,
3664           int (*compare)(const void *, const void *))
3666         switch (request) {
3667         case REQ_TOGGLE_SORT_FIELD:
3668                 state->current = (state->current + 1) % state->size;
3669                 break;
3671         case REQ_TOGGLE_SORT_ORDER:
3672                 state->reverse = !state->reverse;
3673                 break;
3674         default:
3675                 die("Not a sort request");
3676         }
3678         qsort(view->line, view->lines, sizeof(*view->line), compare);
3679         redraw_view(view);
3682 DEFINE_ALLOCATOR(realloc_authors, const char *, 256)
3684 /* Small author cache to reduce memory consumption. It uses binary
3685  * search to lookup or find place to position new entries. No entries
3686  * are ever freed. */
3687 static const char *
3688 get_author(const char *name)
3690         static const char **authors;
3691         static size_t authors_size;
3692         int from = 0, to = authors_size - 1;
3694         while (from <= to) {
3695                 size_t pos = (to + from) / 2;
3696                 int cmp = strcmp(name, authors[pos]);
3698                 if (!cmp)
3699                         return authors[pos];
3701                 if (cmp < 0)
3702                         to = pos - 1;
3703                 else
3704                         from = pos + 1;
3705         }
3707         if (!realloc_authors(&authors, authors_size, 1))
3708                 return NULL;
3709         name = strdup(name);
3710         if (!name)
3711                 return NULL;
3713         memmove(authors + from + 1, authors + from, (authors_size - from) * sizeof(*authors));
3714         authors[from] = name;
3715         authors_size++;
3717         return name;
3720 static void
3721 parse_timesec(struct time *time, const char *sec)
3723         time->sec = (time_t) atol(sec);
3726 static void
3727 parse_timezone(struct time *time, const char *zone)
3729         long tz;
3731         tz  = ('0' - zone[1]) * 60 * 60 * 10;
3732         tz += ('0' - zone[2]) * 60 * 60;
3733         tz += ('0' - zone[3]) * 60;
3734         tz += ('0' - zone[4]);
3736         if (zone[0] == '-')
3737                 tz = -tz;
3739         time->tz = tz;
3740         time->sec -= tz;
3743 /* Parse author lines where the name may be empty:
3744  *      author  <email@address.tld> 1138474660 +0100
3745  */
3746 static void
3747 parse_author_line(char *ident, const char **author, struct time *time)
3749         char *nameend = strchr(ident, '<');
3750         char *emailend = strchr(ident, '>');
3752         if (nameend && emailend)
3753                 *nameend = *emailend = 0;
3754         ident = chomp_string(ident);
3755         if (!*ident) {
3756                 if (nameend)
3757                         ident = chomp_string(nameend + 1);
3758                 if (!*ident)
3759                         ident = "Unknown";
3760         }
3762         *author = get_author(ident);
3764         /* Parse epoch and timezone */
3765         if (emailend && emailend[1] == ' ') {
3766                 char *secs = emailend + 2;
3767                 char *zone = strchr(secs, ' ');
3769                 parse_timesec(time, secs);
3771                 if (zone && strlen(zone) == STRING_SIZE(" +0700"))
3772                         parse_timezone(time, zone + 1);
3773         }
3776 static bool
3777 open_commit_parent_menu(char buf[SIZEOF_STR], int *parents)
3779         char rev[SIZEOF_REV];
3780         const char *revlist_argv[] = {
3781                 "git", "log", "--no-color", "-1", "--pretty=format:%s", rev, NULL
3782         };
3783         struct menu_item *items;
3784         char text[SIZEOF_STR];
3785         bool ok = TRUE;
3786         int i;
3788         items = calloc(*parents + 1, sizeof(*items));
3789         if (!items)
3790                 return FALSE;
3792         for (i = 0; i < *parents; i++) {
3793                 string_copy_rev(rev, &buf[SIZEOF_REV * i]);
3794                 if (!run_io_buf(revlist_argv, text, sizeof(text)) ||
3795                     !(items[i].text = strdup(text))) {
3796                         ok = FALSE;
3797                         break;
3798                 }
3799         }
3801         if (ok) {
3802                 *parents = 0;
3803                 ok = prompt_menu("Select parent", items, parents);
3804         }
3805         for (i = 0; items[i].text; i++)
3806                 free((char *) items[i].text);
3807         free(items);
3808         return ok;
3811 static bool
3812 select_commit_parent(const char *id, char rev[SIZEOF_REV], const char *path)
3814         char buf[SIZEOF_STR * 4];
3815         const char *revlist_argv[] = {
3816                 "git", "log", "--no-color", "-1",
3817                         "--pretty=format:%P", id, "--", path, NULL
3818         };
3819         int parents;
3821         if (!run_io_buf(revlist_argv, buf, sizeof(buf)) ||
3822             (parents = strlen(buf) / 40) < 0) {
3823                 report("Failed to get parent information");
3824                 return FALSE;
3826         } else if (parents == 0) {
3827                 if (path)
3828                         report("Path '%s' does not exist in the parent", path);
3829                 else
3830                         report("The selected commit has no parents");
3831                 return FALSE;
3832         }
3834         if (parents > 1 && !open_commit_parent_menu(buf, &parents))
3835                 return FALSE;
3837         string_copy_rev(rev, &buf[41 * parents]);
3838         return TRUE;
3841 /*
3842  * Pager backend
3843  */
3845 static bool
3846 pager_draw(struct view *view, struct line *line, unsigned int lineno)
3848         char text[SIZEOF_STR];
3850         if (opt_line_number && draw_lineno(view, lineno))
3851                 return TRUE;
3853         string_expand(text, sizeof(text), line->data, opt_tab_size);
3854         draw_text(view, line->type, text, TRUE);
3855         return TRUE;
3858 static bool
3859 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
3861         const char *describe_argv[] = { "git", "describe", commit_id, NULL };
3862         char ref[SIZEOF_STR];
3864         if (!run_io_buf(describe_argv, ref, sizeof(ref)) || !*ref)
3865                 return TRUE;
3867         /* This is the only fatal call, since it can "corrupt" the buffer. */
3868         if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
3869                 return FALSE;
3871         return TRUE;
3874 static void
3875 add_pager_refs(struct view *view, struct line *line)
3877         char buf[SIZEOF_STR];
3878         char *commit_id = (char *)line->data + STRING_SIZE("commit ");
3879         struct ref_list *list;
3880         size_t bufpos = 0, i;
3881         const char *sep = "Refs: ";
3882         bool is_tag = FALSE;
3884         assert(line->type == LINE_COMMIT);
3886         list = get_ref_list(commit_id);
3887         if (!list) {
3888                 if (view == VIEW(REQ_VIEW_DIFF))
3889                         goto try_add_describe_ref;
3890                 return;
3891         }
3893         for (i = 0; i < list->size; i++) {
3894                 struct ref *ref = list->refs[i];
3895                 const char *fmt = ref->tag    ? "%s[%s]" :
3896                                   ref->remote ? "%s<%s>" : "%s%s";
3898                 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
3899                         return;
3900                 sep = ", ";
3901                 if (ref->tag)
3902                         is_tag = TRUE;
3903         }
3905         if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
3906 try_add_describe_ref:
3907                 /* Add <tag>-g<commit_id> "fake" reference. */
3908                 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
3909                         return;
3910         }
3912         if (bufpos == 0)
3913                 return;
3915         add_line_text(view, buf, LINE_PP_REFS);
3918 static bool
3919 pager_read(struct view *view, char *data)
3921         struct line *line;
3923         if (!data)
3924                 return TRUE;
3926         line = add_line_text(view, data, get_line_type(data));
3927         if (!line)
3928                 return FALSE;
3930         if (line->type == LINE_COMMIT &&
3931             (view == VIEW(REQ_VIEW_DIFF) ||
3932              view == VIEW(REQ_VIEW_LOG)))
3933                 add_pager_refs(view, line);
3935         return TRUE;
3938 static enum request
3939 pager_request(struct view *view, enum request request, struct line *line)
3941         int split = 0;
3943         if (request != REQ_ENTER)
3944                 return request;
3946         if (line->type == LINE_COMMIT &&
3947            (view == VIEW(REQ_VIEW_LOG) ||
3948             view == VIEW(REQ_VIEW_PAGER))) {
3949                 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
3950                 split = 1;
3951         }
3953         /* Always scroll the view even if it was split. That way
3954          * you can use Enter to scroll through the log view and
3955          * split open each commit diff. */
3956         scroll_view(view, REQ_SCROLL_LINE_DOWN);
3958         /* FIXME: A minor workaround. Scrolling the view will call report("")
3959          * but if we are scrolling a non-current view this won't properly
3960          * update the view title. */
3961         if (split)
3962                 update_view_title(view);
3964         return REQ_NONE;
3967 static bool
3968 pager_grep(struct view *view, struct line *line)
3970         const char *text[] = { line->data, NULL };
3972         return grep_text(view, text);
3975 static void
3976 pager_select(struct view *view, struct line *line)
3978         if (line->type == LINE_COMMIT) {
3979                 char *text = (char *)line->data + STRING_SIZE("commit ");
3981                 if (view != VIEW(REQ_VIEW_PAGER))
3982                         string_copy_rev(view->ref, text);
3983                 string_copy_rev(ref_commit, text);
3984         }
3987 static struct view_ops pager_ops = {
3988         "line",
3989         NULL,
3990         NULL,
3991         pager_read,
3992         pager_draw,
3993         pager_request,
3994         pager_grep,
3995         pager_select,
3996 };
3998 static const char *log_argv[SIZEOF_ARG] = {
3999         "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
4000 };
4002 static enum request
4003 log_request(struct view *view, enum request request, struct line *line)
4005         switch (request) {
4006         case REQ_REFRESH:
4007                 load_refs();
4008                 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
4009                 return REQ_NONE;
4010         default:
4011                 return pager_request(view, request, line);
4012         }
4015 static struct view_ops log_ops = {
4016         "line",
4017         log_argv,
4018         NULL,
4019         pager_read,
4020         pager_draw,
4021         log_request,
4022         pager_grep,
4023         pager_select,
4024 };
4026 static const char *diff_argv[SIZEOF_ARG] = {
4027         "git", "show", "--pretty=fuller", "--no-color", "--root",
4028                 "--patch-with-stat", "--find-copies-harder", "-C", "%(commit)", NULL
4029 };
4031 static struct view_ops diff_ops = {
4032         "line",
4033         diff_argv,
4034         NULL,
4035         pager_read,
4036         pager_draw,
4037         pager_request,
4038         pager_grep,
4039         pager_select,
4040 };
4042 /*
4043  * Help backend
4044  */
4046 static bool help_keymap_hidden[ARRAY_SIZE(keymap_table)];
4048 static bool
4049 help_open_keymap_title(struct view *view, enum keymap keymap)
4051         struct line *line;
4053         line = add_line_format(view, LINE_HELP_KEYMAP, "[%c] %s bindings",
4054                                help_keymap_hidden[keymap] ? '+' : '-',
4055                                enum_name(keymap_table[keymap]));
4056         if (line)
4057                 line->other = keymap;
4059         return help_keymap_hidden[keymap];
4062 static void
4063 help_open_keymap(struct view *view, enum keymap keymap)
4065         const char *group = NULL;
4066         char buf[SIZEOF_STR];
4067         size_t bufpos;
4068         bool add_title = TRUE;
4069         int i;
4071         for (i = 0; i < ARRAY_SIZE(req_info); i++) {
4072                 const char *key = NULL;
4074                 if (req_info[i].request == REQ_NONE)
4075                         continue;
4077                 if (!req_info[i].request) {
4078                         group = req_info[i].help;
4079                         continue;
4080                 }
4082                 key = get_keys(keymap, req_info[i].request, TRUE);
4083                 if (!key || !*key)
4084                         continue;
4086                 if (add_title && help_open_keymap_title(view, keymap))
4087                         return;
4088                 add_title = FALSE;
4090                 if (group) {
4091                         add_line_text(view, group, LINE_HELP_GROUP);
4092                         group = NULL;
4093                 }
4095                 add_line_format(view, LINE_DEFAULT, "    %-25s %-20s %s", key,
4096                                 enum_name(req_info[i]), req_info[i].help);
4097         }
4099         group = "External commands:";
4101         for (i = 0; i < run_requests; i++) {
4102                 struct run_request *req = get_run_request(REQ_NONE + i + 1);
4103                 const char *key;
4104                 int argc;
4106                 if (!req || req->keymap != keymap)
4107                         continue;
4109                 key = get_key_name(req->key);
4110                 if (!*key)
4111                         key = "(no key defined)";
4113                 if (add_title && help_open_keymap_title(view, keymap))
4114                         return;
4115                 if (group) {
4116                         add_line_text(view, group, LINE_HELP_GROUP);
4117                         group = NULL;
4118                 }
4120                 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
4121                         if (!string_format_from(buf, &bufpos, "%s%s",
4122                                                 argc ? " " : "", req->argv[argc]))
4123                                 return;
4125                 add_line_format(view, LINE_DEFAULT, "    %-25s `%s`", key, buf);
4126         }
4129 static bool
4130 help_open(struct view *view)
4132         enum keymap keymap;
4134         reset_view(view);
4135         add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
4136         add_line_text(view, "", LINE_DEFAULT);
4138         for (keymap = 0; keymap < ARRAY_SIZE(keymap_table); keymap++)
4139                 help_open_keymap(view, keymap);
4141         return TRUE;
4144 static enum request
4145 help_request(struct view *view, enum request request, struct line *line)
4147         switch (request) {
4148         case REQ_ENTER:
4149                 if (line->type == LINE_HELP_KEYMAP) {
4150                         help_keymap_hidden[line->other] =
4151                                 !help_keymap_hidden[line->other];
4152                         view->p_restore = TRUE;
4153                         open_view(view, REQ_VIEW_HELP, OPEN_REFRESH);
4154                 }
4156                 return REQ_NONE;
4157         default:
4158                 return pager_request(view, request, line);
4159         }
4162 static struct view_ops help_ops = {
4163         "line",
4164         NULL,
4165         help_open,
4166         NULL,
4167         pager_draw,
4168         help_request,
4169         pager_grep,
4170         pager_select,
4171 };
4174 /*
4175  * Tree backend
4176  */
4178 struct tree_stack_entry {
4179         struct tree_stack_entry *prev;  /* Entry below this in the stack */
4180         unsigned long lineno;           /* Line number to restore */
4181         char *name;                     /* Position of name in opt_path */
4182 };
4184 /* The top of the path stack. */
4185 static struct tree_stack_entry *tree_stack = NULL;
4186 unsigned long tree_lineno = 0;
4188 static void
4189 pop_tree_stack_entry(void)
4191         struct tree_stack_entry *entry = tree_stack;
4193         tree_lineno = entry->lineno;
4194         entry->name[0] = 0;
4195         tree_stack = entry->prev;
4196         free(entry);
4199 static void
4200 push_tree_stack_entry(const char *name, unsigned long lineno)
4202         struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
4203         size_t pathlen = strlen(opt_path);
4205         if (!entry)
4206                 return;
4208         entry->prev = tree_stack;
4209         entry->name = opt_path + pathlen;
4210         tree_stack = entry;
4212         if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
4213                 pop_tree_stack_entry();
4214                 return;
4215         }
4217         /* Move the current line to the first tree entry. */
4218         tree_lineno = 1;
4219         entry->lineno = lineno;
4222 /* Parse output from git-ls-tree(1):
4223  *
4224  * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
4225  */
4227 #define SIZEOF_TREE_ATTR \
4228         STRING_SIZE("100644 blob f931e1d229c3e185caad4449bf5b66ed72462657\t")
4230 #define SIZEOF_TREE_MODE \
4231         STRING_SIZE("100644 ")
4233 #define TREE_ID_OFFSET \
4234         STRING_SIZE("100644 blob ")
4236 struct tree_entry {
4237         char id[SIZEOF_REV];
4238         mode_t mode;
4239         struct time time;               /* Date from the author ident. */
4240         const char *author;             /* Author of the commit. */
4241         char name[1];
4242 };
4244 static const char *
4245 tree_path(const struct line *line)
4247         return ((struct tree_entry *) line->data)->name;
4250 static int
4251 tree_compare_entry(const struct line *line1, const struct line *line2)
4253         if (line1->type != line2->type)
4254                 return line1->type == LINE_TREE_DIR ? -1 : 1;
4255         return strcmp(tree_path(line1), tree_path(line2));
4258 static const enum sort_field tree_sort_fields[] = {
4259         ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
4260 };
4261 static struct sort_state tree_sort_state = SORT_STATE(tree_sort_fields);
4263 static int
4264 tree_compare(const void *l1, const void *l2)
4266         const struct line *line1 = (const struct line *) l1;
4267         const struct line *line2 = (const struct line *) l2;
4268         const struct tree_entry *entry1 = ((const struct line *) l1)->data;
4269         const struct tree_entry *entry2 = ((const struct line *) l2)->data;
4271         if (line1->type == LINE_TREE_HEAD)
4272                 return -1;
4273         if (line2->type == LINE_TREE_HEAD)
4274                 return 1;
4276         switch (get_sort_field(tree_sort_state)) {
4277         case ORDERBY_DATE:
4278                 return sort_order(tree_sort_state, timecmp(&entry1->time, &entry2->time));
4280         case ORDERBY_AUTHOR:
4281                 return sort_order(tree_sort_state, strcmp(entry1->author, entry2->author));
4283         case ORDERBY_NAME:
4284         default:
4285                 return sort_order(tree_sort_state, tree_compare_entry(line1, line2));
4286         }
4290 static struct line *
4291 tree_entry(struct view *view, enum line_type type, const char *path,
4292            const char *mode, const char *id)
4294         struct tree_entry *entry = calloc(1, sizeof(*entry) + strlen(path));
4295         struct line *line = entry ? add_line_data(view, entry, type) : NULL;
4297         if (!entry || !line) {
4298                 free(entry);
4299                 return NULL;
4300         }
4302         strncpy(entry->name, path, strlen(path));
4303         if (mode)
4304                 entry->mode = strtoul(mode, NULL, 8);
4305         if (id)
4306                 string_copy_rev(entry->id, id);
4308         return line;
4311 static bool
4312 tree_read_date(struct view *view, char *text, bool *read_date)
4314         static const char *author_name;
4315         static struct time author_time;
4317         if (!text && *read_date) {
4318                 *read_date = FALSE;
4319                 return TRUE;
4321         } else if (!text) {
4322                 char *path = *opt_path ? opt_path : ".";
4323                 /* Find next entry to process */
4324                 const char *log_file[] = {
4325                         "git", "log", "--no-color", "--pretty=raw",
4326                                 "--cc", "--raw", view->id, "--", path, NULL
4327                 };
4328                 struct io io = {};
4330                 if (!view->lines) {
4331                         tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL);
4332                         report("Tree is empty");
4333                         return TRUE;
4334                 }
4336                 if (!run_io_rd(&io, log_file, opt_cdup, FORMAT_NONE)) {
4337                         report("Failed to load tree data");
4338                         return TRUE;
4339                 }
4341                 done_io(view->pipe);
4342                 view->io = io;
4343                 *read_date = TRUE;
4344                 return FALSE;
4346         } else if (*text == 'a' && get_line_type(text) == LINE_AUTHOR) {
4347                 parse_author_line(text + STRING_SIZE("author "),
4348                                   &author_name, &author_time);
4350         } else if (*text == ':') {
4351                 char *pos;
4352                 size_t annotated = 1;
4353                 size_t i;
4355                 pos = strchr(text, '\t');
4356                 if (!pos)
4357                         return TRUE;
4358                 text = pos + 1;
4359                 if (*opt_path && !strncmp(text, opt_path, strlen(opt_path)))
4360                         text += strlen(opt_path);
4361                 pos = strchr(text, '/');
4362                 if (pos)
4363                         *pos = 0;
4365                 for (i = 1; i < view->lines; i++) {
4366                         struct line *line = &view->line[i];
4367                         struct tree_entry *entry = line->data;
4369                         annotated += !!entry->author;
4370                         if (entry->author || strcmp(entry->name, text))
4371                                 continue;
4373                         entry->author = author_name;
4374                         entry->time = author_time;
4375                         line->dirty = 1;
4376                         break;
4377                 }
4379                 if (annotated == view->lines)
4380                         kill_io(view->pipe);
4381         }
4382         return TRUE;
4385 static bool
4386 tree_read(struct view *view, char *text)
4388         static bool read_date = FALSE;
4389         struct tree_entry *data;
4390         struct line *entry, *line;
4391         enum line_type type;
4392         size_t textlen = text ? strlen(text) : 0;
4393         char *path = text + SIZEOF_TREE_ATTR;
4395         if (read_date || !text)
4396                 return tree_read_date(view, text, &read_date);
4398         if (textlen <= SIZEOF_TREE_ATTR)
4399                 return FALSE;
4400         if (view->lines == 0 &&
4401             !tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL))
4402                 return FALSE;
4404         /* Strip the path part ... */
4405         if (*opt_path) {
4406                 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
4407                 size_t striplen = strlen(opt_path);
4409                 if (pathlen > striplen)
4410                         memmove(path, path + striplen,
4411                                 pathlen - striplen + 1);
4413                 /* Insert "link" to parent directory. */
4414                 if (view->lines == 1 &&
4415                     !tree_entry(view, LINE_TREE_DIR, "..", "040000", view->ref))
4416                         return FALSE;
4417         }
4419         type = text[SIZEOF_TREE_MODE] == 't' ? LINE_TREE_DIR : LINE_TREE_FILE;
4420         entry = tree_entry(view, type, path, text, text + TREE_ID_OFFSET);
4421         if (!entry)
4422                 return FALSE;
4423         data = entry->data;
4425         /* Skip "Directory ..." and ".." line. */
4426         for (line = &view->line[1 + !!*opt_path]; line < entry; line++) {
4427                 if (tree_compare_entry(line, entry) <= 0)
4428                         continue;
4430                 memmove(line + 1, line, (entry - line) * sizeof(*entry));
4432                 line->data = data;
4433                 line->type = type;
4434                 for (; line <= entry; line++)
4435                         line->dirty = line->cleareol = 1;
4436                 return TRUE;
4437         }
4439         if (tree_lineno > view->lineno) {
4440                 view->lineno = tree_lineno;
4441                 tree_lineno = 0;
4442         }
4444         return TRUE;
4447 static bool
4448 tree_draw(struct view *view, struct line *line, unsigned int lineno)
4450         struct tree_entry *entry = line->data;
4452         if (line->type == LINE_TREE_HEAD) {
4453                 if (draw_text(view, line->type, "Directory path /", TRUE))
4454                         return TRUE;
4455         } else {
4456                 if (draw_mode(view, entry->mode))
4457                         return TRUE;
4459                 if (opt_author && draw_author(view, entry->author))
4460                         return TRUE;
4462                 if (opt_date && draw_date(view, &entry->time))
4463                         return TRUE;
4464         }
4465         if (draw_text(view, line->type, entry->name, TRUE))
4466                 return TRUE;
4467         return TRUE;
4470 static void
4471 open_blob_editor()
4473         char file[SIZEOF_STR] = "/tmp/tigblob.XXXXXX";
4474         int fd = mkstemp(file);
4476         if (fd == -1)
4477                 report("Failed to create temporary file");
4478         else if (!run_io_append(blob_ops.argv, FORMAT_ALL, fd))
4479                 report("Failed to save blob data to file");
4480         else
4481                 open_editor(file);
4482         if (fd != -1)
4483                 unlink(file);
4486 static enum request
4487 tree_request(struct view *view, enum request request, struct line *line)
4489         enum open_flags flags;
4491         switch (request) {
4492         case REQ_VIEW_BLAME:
4493                 if (line->type != LINE_TREE_FILE) {
4494                         report("Blame only supported for files");
4495                         return REQ_NONE;
4496                 }
4498                 string_copy(opt_ref, view->vid);
4499                 return request;
4501         case REQ_EDIT:
4502                 if (line->type != LINE_TREE_FILE) {
4503                         report("Edit only supported for files");
4504                 } else if (!is_head_commit(view->vid)) {
4505                         open_blob_editor();
4506                 } else {
4507                         open_editor(opt_file);
4508                 }
4509                 return REQ_NONE;
4511         case REQ_TOGGLE_SORT_FIELD:
4512         case REQ_TOGGLE_SORT_ORDER:
4513                 sort_view(view, request, &tree_sort_state, tree_compare);
4514                 return REQ_NONE;
4516         case REQ_PARENT:
4517                 if (!*opt_path) {
4518                         /* quit view if at top of tree */
4519                         return REQ_VIEW_CLOSE;
4520                 }
4521                 /* fake 'cd  ..' */
4522                 line = &view->line[1];
4523                 break;
4525         case REQ_ENTER:
4526                 break;
4528         default:
4529                 return request;
4530         }
4532         /* Cleanup the stack if the tree view is at a different tree. */
4533         while (!*opt_path && tree_stack)
4534                 pop_tree_stack_entry();
4536         switch (line->type) {
4537         case LINE_TREE_DIR:
4538                 /* Depending on whether it is a subdirectory or parent link
4539                  * mangle the path buffer. */
4540                 if (line == &view->line[1] && *opt_path) {
4541                         pop_tree_stack_entry();
4543                 } else {
4544                         const char *basename = tree_path(line);
4546                         push_tree_stack_entry(basename, view->lineno);
4547                 }
4549                 /* Trees and subtrees share the same ID, so they are not not
4550                  * unique like blobs. */
4551                 flags = OPEN_RELOAD;
4552                 request = REQ_VIEW_TREE;
4553                 break;
4555         case LINE_TREE_FILE:
4556                 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4557                 request = REQ_VIEW_BLOB;
4558                 break;
4560         default:
4561                 return REQ_NONE;
4562         }
4564         open_view(view, request, flags);
4565         if (request == REQ_VIEW_TREE)
4566                 view->lineno = tree_lineno;
4568         return REQ_NONE;
4571 static bool
4572 tree_grep(struct view *view, struct line *line)
4574         struct tree_entry *entry = line->data;
4575         const char *text[] = {
4576                 entry->name,
4577                 opt_author ? entry->author : "",
4578                 opt_date ? mkdate(&entry->time) : "",
4579                 NULL
4580         };
4582         return grep_text(view, text);
4585 static void
4586 tree_select(struct view *view, struct line *line)
4588         struct tree_entry *entry = line->data;
4590         if (line->type == LINE_TREE_FILE) {
4591                 string_copy_rev(ref_blob, entry->id);
4592                 string_format(opt_file, "%s%s", opt_path, tree_path(line));
4594         } else if (line->type != LINE_TREE_DIR) {
4595                 return;
4596         }
4598         string_copy_rev(view->ref, entry->id);
4601 static bool
4602 tree_prepare(struct view *view)
4604         if (view->lines == 0 && opt_prefix[0]) {
4605                 char *pos = opt_prefix;
4607                 while (pos && *pos) {
4608                         char *end = strchr(pos, '/');
4610                         if (end)
4611                                 *end = 0;
4612                         push_tree_stack_entry(pos, 0);
4613                         pos = end;
4614                         if (end) {
4615                                 *end = '/';
4616                                 pos++;
4617                         }
4618                 }
4620         } else if (strcmp(view->vid, view->id)) {
4621                 opt_path[0] = 0;
4622         }
4624         return init_io_rd(&view->io, view->ops->argv, opt_cdup, FORMAT_ALL);
4627 static const char *tree_argv[SIZEOF_ARG] = {
4628         "git", "ls-tree", "%(commit)", "%(directory)", NULL
4629 };
4631 static struct view_ops tree_ops = {
4632         "file",
4633         tree_argv,
4634         NULL,
4635         tree_read,
4636         tree_draw,
4637         tree_request,
4638         tree_grep,
4639         tree_select,
4640         tree_prepare,
4641 };
4643 static bool
4644 blob_read(struct view *view, char *line)
4646         if (!line)
4647                 return TRUE;
4648         return add_line_text(view, line, LINE_DEFAULT) != NULL;
4651 static enum request
4652 blob_request(struct view *view, enum request request, struct line *line)
4654         switch (request) {
4655         case REQ_EDIT:
4656                 open_blob_editor();
4657                 return REQ_NONE;
4658         default:
4659                 return pager_request(view, request, line);
4660         }
4663 static const char *blob_argv[SIZEOF_ARG] = {
4664         "git", "cat-file", "blob", "%(blob)", NULL
4665 };
4667 static struct view_ops blob_ops = {
4668         "line",
4669         blob_argv,
4670         NULL,
4671         blob_read,
4672         pager_draw,
4673         blob_request,
4674         pager_grep,
4675         pager_select,
4676 };
4678 /*
4679  * Blame backend
4680  *
4681  * Loading the blame view is a two phase job:
4682  *
4683  *  1. File content is read either using opt_file from the
4684  *     filesystem or using git-cat-file.
4685  *  2. Then blame information is incrementally added by
4686  *     reading output from git-blame.
4687  */
4689 static const char *blame_head_argv[] = {
4690         "git", "blame", "--incremental", "--", "%(file)", NULL
4691 };
4693 static const char *blame_ref_argv[] = {
4694         "git", "blame", "--incremental", "%(ref)", "--", "%(file)", NULL
4695 };
4697 static const char *blame_cat_file_argv[] = {
4698         "git", "cat-file", "blob", "%(ref):%(file)", NULL
4699 };
4701 struct blame_commit {
4702         char id[SIZEOF_REV];            /* SHA1 ID. */
4703         char title[128];                /* First line of the commit message. */
4704         const char *author;             /* Author of the commit. */
4705         struct time time;               /* Date from the author ident. */
4706         char filename[128];             /* Name of file. */
4707         bool has_previous;              /* Was a "previous" line detected. */
4708 };
4710 struct blame {
4711         struct blame_commit *commit;
4712         unsigned long lineno;
4713         char text[1];
4714 };
4716 static bool
4717 blame_open(struct view *view)
4719         char path[SIZEOF_STR];
4721         if (!view->parent && *opt_prefix) {
4722                 string_copy(path, opt_file);
4723                 if (!string_format(opt_file, "%s%s", opt_prefix, path))
4724                         return FALSE;
4725         }
4727         if (*opt_ref || !io_open(&view->io, "%s%s", opt_cdup, opt_file)) {
4728                 if (!run_io_rd(&view->io, blame_cat_file_argv, opt_cdup, FORMAT_ALL))
4729                         return FALSE;
4730         }
4732         setup_update(view, opt_file);
4733         string_format(view->ref, "%s ...", opt_file);
4735         return TRUE;
4738 static struct blame_commit *
4739 get_blame_commit(struct view *view, const char *id)
4741         size_t i;
4743         for (i = 0; i < view->lines; i++) {
4744                 struct blame *blame = view->line[i].data;
4746                 if (!blame->commit)
4747                         continue;
4749                 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
4750                         return blame->commit;
4751         }
4753         {
4754                 struct blame_commit *commit = calloc(1, sizeof(*commit));
4756                 if (commit)
4757                         string_ncopy(commit->id, id, SIZEOF_REV);
4758                 return commit;
4759         }
4762 static bool
4763 parse_number(const char **posref, size_t *number, size_t min, size_t max)
4765         const char *pos = *posref;
4767         *posref = NULL;
4768         pos = strchr(pos + 1, ' ');
4769         if (!pos || !isdigit(pos[1]))
4770                 return FALSE;
4771         *number = atoi(pos + 1);
4772         if (*number < min || *number > max)
4773                 return FALSE;
4775         *posref = pos;
4776         return TRUE;
4779 static struct blame_commit *
4780 parse_blame_commit(struct view *view, const char *text, int *blamed)
4782         struct blame_commit *commit;
4783         struct blame *blame;
4784         const char *pos = text + SIZEOF_REV - 2;
4785         size_t orig_lineno = 0;
4786         size_t lineno;
4787         size_t group;
4789         if (strlen(text) <= SIZEOF_REV || pos[1] != ' ')
4790                 return NULL;
4792         if (!parse_number(&pos, &orig_lineno, 1, 9999999) ||
4793             !parse_number(&pos, &lineno, 1, view->lines) ||
4794             !parse_number(&pos, &group, 1, view->lines - lineno + 1))
4795                 return NULL;
4797         commit = get_blame_commit(view, text);
4798         if (!commit)
4799                 return NULL;
4801         *blamed += group;
4802         while (group--) {
4803                 struct line *line = &view->line[lineno + group - 1];
4805                 blame = line->data;
4806                 blame->commit = commit;
4807                 blame->lineno = orig_lineno + group - 1;
4808                 line->dirty = 1;
4809         }
4811         return commit;
4814 static bool
4815 blame_read_file(struct view *view, const char *line, bool *read_file)
4817         if (!line) {
4818                 const char **argv = *opt_ref ? blame_ref_argv : blame_head_argv;
4819                 struct io io = {};
4821                 if (view->lines == 0 && !view->parent)
4822                         die("No blame exist for %s", view->vid);
4824                 if (view->lines == 0 || !run_io_rd(&io, argv, opt_cdup, FORMAT_ALL)) {
4825                         report("Failed to load blame data");
4826                         return TRUE;
4827                 }
4829                 done_io(view->pipe);
4830                 view->io = io;
4831                 *read_file = FALSE;
4832                 return FALSE;
4834         } else {
4835                 size_t linelen = strlen(line);
4836                 struct blame *blame = malloc(sizeof(*blame) + linelen);
4838                 if (!blame)
4839                         return FALSE;
4841                 blame->commit = NULL;
4842                 strncpy(blame->text, line, linelen);
4843                 blame->text[linelen] = 0;
4844                 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
4845         }
4848 static bool
4849 match_blame_header(const char *name, char **line)
4851         size_t namelen = strlen(name);
4852         bool matched = !strncmp(name, *line, namelen);
4854         if (matched)
4855                 *line += namelen;
4857         return matched;
4860 static bool
4861 blame_read(struct view *view, char *line)
4863         static struct blame_commit *commit = NULL;
4864         static int blamed = 0;
4865         static bool read_file = TRUE;
4867         if (read_file)
4868                 return blame_read_file(view, line, &read_file);
4870         if (!line) {
4871                 /* Reset all! */
4872                 commit = NULL;
4873                 blamed = 0;
4874                 read_file = TRUE;
4875                 string_format(view->ref, "%s", view->vid);
4876                 if (view_is_displayed(view)) {
4877                         update_view_title(view);
4878                         redraw_view_from(view, 0);
4879                 }
4880                 return TRUE;
4881         }
4883         if (!commit) {
4884                 commit = parse_blame_commit(view, line, &blamed);
4885                 string_format(view->ref, "%s %2d%%", view->vid,
4886                               view->lines ? blamed * 100 / view->lines : 0);
4888         } else if (match_blame_header("author ", &line)) {
4889                 commit->author = get_author(line);
4891         } else if (match_blame_header("author-time ", &line)) {
4892                 parse_timesec(&commit->time, line);
4894         } else if (match_blame_header("author-tz ", &line)) {
4895                 parse_timezone(&commit->time, line);
4897         } else if (match_blame_header("summary ", &line)) {
4898                 string_ncopy(commit->title, line, strlen(line));
4900         } else if (match_blame_header("previous ", &line)) {
4901                 commit->has_previous = TRUE;
4903         } else if (match_blame_header("filename ", &line)) {
4904                 string_ncopy(commit->filename, line, strlen(line));
4905                 commit = NULL;
4906         }
4908         return TRUE;
4911 static bool
4912 blame_draw(struct view *view, struct line *line, unsigned int lineno)
4914         struct blame *blame = line->data;
4915         struct time *time = NULL;
4916         const char *id = NULL, *author = NULL;
4917         char text[SIZEOF_STR];
4919         if (blame->commit && *blame->commit->filename) {
4920                 id = blame->commit->id;
4921                 author = blame->commit->author;
4922                 time = &blame->commit->time;
4923         }
4925         if (opt_date && draw_date(view, time))
4926                 return TRUE;
4928         if (opt_author && draw_author(view, author))
4929                 return TRUE;
4931         if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
4932                 return TRUE;
4934         if (draw_lineno(view, lineno))
4935                 return TRUE;
4937         string_expand(text, sizeof(text), blame->text, opt_tab_size);
4938         draw_text(view, LINE_DEFAULT, text, TRUE);
4939         return TRUE;
4942 static bool
4943 check_blame_commit(struct blame *blame, bool check_null_id)
4945         if (!blame->commit)
4946                 report("Commit data not loaded yet");
4947         else if (check_null_id && !strcmp(blame->commit->id, NULL_ID))
4948                 report("No commit exist for the selected line");
4949         else
4950                 return TRUE;
4951         return FALSE;
4954 static void
4955 setup_blame_parent_line(struct view *view, struct blame *blame)
4957         const char *diff_tree_argv[] = {
4958                 "git", "diff-tree", "-U0", blame->commit->id,
4959                         "--", blame->commit->filename, NULL
4960         };
4961         struct io io = {};
4962         int parent_lineno = -1;
4963         int blamed_lineno = -1;
4964         char *line;
4966         if (!run_io(&io, diff_tree_argv, NULL, IO_RD))
4967                 return;
4969         while ((line = io_get(&io, '\n', TRUE))) {
4970                 if (*line == '@') {
4971                         char *pos = strchr(line, '+');
4973                         parent_lineno = atoi(line + 4);
4974                         if (pos)
4975                                 blamed_lineno = atoi(pos + 1);
4977                 } else if (*line == '+' && parent_lineno != -1) {
4978                         if (blame->lineno == blamed_lineno - 1 &&
4979                             !strcmp(blame->text, line + 1)) {
4980                                 view->lineno = parent_lineno ? parent_lineno - 1 : 0;
4981                                 break;
4982                         }
4983                         blamed_lineno++;
4984                 }
4985         }
4987         done_io(&io);
4990 static enum request
4991 blame_request(struct view *view, enum request request, struct line *line)
4993         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4994         struct blame *blame = line->data;
4996         switch (request) {
4997         case REQ_VIEW_BLAME:
4998                 if (check_blame_commit(blame, TRUE)) {
4999                         string_copy(opt_ref, blame->commit->id);
5000                         string_copy(opt_file, blame->commit->filename);
5001                         if (blame->lineno)
5002                                 view->lineno = blame->lineno;
5003                         open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
5004                 }
5005                 break;
5007         case REQ_PARENT:
5008                 if (check_blame_commit(blame, TRUE) &&
5009                     select_commit_parent(blame->commit->id, opt_ref,
5010                                          blame->commit->filename)) {
5011                         string_copy(opt_file, blame->commit->filename);
5012                         setup_blame_parent_line(view, blame);
5013                         open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
5014                 }
5015                 break;
5017         case REQ_ENTER:
5018                 if (!check_blame_commit(blame, FALSE))
5019                         break;
5021                 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
5022                     !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
5023                         break;
5025                 if (!strcmp(blame->commit->id, NULL_ID)) {
5026                         struct view *diff = VIEW(REQ_VIEW_DIFF);
5027                         const char *diff_index_argv[] = {
5028                                 "git", "diff-index", "--root", "--patch-with-stat",
5029                                         "-C", "-M", "HEAD", "--", view->vid, NULL
5030                         };
5032                         if (!blame->commit->has_previous) {
5033                                 diff_index_argv[1] = "diff";
5034                                 diff_index_argv[2] = "--no-color";
5035                                 diff_index_argv[6] = "--";
5036                                 diff_index_argv[7] = "/dev/null";
5037                         }
5039                         if (!prepare_update(diff, diff_index_argv, NULL, FORMAT_DASH)) {
5040                                 report("Failed to allocate diff command");
5041                                 break;
5042                         }
5043                         flags |= OPEN_PREPARED;
5044                 }
5046                 open_view(view, REQ_VIEW_DIFF, flags);
5047                 if (VIEW(REQ_VIEW_DIFF)->pipe && !strcmp(blame->commit->id, NULL_ID))
5048                         string_copy_rev(VIEW(REQ_VIEW_DIFF)->ref, NULL_ID);
5049                 break;
5051         default:
5052                 return request;
5053         }
5055         return REQ_NONE;
5058 static bool
5059 blame_grep(struct view *view, struct line *line)
5061         struct blame *blame = line->data;
5062         struct blame_commit *commit = blame->commit;
5063         const char *text[] = {
5064                 blame->text,
5065                 commit ? commit->title : "",
5066                 commit ? commit->id : "",
5067                 commit && opt_author ? commit->author : "",
5068                 commit && opt_date ? mkdate(&commit->time) : "",
5069                 NULL
5070         };
5072         return grep_text(view, text);
5075 static void
5076 blame_select(struct view *view, struct line *line)
5078         struct blame *blame = line->data;
5079         struct blame_commit *commit = blame->commit;
5081         if (!commit)
5082                 return;
5084         if (!strcmp(commit->id, NULL_ID))
5085                 string_ncopy(ref_commit, "HEAD", 4);
5086         else
5087                 string_copy_rev(ref_commit, commit->id);
5090 static struct view_ops blame_ops = {
5091         "line",
5092         NULL,
5093         blame_open,
5094         blame_read,
5095         blame_draw,
5096         blame_request,
5097         blame_grep,
5098         blame_select,
5099 };
5101 /*
5102  * Branch backend
5103  */
5105 struct branch {
5106         const char *author;             /* Author of the last commit. */
5107         struct time time;               /* Date of the last activity. */
5108         const struct ref *ref;          /* Name and commit ID information. */
5109 };
5111 static const struct ref branch_all;
5113 static const enum sort_field branch_sort_fields[] = {
5114         ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
5115 };
5116 static struct sort_state branch_sort_state = SORT_STATE(branch_sort_fields);
5118 static int
5119 branch_compare(const void *l1, const void *l2)
5121         const struct branch *branch1 = ((const struct line *) l1)->data;
5122         const struct branch *branch2 = ((const struct line *) l2)->data;
5124         switch (get_sort_field(branch_sort_state)) {
5125         case ORDERBY_DATE:
5126                 return sort_order(branch_sort_state, timecmp(&branch1->time, &branch2->time));
5128         case ORDERBY_AUTHOR:
5129                 return sort_order(branch_sort_state, strcmp(branch1->author, branch2->author));
5131         case ORDERBY_NAME:
5132         default:
5133                 return sort_order(branch_sort_state, strcmp(branch1->ref->name, branch2->ref->name));
5134         }
5137 static bool
5138 branch_draw(struct view *view, struct line *line, unsigned int lineno)
5140         struct branch *branch = line->data;
5141         enum line_type type = branch->ref->head ? LINE_MAIN_HEAD : LINE_DEFAULT;
5143         if (opt_date && draw_date(view, &branch->time))
5144                 return TRUE;
5146         if (opt_author && draw_author(view, branch->author))
5147                 return TRUE;
5149         draw_text(view, type, branch->ref == &branch_all ? "All branches" : branch->ref->name, TRUE);
5150         return TRUE;
5153 static enum request
5154 branch_request(struct view *view, enum request request, struct line *line)
5156         struct branch *branch = line->data;
5158         switch (request) {
5159         case REQ_REFRESH:
5160                 load_refs();
5161                 open_view(view, REQ_VIEW_BRANCH, OPEN_REFRESH);
5162                 return REQ_NONE;
5164         case REQ_TOGGLE_SORT_FIELD:
5165         case REQ_TOGGLE_SORT_ORDER:
5166                 sort_view(view, request, &branch_sort_state, branch_compare);
5167                 return REQ_NONE;
5169         case REQ_ENTER:
5170                 if (branch->ref == &branch_all) {
5171                         const char *all_branches_argv[] = {
5172                                 "git", "log", "--no-color", "--pretty=raw", "--parents",
5173                                       "--topo-order", "--all", NULL
5174                         };
5175                         struct view *main_view = VIEW(REQ_VIEW_MAIN);
5177                         if (!prepare_update(main_view, all_branches_argv, NULL, FORMAT_NONE)) {
5178                                 report("Failed to load view of all branches");
5179                                 return REQ_NONE;
5180                         }
5181                         open_view(view, REQ_VIEW_MAIN, OPEN_PREPARED | OPEN_SPLIT);
5182                 } else {
5183                         open_view(view, REQ_VIEW_MAIN, OPEN_SPLIT);
5184                 }
5185                 return REQ_NONE;
5187         default:
5188                 return request;
5189         }
5192 static bool
5193 branch_read(struct view *view, char *line)
5195         static char id[SIZEOF_REV];
5196         struct branch *reference;
5197         size_t i;
5199         if (!line)
5200                 return TRUE;
5202         switch (get_line_type(line)) {
5203         case LINE_COMMIT:
5204                 string_copy_rev(id, line + STRING_SIZE("commit "));
5205                 return TRUE;
5207         case LINE_AUTHOR:
5208                 for (i = 0, reference = NULL; i < view->lines; i++) {
5209                         struct branch *branch = view->line[i].data;
5211                         if (strcmp(branch->ref->id, id))
5212                                 continue;
5214                         view->line[i].dirty = TRUE;
5215                         if (reference) {
5216                                 branch->author = reference->author;
5217                                 branch->time = reference->time;
5218                                 continue;
5219                         }
5221                         parse_author_line(line + STRING_SIZE("author "),
5222                                           &branch->author, &branch->time);
5223                         reference = branch;
5224                 }
5225                 return TRUE;
5227         default:
5228                 return TRUE;
5229         }
5233 static bool
5234 branch_open_visitor(void *data, const struct ref *ref)
5236         struct view *view = data;
5237         struct branch *branch;
5239         if (ref->tag || ref->ltag || ref->remote)
5240                 return TRUE;
5242         branch = calloc(1, sizeof(*branch));
5243         if (!branch)
5244                 return FALSE;
5246         branch->ref = ref;
5247         return !!add_line_data(view, branch, LINE_DEFAULT);
5250 static bool
5251 branch_open(struct view *view)
5253         const char *branch_log[] = {
5254                 "git", "log", "--no-color", "--pretty=raw",
5255                         "--simplify-by-decoration", "--all", NULL
5256         };
5258         if (!run_io_rd(&view->io, branch_log, NULL, FORMAT_NONE)) {
5259                 report("Failed to load branch data");
5260                 return TRUE;
5261         }
5263         setup_update(view, view->id);
5264         branch_open_visitor(view, &branch_all);
5265         foreach_ref(branch_open_visitor, view);
5266         view->p_restore = TRUE;
5268         return TRUE;
5271 static bool
5272 branch_grep(struct view *view, struct line *line)
5274         struct branch *branch = line->data;
5275         const char *text[] = {
5276                 branch->ref->name,
5277                 branch->author,
5278                 NULL
5279         };
5281         return grep_text(view, text);
5284 static void
5285 branch_select(struct view *view, struct line *line)
5287         struct branch *branch = line->data;
5289         string_copy_rev(view->ref, branch->ref->id);
5290         string_copy_rev(ref_commit, branch->ref->id);
5291         string_copy_rev(ref_head, branch->ref->id);
5294 static struct view_ops branch_ops = {
5295         "branch",
5296         NULL,
5297         branch_open,
5298         branch_read,
5299         branch_draw,
5300         branch_request,
5301         branch_grep,
5302         branch_select,
5303 };
5305 /*
5306  * Status backend
5307  */
5309 struct status {
5310         char status;
5311         struct {
5312                 mode_t mode;
5313                 char rev[SIZEOF_REV];
5314                 char name[SIZEOF_STR];
5315         } old;
5316         struct {
5317                 mode_t mode;
5318                 char rev[SIZEOF_REV];
5319                 char name[SIZEOF_STR];
5320         } new;
5321 };
5323 static char status_onbranch[SIZEOF_STR];
5324 static struct status stage_status;
5325 static enum line_type stage_line_type;
5326 static size_t stage_chunks;
5327 static int *stage_chunk;
5329 DEFINE_ALLOCATOR(realloc_ints, int, 32)
5331 /* This should work even for the "On branch" line. */
5332 static inline bool
5333 status_has_none(struct view *view, struct line *line)
5335         return line < view->line + view->lines && !line[1].data;
5338 /* Get fields from the diff line:
5339  * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
5340  */
5341 static inline bool
5342 status_get_diff(struct status *file, const char *buf, size_t bufsize)
5344         const char *old_mode = buf +  1;
5345         const char *new_mode = buf +  8;
5346         const char *old_rev  = buf + 15;
5347         const char *new_rev  = buf + 56;
5348         const char *status   = buf + 97;
5350         if (bufsize < 98 ||
5351             old_mode[-1] != ':' ||
5352             new_mode[-1] != ' ' ||
5353             old_rev[-1]  != ' ' ||
5354             new_rev[-1]  != ' ' ||
5355             status[-1]   != ' ')
5356                 return FALSE;
5358         file->status = *status;
5360         string_copy_rev(file->old.rev, old_rev);
5361         string_copy_rev(file->new.rev, new_rev);
5363         file->old.mode = strtoul(old_mode, NULL, 8);
5364         file->new.mode = strtoul(new_mode, NULL, 8);
5366         file->old.name[0] = file->new.name[0] = 0;
5368         return TRUE;
5371 static bool
5372 status_run(struct view *view, const char *argv[], char status, enum line_type type)
5374         struct status *unmerged = NULL;
5375         char *buf;
5376         struct io io = {};
5378         if (!run_io(&io, argv, opt_cdup, IO_RD))
5379                 return FALSE;
5381         add_line_data(view, NULL, type);
5383         while ((buf = io_get(&io, 0, TRUE))) {
5384                 struct status *file = unmerged;
5386                 if (!file) {
5387                         file = calloc(1, sizeof(*file));
5388                         if (!file || !add_line_data(view, file, type))
5389                                 goto error_out;
5390                 }
5392                 /* Parse diff info part. */
5393                 if (status) {
5394                         file->status = status;
5395                         if (status == 'A')
5396                                 string_copy(file->old.rev, NULL_ID);
5398                 } else if (!file->status || file == unmerged) {
5399                         if (!status_get_diff(file, buf, strlen(buf)))
5400                                 goto error_out;
5402                         buf = io_get(&io, 0, TRUE);
5403                         if (!buf)
5404                                 break;
5406                         /* Collapse all modified entries that follow an
5407                          * associated unmerged entry. */
5408                         if (unmerged == file) {
5409                                 unmerged->status = 'U';
5410                                 unmerged = NULL;
5411                         } else if (file->status == 'U') {
5412                                 unmerged = file;
5413                         }
5414                 }
5416                 /* Grab the old name for rename/copy. */
5417                 if (!*file->old.name &&
5418                     (file->status == 'R' || file->status == 'C')) {
5419                         string_ncopy(file->old.name, buf, strlen(buf));
5421                         buf = io_get(&io, 0, TRUE);
5422                         if (!buf)
5423                                 break;
5424                 }
5426                 /* git-ls-files just delivers a NUL separated list of
5427                  * file names similar to the second half of the
5428                  * git-diff-* output. */
5429                 string_ncopy(file->new.name, buf, strlen(buf));
5430                 if (!*file->old.name)
5431                         string_copy(file->old.name, file->new.name);
5432                 file = NULL;
5433         }
5435         if (io_error(&io)) {
5436 error_out:
5437                 done_io(&io);
5438                 return FALSE;
5439         }
5441         if (!view->line[view->lines - 1].data)
5442                 add_line_data(view, NULL, LINE_STAT_NONE);
5444         done_io(&io);
5445         return TRUE;
5448 /* Don't show unmerged entries in the staged section. */
5449 static const char *status_diff_index_argv[] = {
5450         "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
5451                              "--cached", "-M", "HEAD", NULL
5452 };
5454 static const char *status_diff_files_argv[] = {
5455         "git", "diff-files", "-z", NULL
5456 };
5458 static const char *status_list_other_argv[] = {
5459         "git", "ls-files", "-z", "--others", "--exclude-standard", opt_prefix, NULL
5460 };
5462 static const char *status_list_no_head_argv[] = {
5463         "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
5464 };
5466 static const char *update_index_argv[] = {
5467         "git", "update-index", "-q", "--unmerged", "--refresh", NULL
5468 };
5470 /* Restore the previous line number to stay in the context or select a
5471  * line with something that can be updated. */
5472 static void
5473 status_restore(struct view *view)
5475         if (view->p_lineno >= view->lines)
5476                 view->p_lineno = view->lines - 1;
5477         while (view->p_lineno < view->lines && !view->line[view->p_lineno].data)
5478                 view->p_lineno++;
5479         while (view->p_lineno > 0 && !view->line[view->p_lineno].data)
5480                 view->p_lineno--;
5482         /* If the above fails, always skip the "On branch" line. */
5483         if (view->p_lineno < view->lines)
5484                 view->lineno = view->p_lineno;
5485         else
5486                 view->lineno = 1;
5488         if (view->lineno < view->offset)
5489                 view->offset = view->lineno;
5490         else if (view->offset + view->height <= view->lineno)
5491                 view->offset = view->lineno - view->height + 1;
5493         view->p_restore = FALSE;
5496 static void
5497 status_update_onbranch(void)
5499         static const char *paths[][2] = {
5500                 { "rebase-apply/rebasing",      "Rebasing" },
5501                 { "rebase-apply/applying",      "Applying mailbox" },
5502                 { "rebase-apply/",              "Rebasing mailbox" },
5503                 { "rebase-merge/interactive",   "Interactive rebase" },
5504                 { "rebase-merge/",              "Rebase merge" },
5505                 { "MERGE_HEAD",                 "Merging" },
5506                 { "BISECT_LOG",                 "Bisecting" },
5507                 { "HEAD",                       "On branch" },
5508         };
5509         char buf[SIZEOF_STR];
5510         struct stat stat;
5511         int i;
5513         if (is_initial_commit()) {
5514                 string_copy(status_onbranch, "Initial commit");
5515                 return;
5516         }
5518         for (i = 0; i < ARRAY_SIZE(paths); i++) {
5519                 char *head = opt_head;
5521                 if (!string_format(buf, "%s/%s", opt_git_dir, paths[i][0]) ||
5522                     lstat(buf, &stat) < 0)
5523                         continue;
5525                 if (!*opt_head) {
5526                         struct io io = {};
5528                         if (io_open(&io, "%s/rebase-merge/head-name", opt_git_dir) &&
5529                             io_read_buf(&io, buf, sizeof(buf))) {
5530                                 head = buf;
5531                                 if (!prefixcmp(head, "refs/heads/"))
5532                                         head += STRING_SIZE("refs/heads/");
5533                         }
5534                 }
5536                 if (!string_format(status_onbranch, "%s %s", paths[i][1], head))
5537                         string_copy(status_onbranch, opt_head);
5538                 return;
5539         }
5541         string_copy(status_onbranch, "Not currently on any branch");
5544 /* First parse staged info using git-diff-index(1), then parse unstaged
5545  * info using git-diff-files(1), and finally untracked files using
5546  * git-ls-files(1). */
5547 static bool
5548 status_open(struct view *view)
5550         reset_view(view);
5552         add_line_data(view, NULL, LINE_STAT_HEAD);
5553         status_update_onbranch();
5555         run_io_bg(update_index_argv);
5557         if (is_initial_commit()) {
5558                 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
5559                         return FALSE;
5560         } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
5561                 return FALSE;
5562         }
5564         if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
5565             !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
5566                 return FALSE;
5568         /* Restore the exact position or use the specialized restore
5569          * mode? */
5570         if (!view->p_restore)
5571                 status_restore(view);
5572         return TRUE;
5575 static bool
5576 status_draw(struct view *view, struct line *line, unsigned int lineno)
5578         struct status *status = line->data;
5579         enum line_type type;
5580         const char *text;
5582         if (!status) {
5583                 switch (line->type) {
5584                 case LINE_STAT_STAGED:
5585                         type = LINE_STAT_SECTION;
5586                         text = "Changes to be committed:";
5587                         break;
5589                 case LINE_STAT_UNSTAGED:
5590                         type = LINE_STAT_SECTION;
5591                         text = "Changed but not updated:";
5592                         break;
5594                 case LINE_STAT_UNTRACKED:
5595                         type = LINE_STAT_SECTION;
5596                         text = "Untracked files:";
5597                         break;
5599                 case LINE_STAT_NONE:
5600                         type = LINE_DEFAULT;
5601                         text = "  (no files)";
5602                         break;
5604                 case LINE_STAT_HEAD:
5605                         type = LINE_STAT_HEAD;
5606                         text = status_onbranch;
5607                         break;
5609                 default:
5610                         return FALSE;
5611                 }
5612         } else {
5613                 static char buf[] = { '?', ' ', ' ', ' ', 0 };
5615                 buf[0] = status->status;
5616                 if (draw_text(view, line->type, buf, TRUE))
5617                         return TRUE;
5618                 type = LINE_DEFAULT;
5619                 text = status->new.name;
5620         }
5622         draw_text(view, type, text, TRUE);
5623         return TRUE;
5626 static enum request
5627 status_load_error(struct view *view, struct view *stage, const char *path)
5629         if (displayed_views() == 2 || display[current_view] != view)
5630                 maximize_view(view);
5631         report("Failed to load '%s': %s", path, io_strerror(&stage->io));
5632         return REQ_NONE;
5635 static enum request
5636 status_enter(struct view *view, struct line *line)
5638         struct status *status = line->data;
5639         const char *oldpath = status ? status->old.name : NULL;
5640         /* Diffs for unmerged entries are empty when passing the new
5641          * path, so leave it empty. */
5642         const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
5643         const char *info;
5644         enum open_flags split;
5645         struct view *stage = VIEW(REQ_VIEW_STAGE);
5647         if (line->type == LINE_STAT_NONE ||
5648             (!status && line[1].type == LINE_STAT_NONE)) {
5649                 report("No file to diff");
5650                 return REQ_NONE;
5651         }
5653         switch (line->type) {
5654         case LINE_STAT_STAGED:
5655                 if (is_initial_commit()) {
5656                         const char *no_head_diff_argv[] = {
5657                                 "git", "diff", "--no-color", "--patch-with-stat",
5658                                         "--", "/dev/null", newpath, NULL
5659                         };
5661                         if (!prepare_update(stage, no_head_diff_argv, opt_cdup, FORMAT_DASH))
5662                                 return status_load_error(view, stage, newpath);
5663                 } else {
5664                         const char *index_show_argv[] = {
5665                                 "git", "diff-index", "--root", "--patch-with-stat",
5666                                         "-C", "-M", "--cached", "HEAD", "--",
5667                                         oldpath, newpath, NULL
5668                         };
5670                         if (!prepare_update(stage, index_show_argv, opt_cdup, FORMAT_DASH))
5671                                 return status_load_error(view, stage, newpath);
5672                 }
5674                 if (status)
5675                         info = "Staged changes to %s";
5676                 else
5677                         info = "Staged changes";
5678                 break;
5680         case LINE_STAT_UNSTAGED:
5681         {
5682                 const char *files_show_argv[] = {
5683                         "git", "diff-files", "--root", "--patch-with-stat",
5684                                 "-C", "-M", "--", oldpath, newpath, NULL
5685                 };
5687                 if (!prepare_update(stage, files_show_argv, opt_cdup, FORMAT_DASH))
5688                         return status_load_error(view, stage, newpath);
5689                 if (status)
5690                         info = "Unstaged changes to %s";
5691                 else
5692                         info = "Unstaged changes";
5693                 break;
5694         }
5695         case LINE_STAT_UNTRACKED:
5696                 if (!newpath) {
5697                         report("No file to show");
5698                         return REQ_NONE;
5699                 }
5701                 if (!suffixcmp(status->new.name, -1, "/")) {
5702                         report("Cannot display a directory");
5703                         return REQ_NONE;
5704                 }
5706                 if (!prepare_update_file(stage, newpath))
5707                         return status_load_error(view, stage, newpath);
5708                 info = "Untracked file %s";
5709                 break;
5711         case LINE_STAT_HEAD:
5712                 return REQ_NONE;
5714         default:
5715                 die("line type %d not handled in switch", line->type);
5716         }
5718         split = view_is_displayed(view) ? OPEN_SPLIT : 0;
5719         open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split);
5720         if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
5721                 if (status) {
5722                         stage_status = *status;
5723                 } else {
5724                         memset(&stage_status, 0, sizeof(stage_status));
5725                 }
5727                 stage_line_type = line->type;
5728                 stage_chunks = 0;
5729                 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
5730         }
5732         return REQ_NONE;
5735 static bool
5736 status_exists(struct status *status, enum line_type type)
5738         struct view *view = VIEW(REQ_VIEW_STATUS);
5739         unsigned long lineno;
5741         for (lineno = 0; lineno < view->lines; lineno++) {
5742                 struct line *line = &view->line[lineno];
5743                 struct status *pos = line->data;
5745                 if (line->type != type)
5746                         continue;
5747                 if (!pos && (!status || !status->status) && line[1].data) {
5748                         select_view_line(view, lineno);
5749                         return TRUE;
5750                 }
5751                 if (pos && !strcmp(status->new.name, pos->new.name)) {
5752                         select_view_line(view, lineno);
5753                         return TRUE;
5754                 }
5755         }
5757         return FALSE;
5761 static bool
5762 status_update_prepare(struct io *io, enum line_type type)
5764         const char *staged_argv[] = {
5765                 "git", "update-index", "-z", "--index-info", NULL
5766         };
5767         const char *others_argv[] = {
5768                 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
5769         };
5771         switch (type) {
5772         case LINE_STAT_STAGED:
5773                 return run_io(io, staged_argv, opt_cdup, IO_WR);
5775         case LINE_STAT_UNSTAGED:
5776         case LINE_STAT_UNTRACKED:
5777                 return run_io(io, others_argv, opt_cdup, IO_WR);
5779         default:
5780                 die("line type %d not handled in switch", type);
5781                 return FALSE;
5782         }
5785 static bool
5786 status_update_write(struct io *io, struct status *status, enum line_type type)
5788         char buf[SIZEOF_STR];
5789         size_t bufsize = 0;
5791         switch (type) {
5792         case LINE_STAT_STAGED:
5793                 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
5794                                         status->old.mode,
5795                                         status->old.rev,
5796                                         status->old.name, 0))
5797                         return FALSE;
5798                 break;
5800         case LINE_STAT_UNSTAGED:
5801         case LINE_STAT_UNTRACKED:
5802                 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
5803                         return FALSE;
5804                 break;
5806         default:
5807                 die("line type %d not handled in switch", type);
5808         }
5810         return io_write(io, buf, bufsize);
5813 static bool
5814 status_update_file(struct status *status, enum line_type type)
5816         struct io io = {};
5817         bool result;
5819         if (!status_update_prepare(&io, type))
5820                 return FALSE;
5822         result = status_update_write(&io, status, type);
5823         return done_io(&io) && result;
5826 static bool
5827 status_update_files(struct view *view, struct line *line)
5829         char buf[sizeof(view->ref)];
5830         struct io io = {};
5831         bool result = TRUE;
5832         struct line *pos = view->line + view->lines;
5833         int files = 0;
5834         int file, done;
5835         int cursor_y = -1, cursor_x = -1;
5837         if (!status_update_prepare(&io, line->type))
5838                 return FALSE;
5840         for (pos = line; pos < view->line + view->lines && pos->data; pos++)
5841                 files++;
5843         string_copy(buf, view->ref);
5844         getsyx(cursor_y, cursor_x);
5845         for (file = 0, done = 5; result && file < files; line++, file++) {
5846                 int almost_done = file * 100 / files;
5848                 if (almost_done > done) {
5849                         done = almost_done;
5850                         string_format(view->ref, "updating file %u of %u (%d%% done)",
5851                                       file, files, done);
5852                         update_view_title(view);
5853                         setsyx(cursor_y, cursor_x);
5854                         doupdate();
5855                 }
5856                 result = status_update_write(&io, line->data, line->type);
5857         }
5858         string_copy(view->ref, buf);
5860         return done_io(&io) && result;
5863 static bool
5864 status_update(struct view *view)
5866         struct line *line = &view->line[view->lineno];
5868         assert(view->lines);
5870         if (!line->data) {
5871                 /* This should work even for the "On branch" line. */
5872                 if (line < view->line + view->lines && !line[1].data) {
5873                         report("Nothing to update");
5874                         return FALSE;
5875                 }
5877                 if (!status_update_files(view, line + 1)) {
5878                         report("Failed to update file status");
5879                         return FALSE;
5880                 }
5882         } else if (!status_update_file(line->data, line->type)) {
5883                 report("Failed to update file status");
5884                 return FALSE;
5885         }
5887         return TRUE;
5890 static bool
5891 status_revert(struct status *status, enum line_type type, bool has_none)
5893         if (!status || type != LINE_STAT_UNSTAGED) {
5894                 if (type == LINE_STAT_STAGED) {
5895                         report("Cannot revert changes to staged files");
5896                 } else if (type == LINE_STAT_UNTRACKED) {
5897                         report("Cannot revert changes to untracked files");
5898                 } else if (has_none) {
5899                         report("Nothing to revert");
5900                 } else {
5901                         report("Cannot revert changes to multiple files");
5902                 }
5904         } else if (prompt_yesno("Are you sure you want to revert changes?")) {
5905                 char mode[10] = "100644";
5906                 const char *reset_argv[] = {
5907                         "git", "update-index", "--cacheinfo", mode,
5908                                 status->old.rev, status->old.name, NULL
5909                 };
5910                 const char *checkout_argv[] = {
5911                         "git", "checkout", "--", status->old.name, NULL
5912                 };
5914                 if (status->status == 'U') {
5915                         string_format(mode, "%5o", status->old.mode);
5917                         if (status->old.mode == 0 && status->new.mode == 0) {
5918                                 reset_argv[2] = "--force-remove";
5919                                 reset_argv[3] = status->old.name;
5920                                 reset_argv[4] = NULL;
5921                         }
5923                         if (!run_io_fg(reset_argv, opt_cdup))
5924                                 return FALSE;
5925                         if (status->old.mode == 0 && status->new.mode == 0)
5926                                 return TRUE;
5927                 }
5929                 return run_io_fg(checkout_argv, opt_cdup);
5930         }
5932         return FALSE;
5935 static enum request
5936 status_request(struct view *view, enum request request, struct line *line)
5938         struct status *status = line->data;
5940         switch (request) {
5941         case REQ_STATUS_UPDATE:
5942                 if (!status_update(view))
5943                         return REQ_NONE;
5944                 break;
5946         case REQ_STATUS_REVERT:
5947                 if (!status_revert(status, line->type, status_has_none(view, line)))
5948                         return REQ_NONE;
5949                 break;
5951         case REQ_STATUS_MERGE:
5952                 if (!status || status->status != 'U') {
5953                         report("Merging only possible for files with unmerged status ('U').");
5954                         return REQ_NONE;
5955                 }
5956                 open_mergetool(status->new.name);
5957                 break;
5959         case REQ_EDIT:
5960                 if (!status)
5961                         return request;
5962                 if (status->status == 'D') {
5963                         report("File has been deleted.");
5964                         return REQ_NONE;
5965                 }
5967                 open_editor(status->new.name);
5968                 break;
5970         case REQ_VIEW_BLAME:
5971                 if (status)
5972                         opt_ref[0] = 0;
5973                 return request;
5975         case REQ_ENTER:
5976                 /* After returning the status view has been split to
5977                  * show the stage view. No further reloading is
5978                  * necessary. */
5979                 return status_enter(view, line);
5981         case REQ_REFRESH:
5982                 /* Simply reload the view. */
5983                 break;
5985         default:
5986                 return request;
5987         }
5989         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
5991         return REQ_NONE;
5994 static void
5995 status_select(struct view *view, struct line *line)
5997         struct status *status = line->data;
5998         char file[SIZEOF_STR] = "all files";
5999         const char *text;
6000         const char *key;
6002         if (status && !string_format(file, "'%s'", status->new.name))
6003                 return;
6005         if (!status && line[1].type == LINE_STAT_NONE)
6006                 line++;
6008         switch (line->type) {
6009         case LINE_STAT_STAGED:
6010                 text = "Press %s to unstage %s for commit";
6011                 break;
6013         case LINE_STAT_UNSTAGED:
6014                 text = "Press %s to stage %s for commit";
6015                 break;
6017         case LINE_STAT_UNTRACKED:
6018                 text = "Press %s to stage %s for addition";
6019                 break;
6021         case LINE_STAT_HEAD:
6022         case LINE_STAT_NONE:
6023                 text = "Nothing to update";
6024                 break;
6026         default:
6027                 die("line type %d not handled in switch", line->type);
6028         }
6030         if (status && status->status == 'U') {
6031                 text = "Press %s to resolve conflict in %s";
6032                 key = get_key(KEYMAP_STATUS, REQ_STATUS_MERGE);
6034         } else {
6035                 key = get_key(KEYMAP_STATUS, REQ_STATUS_UPDATE);
6036         }
6038         string_format(view->ref, text, key, file);
6039         if (status)
6040                 string_copy(opt_file, status->new.name);
6043 static bool
6044 status_grep(struct view *view, struct line *line)
6046         struct status *status = line->data;
6048         if (status) {
6049                 const char buf[2] = { status->status, 0 };
6050                 const char *text[] = { status->new.name, buf, NULL };
6052                 return grep_text(view, text);
6053         }
6055         return FALSE;
6058 static struct view_ops status_ops = {
6059         "file",
6060         NULL,
6061         status_open,
6062         NULL,
6063         status_draw,
6064         status_request,
6065         status_grep,
6066         status_select,
6067 };
6070 static bool
6071 stage_diff_write(struct io *io, struct line *line, struct line *end)
6073         while (line < end) {
6074                 if (!io_write(io, line->data, strlen(line->data)) ||
6075                     !io_write(io, "\n", 1))
6076                         return FALSE;
6077                 line++;
6078                 if (line->type == LINE_DIFF_CHUNK ||
6079                     line->type == LINE_DIFF_HEADER)
6080                         break;
6081         }
6083         return TRUE;
6086 static struct line *
6087 stage_diff_find(struct view *view, struct line *line, enum line_type type)
6089         for (; view->line < line; line--)
6090                 if (line->type == type)
6091                         return line;
6093         return NULL;
6096 static bool
6097 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
6099         const char *apply_argv[SIZEOF_ARG] = {
6100                 "git", "apply", "--whitespace=nowarn", NULL
6101         };
6102         struct line *diff_hdr;
6103         struct io io = {};
6104         int argc = 3;
6106         diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
6107         if (!diff_hdr)
6108                 return FALSE;
6110         if (!revert)
6111                 apply_argv[argc++] = "--cached";
6112         if (revert || stage_line_type == LINE_STAT_STAGED)
6113                 apply_argv[argc++] = "-R";
6114         apply_argv[argc++] = "-";
6115         apply_argv[argc++] = NULL;
6116         if (!run_io(&io, apply_argv, opt_cdup, IO_WR))
6117                 return FALSE;
6119         if (!stage_diff_write(&io, diff_hdr, chunk) ||
6120             !stage_diff_write(&io, chunk, view->line + view->lines))
6121                 chunk = NULL;
6123         done_io(&io);
6124         run_io_bg(update_index_argv);
6126         return chunk ? TRUE : FALSE;
6129 static bool
6130 stage_update(struct view *view, struct line *line)
6132         struct line *chunk = NULL;
6134         if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
6135                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6137         if (chunk) {
6138                 if (!stage_apply_chunk(view, chunk, FALSE)) {
6139                         report("Failed to apply chunk");
6140                         return FALSE;
6141                 }
6143         } else if (!stage_status.status) {
6144                 view = VIEW(REQ_VIEW_STATUS);
6146                 for (line = view->line; line < view->line + view->lines; line++)
6147                         if (line->type == stage_line_type)
6148                                 break;
6150                 if (!status_update_files(view, line + 1)) {
6151                         report("Failed to update files");
6152                         return FALSE;
6153                 }
6155         } else if (!status_update_file(&stage_status, stage_line_type)) {
6156                 report("Failed to update file");
6157                 return FALSE;
6158         }
6160         return TRUE;
6163 static bool
6164 stage_revert(struct view *view, struct line *line)
6166         struct line *chunk = NULL;
6168         if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
6169                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6171         if (chunk) {
6172                 if (!prompt_yesno("Are you sure you want to revert changes?"))
6173                         return FALSE;
6175                 if (!stage_apply_chunk(view, chunk, TRUE)) {
6176                         report("Failed to revert chunk");
6177                         return FALSE;
6178                 }
6179                 return TRUE;
6181         } else {
6182                 return status_revert(stage_status.status ? &stage_status : NULL,
6183                                      stage_line_type, FALSE);
6184         }
6188 static void
6189 stage_next(struct view *view, struct line *line)
6191         int i;
6193         if (!stage_chunks) {
6194                 for (line = view->line; line < view->line + view->lines; line++) {
6195                         if (line->type != LINE_DIFF_CHUNK)
6196                                 continue;
6198                         if (!realloc_ints(&stage_chunk, stage_chunks, 1)) {
6199                                 report("Allocation failure");
6200                                 return;
6201                         }
6203                         stage_chunk[stage_chunks++] = line - view->line;
6204                 }
6205         }
6207         for (i = 0; i < stage_chunks; i++) {
6208                 if (stage_chunk[i] > view->lineno) {
6209                         do_scroll_view(view, stage_chunk[i] - view->lineno);
6210                         report("Chunk %d of %d", i + 1, stage_chunks);
6211                         return;
6212                 }
6213         }
6215         report("No next chunk found");
6218 static enum request
6219 stage_request(struct view *view, enum request request, struct line *line)
6221         switch (request) {
6222         case REQ_STATUS_UPDATE:
6223                 if (!stage_update(view, line))
6224                         return REQ_NONE;
6225                 break;
6227         case REQ_STATUS_REVERT:
6228                 if (!stage_revert(view, line))
6229                         return REQ_NONE;
6230                 break;
6232         case REQ_STAGE_NEXT:
6233                 if (stage_line_type == LINE_STAT_UNTRACKED) {
6234                         report("File is untracked; press %s to add",
6235                                get_key(KEYMAP_STAGE, REQ_STATUS_UPDATE));
6236                         return REQ_NONE;
6237                 }
6238                 stage_next(view, line);
6239                 return REQ_NONE;
6241         case REQ_EDIT:
6242                 if (!stage_status.new.name[0])
6243                         return request;
6244                 if (stage_status.status == 'D') {
6245                         report("File has been deleted.");
6246                         return REQ_NONE;
6247                 }
6249                 open_editor(stage_status.new.name);
6250                 break;
6252         case REQ_REFRESH:
6253                 /* Reload everything ... */
6254                 break;
6256         case REQ_VIEW_BLAME:
6257                 if (stage_status.new.name[0]) {
6258                         string_copy(opt_file, stage_status.new.name);
6259                         opt_ref[0] = 0;
6260                 }
6261                 return request;
6263         case REQ_ENTER:
6264                 return pager_request(view, request, line);
6266         default:
6267                 return request;
6268         }
6270         VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
6271         open_view(view, REQ_VIEW_STATUS, OPEN_REFRESH);
6273         /* Check whether the staged entry still exists, and close the
6274          * stage view if it doesn't. */
6275         if (!status_exists(&stage_status, stage_line_type)) {
6276                 status_restore(VIEW(REQ_VIEW_STATUS));
6277                 return REQ_VIEW_CLOSE;
6278         }
6280         if (stage_line_type == LINE_STAT_UNTRACKED) {
6281                 if (!suffixcmp(stage_status.new.name, -1, "/")) {
6282                         report("Cannot display a directory");
6283                         return REQ_NONE;
6284                 }
6286                 if (!prepare_update_file(view, stage_status.new.name)) {
6287                         report("Failed to open file: %s", strerror(errno));
6288                         return REQ_NONE;
6289                 }
6290         }
6291         open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
6293         return REQ_NONE;
6296 static struct view_ops stage_ops = {
6297         "line",
6298         NULL,
6299         NULL,
6300         pager_read,
6301         pager_draw,
6302         stage_request,
6303         pager_grep,
6304         pager_select,
6305 };
6308 /*
6309  * Revision graph
6310  */
6312 struct commit {
6313         char id[SIZEOF_REV];            /* SHA1 ID. */
6314         char title[128];                /* First line of the commit message. */
6315         const char *author;             /* Author of the commit. */
6316         struct time time;               /* Date from the author ident. */
6317         struct ref_list *refs;          /* Repository references. */
6318         chtype graph[SIZEOF_REVGRAPH];  /* Ancestry chain graphics. */
6319         size_t graph_size;              /* The width of the graph array. */
6320         bool has_parents;               /* Rewritten --parents seen. */
6321 };
6323 /* Size of rev graph with no  "padding" columns */
6324 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
6326 struct rev_graph {
6327         struct rev_graph *prev, *next, *parents;
6328         char rev[SIZEOF_REVITEMS][SIZEOF_REV];
6329         size_t size;
6330         struct commit *commit;
6331         size_t pos;
6332         unsigned int boundary:1;
6333 };
6335 /* Parents of the commit being visualized. */
6336 static struct rev_graph graph_parents[4];
6338 /* The current stack of revisions on the graph. */
6339 static struct rev_graph graph_stacks[4] = {
6340         { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
6341         { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
6342         { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
6343         { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
6344 };
6346 static inline bool
6347 graph_parent_is_merge(struct rev_graph *graph)
6349         return graph->parents->size > 1;
6352 static inline void
6353 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
6355         struct commit *commit = graph->commit;
6357         if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
6358                 commit->graph[commit->graph_size++] = symbol;
6361 static void
6362 clear_rev_graph(struct rev_graph *graph)
6364         graph->boundary = 0;
6365         graph->size = graph->pos = 0;
6366         graph->commit = NULL;
6367         memset(graph->parents, 0, sizeof(*graph->parents));
6370 static void
6371 done_rev_graph(struct rev_graph *graph)
6373         if (graph_parent_is_merge(graph) &&
6374             graph->pos < graph->size - 1 &&
6375             graph->next->size == graph->size + graph->parents->size - 1) {
6376                 size_t i = graph->pos + graph->parents->size - 1;
6378                 graph->commit->graph_size = i * 2;
6379                 while (i < graph->next->size - 1) {
6380                         append_to_rev_graph(graph, ' ');
6381                         append_to_rev_graph(graph, '\\');
6382                         i++;
6383                 }
6384         }
6386         clear_rev_graph(graph);
6389 static void
6390 push_rev_graph(struct rev_graph *graph, const char *parent)
6392         int i;
6394         /* "Collapse" duplicate parents lines.
6395          *
6396          * FIXME: This needs to also update update the drawn graph but
6397          * for now it just serves as a method for pruning graph lines. */
6398         for (i = 0; i < graph->size; i++)
6399                 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
6400                         return;
6402         if (graph->size < SIZEOF_REVITEMS) {
6403                 string_copy_rev(graph->rev[graph->size++], parent);
6404         }
6407 static chtype
6408 get_rev_graph_symbol(struct rev_graph *graph)
6410         chtype symbol;
6412         if (graph->boundary)
6413                 symbol = REVGRAPH_BOUND;
6414         else if (graph->parents->size == 0)
6415                 symbol = REVGRAPH_INIT;
6416         else if (graph_parent_is_merge(graph))
6417                 symbol = REVGRAPH_MERGE;
6418         else if (graph->pos >= graph->size)
6419                 symbol = REVGRAPH_BRANCH;
6420         else
6421                 symbol = REVGRAPH_COMMIT;
6423         return symbol;
6426 static void
6427 draw_rev_graph(struct rev_graph *graph)
6429         struct rev_filler {
6430                 chtype separator, line;
6431         };
6432         enum { DEFAULT, RSHARP, RDIAG, LDIAG };
6433         static struct rev_filler fillers[] = {
6434                 { ' ',  '|' },
6435                 { '`',  '.' },
6436                 { '\'', ' ' },
6437                 { '/',  ' ' },
6438         };
6439         chtype symbol = get_rev_graph_symbol(graph);
6440         struct rev_filler *filler;
6441         size_t i;
6443         if (opt_line_graphics)
6444                 fillers[DEFAULT].line = line_graphics[LINE_GRAPHIC_VLINE];
6446         filler = &fillers[DEFAULT];
6448         for (i = 0; i < graph->pos; i++) {
6449                 append_to_rev_graph(graph, filler->line);
6450                 if (graph_parent_is_merge(graph->prev) &&
6451                     graph->prev->pos == i)
6452                         filler = &fillers[RSHARP];
6454                 append_to_rev_graph(graph, filler->separator);
6455         }
6457         /* Place the symbol for this revision. */
6458         append_to_rev_graph(graph, symbol);
6460         if (graph->prev->size > graph->size)
6461                 filler = &fillers[RDIAG];
6462         else
6463                 filler = &fillers[DEFAULT];
6465         i++;
6467         for (; i < graph->size; i++) {
6468                 append_to_rev_graph(graph, filler->separator);
6469                 append_to_rev_graph(graph, filler->line);
6470                 if (graph_parent_is_merge(graph->prev) &&
6471                     i < graph->prev->pos + graph->parents->size)
6472                         filler = &fillers[RSHARP];
6473                 if (graph->prev->size > graph->size)
6474                         filler = &fillers[LDIAG];
6475         }
6477         if (graph->prev->size > graph->size) {
6478                 append_to_rev_graph(graph, filler->separator);
6479                 if (filler->line != ' ')
6480                         append_to_rev_graph(graph, filler->line);
6481         }
6484 /* Prepare the next rev graph */
6485 static void
6486 prepare_rev_graph(struct rev_graph *graph)
6488         size_t i;
6490         /* First, traverse all lines of revisions up to the active one. */
6491         for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
6492                 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
6493                         break;
6495                 push_rev_graph(graph->next, graph->rev[graph->pos]);
6496         }
6498         /* Interleave the new revision parent(s). */
6499         for (i = 0; !graph->boundary && i < graph->parents->size; i++)
6500                 push_rev_graph(graph->next, graph->parents->rev[i]);
6502         /* Lastly, put any remaining revisions. */
6503         for (i = graph->pos + 1; i < graph->size; i++)
6504                 push_rev_graph(graph->next, graph->rev[i]);
6507 static void
6508 update_rev_graph(struct view *view, struct rev_graph *graph)
6510         /* If this is the finalizing update ... */
6511         if (graph->commit)
6512                 prepare_rev_graph(graph);
6514         /* Graph visualization needs a one rev look-ahead,
6515          * so the first update doesn't visualize anything. */
6516         if (!graph->prev->commit)
6517                 return;
6519         if (view->lines > 2)
6520                 view->line[view->lines - 3].dirty = 1;
6521         if (view->lines > 1)
6522                 view->line[view->lines - 2].dirty = 1;
6523         draw_rev_graph(graph->prev);
6524         done_rev_graph(graph->prev->prev);
6528 /*
6529  * Main view backend
6530  */
6532 static const char *main_argv[SIZEOF_ARG] = {
6533         "git", "log", "--no-color", "--pretty=raw", "--parents",
6534                       "--topo-order", "%(head)", NULL
6535 };
6537 static bool
6538 main_draw(struct view *view, struct line *line, unsigned int lineno)
6540         struct commit *commit = line->data;
6542         if (!commit->author)
6543                 return FALSE;
6545         if (opt_date && draw_date(view, &commit->time))
6546                 return TRUE;
6548         if (opt_author && draw_author(view, commit->author))
6549                 return TRUE;
6551         if (opt_rev_graph && commit->graph_size &&
6552             draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
6553                 return TRUE;
6555         if (opt_show_refs && commit->refs) {
6556                 size_t i;
6558                 for (i = 0; i < commit->refs->size; i++) {
6559                         struct ref *ref = commit->refs->refs[i];
6560                         enum line_type type;
6562                         if (ref->head)
6563                                 type = LINE_MAIN_HEAD;
6564                         else if (ref->ltag)
6565                                 type = LINE_MAIN_LOCAL_TAG;
6566                         else if (ref->tag)
6567                                 type = LINE_MAIN_TAG;
6568                         else if (ref->tracked)
6569                                 type = LINE_MAIN_TRACKED;
6570                         else if (ref->remote)
6571                                 type = LINE_MAIN_REMOTE;
6572                         else
6573                                 type = LINE_MAIN_REF;
6575                         if (draw_text(view, type, "[", TRUE) ||
6576                             draw_text(view, type, ref->name, TRUE) ||
6577                             draw_text(view, type, "]", TRUE))
6578                                 return TRUE;
6580                         if (draw_text(view, LINE_DEFAULT, " ", TRUE))
6581                                 return TRUE;
6582                 }
6583         }
6585         draw_text(view, LINE_DEFAULT, commit->title, TRUE);
6586         return TRUE;
6589 /* Reads git log --pretty=raw output and parses it into the commit struct. */
6590 static bool
6591 main_read(struct view *view, char *line)
6593         static struct rev_graph *graph = graph_stacks;
6594         enum line_type type;
6595         struct commit *commit;
6597         if (!line) {
6598                 int i;
6600                 if (!view->lines && !view->parent)
6601                         die("No revisions match the given arguments.");
6602                 if (view->lines > 0) {
6603                         commit = view->line[view->lines - 1].data;
6604                         view->line[view->lines - 1].dirty = 1;
6605                         if (!commit->author) {
6606                                 view->lines--;
6607                                 free(commit);
6608                                 graph->commit = NULL;
6609                         }
6610                 }
6611                 update_rev_graph(view, graph);
6613                 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
6614                         clear_rev_graph(&graph_stacks[i]);
6615                 return TRUE;
6616         }
6618         type = get_line_type(line);
6619         if (type == LINE_COMMIT) {
6620                 commit = calloc(1, sizeof(struct commit));
6621                 if (!commit)
6622                         return FALSE;
6624                 line += STRING_SIZE("commit ");
6625                 if (*line == '-') {
6626                         graph->boundary = 1;
6627                         line++;
6628                 }
6630                 string_copy_rev(commit->id, line);
6631                 commit->refs = get_ref_list(commit->id);
6632                 graph->commit = commit;
6633                 add_line_data(view, commit, LINE_MAIN_COMMIT);
6635                 while ((line = strchr(line, ' '))) {
6636                         line++;
6637                         push_rev_graph(graph->parents, line);
6638                         commit->has_parents = TRUE;
6639                 }
6640                 return TRUE;
6641         }
6643         if (!view->lines)
6644                 return TRUE;
6645         commit = view->line[view->lines - 1].data;
6647         switch (type) {
6648         case LINE_PARENT:
6649                 if (commit->has_parents)
6650                         break;
6651                 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
6652                 break;
6654         case LINE_AUTHOR:
6655                 parse_author_line(line + STRING_SIZE("author "),
6656                                   &commit->author, &commit->time);
6657                 update_rev_graph(view, graph);
6658                 graph = graph->next;
6659                 break;
6661         default:
6662                 /* Fill in the commit title if it has not already been set. */
6663                 if (commit->title[0])
6664                         break;
6666                 /* Require titles to start with a non-space character at the
6667                  * offset used by git log. */
6668                 if (strncmp(line, "    ", 4))
6669                         break;
6670                 line += 4;
6671                 /* Well, if the title starts with a whitespace character,
6672                  * try to be forgiving.  Otherwise we end up with no title. */
6673                 while (isspace(*line))
6674                         line++;
6675                 if (*line == '\0')
6676                         break;
6677                 /* FIXME: More graceful handling of titles; append "..." to
6678                  * shortened titles, etc. */
6680                 string_expand(commit->title, sizeof(commit->title), line, 1);
6681                 view->line[view->lines - 1].dirty = 1;
6682         }
6684         return TRUE;
6687 static enum request
6688 main_request(struct view *view, enum request request, struct line *line)
6690         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
6692         switch (request) {
6693         case REQ_ENTER:
6694                 open_view(view, REQ_VIEW_DIFF, flags);
6695                 break;
6696         case REQ_REFRESH:
6697                 load_refs();
6698                 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
6699                 break;
6700         default:
6701                 return request;
6702         }
6704         return REQ_NONE;
6707 static bool
6708 grep_refs(struct ref_list *list, regex_t *regex)
6710         regmatch_t pmatch;
6711         size_t i;
6713         if (!opt_show_refs || !list)
6714                 return FALSE;
6716         for (i = 0; i < list->size; i++) {
6717                 if (regexec(regex, list->refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
6718                         return TRUE;
6719         }
6721         return FALSE;
6724 static bool
6725 main_grep(struct view *view, struct line *line)
6727         struct commit *commit = line->data;
6728         const char *text[] = {
6729                 commit->title,
6730                 opt_author ? commit->author : "",
6731                 opt_date ? mkdate(&commit->time) : "",
6732                 NULL
6733         };
6735         return grep_text(view, text) || grep_refs(commit->refs, view->regex);
6738 static void
6739 main_select(struct view *view, struct line *line)
6741         struct commit *commit = line->data;
6743         string_copy_rev(view->ref, commit->id);
6744         string_copy_rev(ref_commit, view->ref);
6747 static struct view_ops main_ops = {
6748         "commit",
6749         main_argv,
6750         NULL,
6751         main_read,
6752         main_draw,
6753         main_request,
6754         main_grep,
6755         main_select,
6756 };
6759 /*
6760  * Unicode / UTF-8 handling
6761  *
6762  * NOTE: Much of the following code for dealing with Unicode is derived from
6763  * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
6764  * src/intl/charset.c from the UTF-8 branch commit elinks-0.11.0-g31f2c28.
6765  */
6767 static inline int
6768 unicode_width(unsigned long c)
6770         if (c >= 0x1100 &&
6771            (c <= 0x115f                         /* Hangul Jamo */
6772             || c == 0x2329
6773             || c == 0x232a
6774             || (c >= 0x2e80  && c <= 0xa4cf && c != 0x303f)
6775                                                 /* CJK ... Yi */
6776             || (c >= 0xac00  && c <= 0xd7a3)    /* Hangul Syllables */
6777             || (c >= 0xf900  && c <= 0xfaff)    /* CJK Compatibility Ideographs */
6778             || (c >= 0xfe30  && c <= 0xfe6f)    /* CJK Compatibility Forms */
6779             || (c >= 0xff00  && c <= 0xff60)    /* Fullwidth Forms */
6780             || (c >= 0xffe0  && c <= 0xffe6)
6781             || (c >= 0x20000 && c <= 0x2fffd)
6782             || (c >= 0x30000 && c <= 0x3fffd)))
6783                 return 2;
6785         if (c == '\t')
6786                 return opt_tab_size;
6788         return 1;
6791 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
6792  * Illegal bytes are set one. */
6793 static const unsigned char utf8_bytes[256] = {
6794         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,
6795         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,
6796         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,
6797         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,
6798         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,
6799         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,
6800         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,
6801         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,
6802 };
6804 static inline unsigned char
6805 utf8_char_length(const char *string, const char *end)
6807         int c = *(unsigned char *) string;
6809         return utf8_bytes[c];
6812 /* Decode UTF-8 multi-byte representation into a Unicode character. */
6813 static inline unsigned long
6814 utf8_to_unicode(const char *string, size_t length)
6816         unsigned long unicode;
6818         switch (length) {
6819         case 1:
6820                 unicode  =   string[0];
6821                 break;
6822         case 2:
6823                 unicode  =  (string[0] & 0x1f) << 6;
6824                 unicode +=  (string[1] & 0x3f);
6825                 break;
6826         case 3:
6827                 unicode  =  (string[0] & 0x0f) << 12;
6828                 unicode += ((string[1] & 0x3f) << 6);
6829                 unicode +=  (string[2] & 0x3f);
6830                 break;
6831         case 4:
6832                 unicode  =  (string[0] & 0x0f) << 18;
6833                 unicode += ((string[1] & 0x3f) << 12);
6834                 unicode += ((string[2] & 0x3f) << 6);
6835                 unicode +=  (string[3] & 0x3f);
6836                 break;
6837         case 5:
6838                 unicode  =  (string[0] & 0x0f) << 24;
6839                 unicode += ((string[1] & 0x3f) << 18);
6840                 unicode += ((string[2] & 0x3f) << 12);
6841                 unicode += ((string[3] & 0x3f) << 6);
6842                 unicode +=  (string[4] & 0x3f);
6843                 break;
6844         case 6:
6845                 unicode  =  (string[0] & 0x01) << 30;
6846                 unicode += ((string[1] & 0x3f) << 24);
6847                 unicode += ((string[2] & 0x3f) << 18);
6848                 unicode += ((string[3] & 0x3f) << 12);
6849                 unicode += ((string[4] & 0x3f) << 6);
6850                 unicode +=  (string[5] & 0x3f);
6851                 break;
6852         default:
6853                 die("Invalid Unicode length");
6854         }
6856         /* Invalid characters could return the special 0xfffd value but NUL
6857          * should be just as good. */
6858         return unicode > 0xffff ? 0 : unicode;
6861 /* Calculates how much of string can be shown within the given maximum width
6862  * and sets trimmed parameter to non-zero value if all of string could not be
6863  * shown. If the reserve flag is TRUE, it will reserve at least one
6864  * trailing character, which can be useful when drawing a delimiter.
6865  *
6866  * Returns the number of bytes to output from string to satisfy max_width. */
6867 static size_t
6868 utf8_length(const char **start, size_t skip, int *width, size_t max_width, int *trimmed, bool reserve)
6870         const char *string = *start;
6871         const char *end = strchr(string, '\0');
6872         unsigned char last_bytes = 0;
6873         size_t last_ucwidth = 0;
6875         *width = 0;
6876         *trimmed = 0;
6878         while (string < end) {
6879                 unsigned char bytes = utf8_char_length(string, end);
6880                 size_t ucwidth;
6881                 unsigned long unicode;
6883                 if (string + bytes > end)
6884                         break;
6886                 /* Change representation to figure out whether
6887                  * it is a single- or double-width character. */
6889                 unicode = utf8_to_unicode(string, bytes);
6890                 /* FIXME: Graceful handling of invalid Unicode character. */
6891                 if (!unicode)
6892                         break;
6894                 ucwidth = unicode_width(unicode);
6895                 if (skip > 0) {
6896                         skip -= ucwidth <= skip ? ucwidth : skip;
6897                         *start += bytes;
6898                 }
6899                 *width  += ucwidth;
6900                 if (*width > max_width) {
6901                         *trimmed = 1;
6902                         *width -= ucwidth;
6903                         if (reserve && *width == max_width) {
6904                                 string -= last_bytes;
6905                                 *width -= last_ucwidth;
6906                         }
6907                         break;
6908                 }
6910                 string  += bytes;
6911                 last_bytes = ucwidth ? bytes : 0;
6912                 last_ucwidth = ucwidth;
6913         }
6915         return string - *start;
6919 /*
6920  * Status management
6921  */
6923 /* Whether or not the curses interface has been initialized. */
6924 static bool cursed = FALSE;
6926 /* Terminal hacks and workarounds. */
6927 static bool use_scroll_redrawwin;
6928 static bool use_scroll_status_wclear;
6930 /* The status window is used for polling keystrokes. */
6931 static WINDOW *status_win;
6933 /* Reading from the prompt? */
6934 static bool input_mode = FALSE;
6936 static bool status_empty = FALSE;
6938 /* Update status and title window. */
6939 static void
6940 report(const char *msg, ...)
6942         struct view *view = display[current_view];
6944         if (input_mode)
6945                 return;
6947         if (!view) {
6948                 char buf[SIZEOF_STR];
6949                 va_list args;
6951                 va_start(args, msg);
6952                 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
6953                         buf[sizeof(buf) - 1] = 0;
6954                         buf[sizeof(buf) - 2] = '.';
6955                         buf[sizeof(buf) - 3] = '.';
6956                         buf[sizeof(buf) - 4] = '.';
6957                 }
6958                 va_end(args);
6959                 die("%s", buf);
6960         }
6962         if (!status_empty || *msg) {
6963                 va_list args;
6965                 va_start(args, msg);
6967                 wmove(status_win, 0, 0);
6968                 if (view->has_scrolled && use_scroll_status_wclear)
6969                         wclear(status_win);
6970                 if (*msg) {
6971                         vwprintw(status_win, msg, args);
6972                         status_empty = FALSE;
6973                 } else {
6974                         status_empty = TRUE;
6975                 }
6976                 wclrtoeol(status_win);
6977                 wnoutrefresh(status_win);
6979                 va_end(args);
6980         }
6982         update_view_title(view);
6985 /* Controls when nodelay should be in effect when polling user input. */
6986 static void
6987 set_nonblocking_input(bool loading)
6989         static unsigned int loading_views;
6991         if ((loading == FALSE && loading_views-- == 1) ||
6992             (loading == TRUE  && loading_views++ == 0))
6993                 nodelay(status_win, loading);
6996 static void
6997 init_display(void)
6999         const char *term;
7000         int x, y;
7002         /* Initialize the curses library */
7003         if (isatty(STDIN_FILENO)) {
7004                 cursed = !!initscr();
7005                 opt_tty = stdin;
7006         } else {
7007                 /* Leave stdin and stdout alone when acting as a pager. */
7008                 opt_tty = fopen("/dev/tty", "r+");
7009                 if (!opt_tty)
7010                         die("Failed to open /dev/tty");
7011                 cursed = !!newterm(NULL, opt_tty, opt_tty);
7012         }
7014         if (!cursed)
7015                 die("Failed to initialize curses");
7017         nonl();         /* Disable conversion and detect newlines from input. */
7018         cbreak();       /* Take input chars one at a time, no wait for \n */
7019         noecho();       /* Don't echo input */
7020         leaveok(stdscr, FALSE);
7022         if (has_colors())
7023                 init_colors();
7025         getmaxyx(stdscr, y, x);
7026         status_win = newwin(1, 0, y - 1, 0);
7027         if (!status_win)
7028                 die("Failed to create status window");
7030         /* Enable keyboard mapping */
7031         keypad(status_win, TRUE);
7032         wbkgdset(status_win, get_line_attr(LINE_STATUS));
7034         TABSIZE = opt_tab_size;
7035         if (opt_line_graphics) {
7036                 line_graphics[LINE_GRAPHIC_VLINE] = ACS_VLINE;
7037         }
7039         term = getenv("XTERM_VERSION") ? NULL : getenv("COLORTERM");
7040         if (term && !strcmp(term, "gnome-terminal")) {
7041                 /* In the gnome-terminal-emulator, the message from
7042                  * scrolling up one line when impossible followed by
7043                  * scrolling down one line causes corruption of the
7044                  * status line. This is fixed by calling wclear. */
7045                 use_scroll_status_wclear = TRUE;
7046                 use_scroll_redrawwin = FALSE;
7048         } else if (term && !strcmp(term, "xrvt-xpm")) {
7049                 /* No problems with full optimizations in xrvt-(unicode)
7050                  * and aterm. */
7051                 use_scroll_status_wclear = use_scroll_redrawwin = FALSE;
7053         } else {
7054                 /* When scrolling in (u)xterm the last line in the
7055                  * scrolling direction will update slowly. */
7056                 use_scroll_redrawwin = TRUE;
7057                 use_scroll_status_wclear = FALSE;
7058         }
7061 static int
7062 get_input(int prompt_position)
7064         struct view *view;
7065         int i, key, cursor_y, cursor_x;
7067         if (prompt_position)
7068                 input_mode = TRUE;
7070         while (TRUE) {
7071                 foreach_view (view, i) {
7072                         update_view(view);
7073                         if (view_is_displayed(view) && view->has_scrolled &&
7074                             use_scroll_redrawwin)
7075                                 redrawwin(view->win);
7076                         view->has_scrolled = FALSE;
7077                 }
7079                 /* Update the cursor position. */
7080                 if (prompt_position) {
7081                         getbegyx(status_win, cursor_y, cursor_x);
7082                         cursor_x = prompt_position;
7083                 } else {
7084                         view = display[current_view];
7085                         getbegyx(view->win, cursor_y, cursor_x);
7086                         cursor_x = view->width - 1;
7087                         cursor_y += view->lineno - view->offset;
7088                 }
7089                 setsyx(cursor_y, cursor_x);
7091                 /* Refresh, accept single keystroke of input */
7092                 doupdate();
7093                 key = wgetch(status_win);
7095                 /* wgetch() with nodelay() enabled returns ERR when
7096                  * there's no input. */
7097                 if (key == ERR) {
7099                 } else if (key == KEY_RESIZE) {
7100                         int height, width;
7102                         getmaxyx(stdscr, height, width);
7104                         wresize(status_win, 1, width);
7105                         mvwin(status_win, height - 1, 0);
7106                         wnoutrefresh(status_win);
7107                         resize_display();
7108                         redraw_display(TRUE);
7110                 } else {
7111                         input_mode = FALSE;
7112                         return key;
7113                 }
7114         }
7117 static char *
7118 prompt_input(const char *prompt, input_handler handler, void *data)
7120         enum input_status status = INPUT_OK;
7121         static char buf[SIZEOF_STR];
7122         size_t pos = 0;
7124         buf[pos] = 0;
7126         while (status == INPUT_OK || status == INPUT_SKIP) {
7127                 int key;
7129                 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
7130                 wclrtoeol(status_win);
7132                 key = get_input(pos + 1);
7133                 switch (key) {
7134                 case KEY_RETURN:
7135                 case KEY_ENTER:
7136                 case '\n':
7137                         status = pos ? INPUT_STOP : INPUT_CANCEL;
7138                         break;
7140                 case KEY_BACKSPACE:
7141                         if (pos > 0)
7142                                 buf[--pos] = 0;
7143                         else
7144                                 status = INPUT_CANCEL;
7145                         break;
7147                 case KEY_ESC:
7148                         status = INPUT_CANCEL;
7149                         break;
7151                 default:
7152                         if (pos >= sizeof(buf)) {
7153                                 report("Input string too long");
7154                                 return NULL;
7155                         }
7157                         status = handler(data, buf, key);
7158                         if (status == INPUT_OK)
7159                                 buf[pos++] = (char) key;
7160                 }
7161         }
7163         /* Clear the status window */
7164         status_empty = FALSE;
7165         report("");
7167         if (status == INPUT_CANCEL)
7168                 return NULL;
7170         buf[pos++] = 0;
7172         return buf;
7175 static enum input_status
7176 prompt_yesno_handler(void *data, char *buf, int c)
7178         if (c == 'y' || c == 'Y')
7179                 return INPUT_STOP;
7180         if (c == 'n' || c == 'N')
7181                 return INPUT_CANCEL;
7182         return INPUT_SKIP;
7185 static bool
7186 prompt_yesno(const char *prompt)
7188         char prompt2[SIZEOF_STR];
7190         if (!string_format(prompt2, "%s [Yy/Nn]", prompt))
7191                 return FALSE;
7193         return !!prompt_input(prompt2, prompt_yesno_handler, NULL);
7196 static enum input_status
7197 read_prompt_handler(void *data, char *buf, int c)
7199         return isprint(c) ? INPUT_OK : INPUT_SKIP;
7202 static char *
7203 read_prompt(const char *prompt)
7205         return prompt_input(prompt, read_prompt_handler, NULL);
7208 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected)
7210         enum input_status status = INPUT_OK;
7211         int size = 0;
7213         while (items[size].text)
7214                 size++;
7216         while (status == INPUT_OK) {
7217                 const struct menu_item *item = &items[*selected];
7218                 int key;
7219                 int i;
7221                 mvwprintw(status_win, 0, 0, "%s (%d of %d) ",
7222                           prompt, *selected + 1, size);
7223                 if (item->hotkey)
7224                         wprintw(status_win, "[%c] ", (char) item->hotkey);
7225                 wprintw(status_win, "%s", item->text);
7226                 wclrtoeol(status_win);
7228                 key = get_input(COLS - 1);
7229                 switch (key) {
7230                 case KEY_RETURN:
7231                 case KEY_ENTER:
7232                 case '\n':
7233                         status = INPUT_STOP;
7234                         break;
7236                 case KEY_LEFT:
7237                 case KEY_UP:
7238                         *selected = *selected - 1;
7239                         if (*selected < 0)
7240                                 *selected = size - 1;
7241                         break;
7243                 case KEY_RIGHT:
7244                 case KEY_DOWN:
7245                         *selected = (*selected + 1) % size;
7246                         break;
7248                 case KEY_ESC:
7249                         status = INPUT_CANCEL;
7250                         break;
7252                 default:
7253                         for (i = 0; items[i].text; i++)
7254                                 if (items[i].hotkey == key) {
7255                                         *selected = i;
7256                                         status = INPUT_STOP;
7257                                         break;
7258                                 }
7259                 }
7260         }
7262         /* Clear the status window */
7263         status_empty = FALSE;
7264         report("");
7266         return status != INPUT_CANCEL;
7269 /*
7270  * Repository properties
7271  */
7273 static struct ref **refs = NULL;
7274 static size_t refs_size = 0;
7276 static struct ref_list **ref_lists = NULL;
7277 static size_t ref_lists_size = 0;
7279 DEFINE_ALLOCATOR(realloc_refs, struct ref *, 256)
7280 DEFINE_ALLOCATOR(realloc_refs_list, struct ref *, 8)
7281 DEFINE_ALLOCATOR(realloc_ref_lists, struct ref_list *, 8)
7283 static int
7284 compare_refs(const void *ref1_, const void *ref2_)
7286         const struct ref *ref1 = *(const struct ref **)ref1_;
7287         const struct ref *ref2 = *(const struct ref **)ref2_;
7289         if (ref1->tag != ref2->tag)
7290                 return ref2->tag - ref1->tag;
7291         if (ref1->ltag != ref2->ltag)
7292                 return ref2->ltag - ref2->ltag;
7293         if (ref1->head != ref2->head)
7294                 return ref2->head - ref1->head;
7295         if (ref1->tracked != ref2->tracked)
7296                 return ref2->tracked - ref1->tracked;
7297         if (ref1->remote != ref2->remote)
7298                 return ref2->remote - ref1->remote;
7299         return strcmp(ref1->name, ref2->name);
7302 static void
7303 foreach_ref(bool (*visitor)(void *data, const struct ref *ref), void *data)
7305         size_t i;
7307         for (i = 0; i < refs_size; i++)
7308                 if (!visitor(data, refs[i]))
7309                         break;
7312 static struct ref_list *
7313 get_ref_list(const char *id)
7315         struct ref_list *list;
7316         size_t i;
7318         for (i = 0; i < ref_lists_size; i++)
7319                 if (!strcmp(id, ref_lists[i]->id))
7320                         return ref_lists[i];
7322         if (!realloc_ref_lists(&ref_lists, ref_lists_size, 1))
7323                 return NULL;
7324         list = calloc(1, sizeof(*list));
7325         if (!list)
7326                 return NULL;
7328         for (i = 0; i < refs_size; i++) {
7329                 if (!strcmp(id, refs[i]->id) &&
7330                     realloc_refs_list(&list->refs, list->size, 1))
7331                         list->refs[list->size++] = refs[i];
7332         }
7334         if (!list->refs) {
7335                 free(list);
7336                 return NULL;
7337         }
7339         qsort(list->refs, list->size, sizeof(*list->refs), compare_refs);
7340         ref_lists[ref_lists_size++] = list;
7341         return list;
7344 static int
7345 read_ref(char *id, size_t idlen, char *name, size_t namelen)
7347         struct ref *ref = NULL;
7348         bool tag = FALSE;
7349         bool ltag = FALSE;
7350         bool remote = FALSE;
7351         bool tracked = FALSE;
7352         bool head = FALSE;
7353         int from = 0, to = refs_size - 1;
7355         if (!prefixcmp(name, "refs/tags/")) {
7356                 if (!suffixcmp(name, namelen, "^{}")) {
7357                         namelen -= 3;
7358                         name[namelen] = 0;
7359                 } else {
7360                         ltag = TRUE;
7361                 }
7363                 tag = TRUE;
7364                 namelen -= STRING_SIZE("refs/tags/");
7365                 name    += STRING_SIZE("refs/tags/");
7367         } else if (!prefixcmp(name, "refs/remotes/")) {
7368                 remote = TRUE;
7369                 namelen -= STRING_SIZE("refs/remotes/");
7370                 name    += STRING_SIZE("refs/remotes/");
7371                 tracked  = !strcmp(opt_remote, name);
7373         } else if (!prefixcmp(name, "refs/heads/")) {
7374                 namelen -= STRING_SIZE("refs/heads/");
7375                 name    += STRING_SIZE("refs/heads/");
7376                 head     = !strncmp(opt_head, name, namelen);
7378         } else if (!strcmp(name, "HEAD")) {
7379                 string_ncopy(opt_head_rev, id, idlen);
7380                 return OK;
7381         }
7383         /* If we are reloading or it's an annotated tag, replace the
7384          * previous SHA1 with the resolved commit id; relies on the fact
7385          * git-ls-remote lists the commit id of an annotated tag right
7386          * before the commit id it points to. */
7387         while (from <= to) {
7388                 size_t pos = (to + from) / 2;
7389                 int cmp = strcmp(name, refs[pos]->name);
7391                 if (!cmp) {
7392                         ref = refs[pos];
7393                         break;
7394                 }
7396                 if (cmp < 0)
7397                         to = pos - 1;
7398                 else
7399                         from = pos + 1;
7400         }
7402         if (!ref) {
7403                 if (!realloc_refs(&refs, refs_size, 1))
7404                         return ERR;
7405                 ref = calloc(1, sizeof(*ref) + namelen);
7406                 if (!ref)
7407                         return ERR;
7408                 memmove(refs + from + 1, refs + from,
7409                         (refs_size - from) * sizeof(*refs));
7410                 refs[from] = ref;
7411                 strncpy(ref->name, name, namelen);
7412                 refs_size++;
7413         }
7415         ref->head = head;
7416         ref->tag = tag;
7417         ref->ltag = ltag;
7418         ref->remote = remote;
7419         ref->tracked = tracked;
7420         string_copy_rev(ref->id, id);
7422         return OK;
7425 static int
7426 load_refs(void)
7428         const char *head_argv[] = {
7429                 "git", "symbolic-ref", "HEAD", NULL
7430         };
7431         static const char *ls_remote_argv[SIZEOF_ARG] = {
7432                 "git", "ls-remote", opt_git_dir, NULL
7433         };
7434         static bool init = FALSE;
7435         size_t i;
7437         if (!init) {
7438                 argv_from_env(ls_remote_argv, "TIG_LS_REMOTE");
7439                 init = TRUE;
7440         }
7442         if (!*opt_git_dir)
7443                 return OK;
7445         if (run_io_buf(head_argv, opt_head, sizeof(opt_head)) &&
7446             !prefixcmp(opt_head, "refs/heads/")) {
7447                 char *offset = opt_head + STRING_SIZE("refs/heads/");
7449                 memmove(opt_head, offset, strlen(offset) + 1);
7450         }
7452         for (i = 0; i < refs_size; i++)
7453                 refs[i]->id[0] = 0;
7455         if (run_io_load(ls_remote_argv, "\t", read_ref) == ERR)
7456                 return ERR;
7458         /* Update the ref lists to reflect changes. */
7459         for (i = 0; i < ref_lists_size; i++) {
7460                 struct ref_list *list = ref_lists[i];
7461                 size_t old, new;
7463                 for (old = new = 0; old < list->size; old++)
7464                         if (!strcmp(list->id, list->refs[old]->id))
7465                                 list->refs[new++] = list->refs[old];
7466                 list->size = new;
7467         }
7469         return OK;
7472 static void
7473 set_remote_branch(const char *name, const char *value, size_t valuelen)
7475         if (!strcmp(name, ".remote")) {
7476                 string_ncopy(opt_remote, value, valuelen);
7478         } else if (*opt_remote && !strcmp(name, ".merge")) {
7479                 size_t from = strlen(opt_remote);
7481                 if (!prefixcmp(value, "refs/heads/"))
7482                         value += STRING_SIZE("refs/heads/");
7484                 if (!string_format_from(opt_remote, &from, "/%s", value))
7485                         opt_remote[0] = 0;
7486         }
7489 static void
7490 set_repo_config_option(char *name, char *value, int (*cmd)(int, const char **))
7492         const char *argv[SIZEOF_ARG] = { name, "=" };
7493         int argc = 1 + (cmd == option_set_command);
7494         int error = ERR;
7496         if (!argv_from_string(argv, &argc, value))
7497                 config_msg = "Too many option arguments";
7498         else
7499                 error = cmd(argc, argv);
7501         if (error == ERR)
7502                 warn("Option 'tig.%s': %s", name, config_msg);
7505 static bool
7506 set_environment_variable(const char *name, const char *value)
7508         size_t len = strlen(name) + 1 + strlen(value) + 1;
7509         char *env = malloc(len);
7511         if (env &&
7512             string_nformat(env, len, NULL, "%s=%s", name, value) &&
7513             putenv(env) == 0)
7514                 return TRUE;
7515         free(env);
7516         return FALSE;
7519 static void
7520 set_work_tree(const char *value)
7522         char cwd[SIZEOF_STR];
7524         if (!getcwd(cwd, sizeof(cwd)))
7525                 die("Failed to get cwd path: %s", strerror(errno));
7526         if (chdir(opt_git_dir) < 0)
7527                 die("Failed to chdir(%s): %s", strerror(errno));
7528         if (!getcwd(opt_git_dir, sizeof(opt_git_dir)))
7529                 die("Failed to get git path: %s", strerror(errno));
7530         if (chdir(cwd) < 0)
7531                 die("Failed to chdir(%s): %s", cwd, strerror(errno));
7532         if (chdir(value) < 0)
7533                 die("Failed to chdir(%s): %s", value, strerror(errno));
7534         if (!getcwd(cwd, sizeof(cwd)))
7535                 die("Failed to get cwd path: %s", strerror(errno));
7536         if (!set_environment_variable("GIT_WORK_TREE", cwd))
7537                 die("Failed to set GIT_WORK_TREE to '%s'", cwd);
7538         if (!set_environment_variable("GIT_DIR", opt_git_dir))
7539                 die("Failed to set GIT_DIR to '%s'", opt_git_dir);
7540         opt_is_inside_work_tree = TRUE;
7543 static int
7544 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
7546         if (!strcmp(name, "i18n.commitencoding"))
7547                 string_ncopy(opt_encoding, value, valuelen);
7549         else if (!strcmp(name, "core.editor"))
7550                 string_ncopy(opt_editor, value, valuelen);
7552         else if (!strcmp(name, "core.worktree"))
7553                 set_work_tree(value);
7555         else if (!prefixcmp(name, "tig.color."))
7556                 set_repo_config_option(name + 10, value, option_color_command);
7558         else if (!prefixcmp(name, "tig.bind."))
7559                 set_repo_config_option(name + 9, value, option_bind_command);
7561         else if (!prefixcmp(name, "tig."))
7562                 set_repo_config_option(name + 4, value, option_set_command);
7564         else if (*opt_head && !prefixcmp(name, "branch.") &&
7565                  !strncmp(name + 7, opt_head, strlen(opt_head)))
7566                 set_remote_branch(name + 7 + strlen(opt_head), value, valuelen);
7568         return OK;
7571 static int
7572 load_git_config(void)
7574         const char *config_list_argv[] = { "git", "config", "--list", NULL };
7576         return run_io_load(config_list_argv, "=", read_repo_config_option);
7579 static int
7580 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
7582         if (!opt_git_dir[0]) {
7583                 string_ncopy(opt_git_dir, name, namelen);
7585         } else if (opt_is_inside_work_tree == -1) {
7586                 /* This can be 3 different values depending on the
7587                  * version of git being used. If git-rev-parse does not
7588                  * understand --is-inside-work-tree it will simply echo
7589                  * the option else either "true" or "false" is printed.
7590                  * Default to true for the unknown case. */
7591                 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
7593         } else if (*name == '.') {
7594                 string_ncopy(opt_cdup, name, namelen);
7596         } else {
7597                 string_ncopy(opt_prefix, name, namelen);
7598         }
7600         return OK;
7603 static int
7604 load_repo_info(void)
7606         const char *rev_parse_argv[] = {
7607                 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
7608                         "--show-cdup", "--show-prefix", NULL
7609         };
7611         return run_io_load(rev_parse_argv, "=", read_repo_info);
7615 /*
7616  * Main
7617  */
7619 static const char usage[] =
7620 "tig " TIG_VERSION " (" __DATE__ ")\n"
7621 "\n"
7622 "Usage: tig        [options] [revs] [--] [paths]\n"
7623 "   or: tig show   [options] [revs] [--] [paths]\n"
7624 "   or: tig blame  [rev] path\n"
7625 "   or: tig status\n"
7626 "   or: tig <      [git command output]\n"
7627 "\n"
7628 "Options:\n"
7629 "  -v, --version   Show version and exit\n"
7630 "  -h, --help      Show help message and exit";
7632 static void __NORETURN
7633 quit(int sig)
7635         /* XXX: Restore tty modes and let the OS cleanup the rest! */
7636         if (cursed)
7637                 endwin();
7638         exit(0);
7641 static void __NORETURN
7642 die(const char *err, ...)
7644         va_list args;
7646         endwin();
7648         va_start(args, err);
7649         fputs("tig: ", stderr);
7650         vfprintf(stderr, err, args);
7651         fputs("\n", stderr);
7652         va_end(args);
7654         exit(1);
7657 static void
7658 warn(const char *msg, ...)
7660         va_list args;
7662         va_start(args, msg);
7663         fputs("tig warning: ", stderr);
7664         vfprintf(stderr, msg, args);
7665         fputs("\n", stderr);
7666         va_end(args);
7669 static enum request
7670 parse_options(int argc, const char *argv[])
7672         enum request request = REQ_VIEW_MAIN;
7673         const char *subcommand;
7674         bool seen_dashdash = FALSE;
7675         /* XXX: This is vulnerable to the user overriding options
7676          * required for the main view parser. */
7677         const char *custom_argv[SIZEOF_ARG] = {
7678                 "git", "log", "--no-color", "--pretty=raw", "--parents",
7679                         "--topo-order", NULL
7680         };
7681         int i, j = 6;
7683         if (!isatty(STDIN_FILENO)) {
7684                 io_open(&VIEW(REQ_VIEW_PAGER)->io, "");
7685                 return REQ_VIEW_PAGER;
7686         }
7688         if (argc <= 1)
7689                 return REQ_NONE;
7691         subcommand = argv[1];
7692         if (!strcmp(subcommand, "status")) {
7693                 if (argc > 2)
7694                         warn("ignoring arguments after `%s'", subcommand);
7695                 return REQ_VIEW_STATUS;
7697         } else if (!strcmp(subcommand, "blame")) {
7698                 if (argc <= 2 || argc > 4)
7699                         die("invalid number of options to blame\n\n%s", usage);
7701                 i = 2;
7702                 if (argc == 4) {
7703                         string_ncopy(opt_ref, argv[i], strlen(argv[i]));
7704                         i++;
7705                 }
7707                 string_ncopy(opt_file, argv[i], strlen(argv[i]));
7708                 return REQ_VIEW_BLAME;
7710         } else if (!strcmp(subcommand, "show")) {
7711                 request = REQ_VIEW_DIFF;
7713         } else {
7714                 subcommand = NULL;
7715         }
7717         if (subcommand) {
7718                 custom_argv[1] = subcommand;
7719                 j = 2;
7720         }
7722         for (i = 1 + !!subcommand; i < argc; i++) {
7723                 const char *opt = argv[i];
7725                 if (seen_dashdash || !strcmp(opt, "--")) {
7726                         seen_dashdash = TRUE;
7728                 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
7729                         printf("tig version %s\n", TIG_VERSION);
7730                         quit(0);
7732                 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
7733                         printf("%s\n", usage);
7734                         quit(0);
7735                 }
7737                 custom_argv[j++] = opt;
7738                 if (j >= ARRAY_SIZE(custom_argv))
7739                         die("command too long");
7740         }
7742         if (!prepare_update(VIEW(request), custom_argv, NULL, FORMAT_NONE))
7743                 die("Failed to format arguments");
7745         return request;
7748 int
7749 main(int argc, const char *argv[])
7751         enum request request = parse_options(argc, argv);
7752         struct view *view;
7753         size_t i;
7755         signal(SIGINT, quit);
7756         signal(SIGPIPE, SIG_IGN);
7758         if (setlocale(LC_ALL, "")) {
7759                 char *codeset = nl_langinfo(CODESET);
7761                 string_ncopy(opt_codeset, codeset, strlen(codeset));
7762         }
7764         if (load_repo_info() == ERR)
7765                 die("Failed to load repo info.");
7767         if (load_options() == ERR)
7768                 die("Failed to load user config.");
7770         if (load_git_config() == ERR)
7771                 die("Failed to load repo config.");
7773         /* Require a git repository unless when running in pager mode. */
7774         if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
7775                 die("Not a git repository");
7777         if (*opt_encoding && strcmp(opt_codeset, "UTF-8")) {
7778                 opt_iconv_in = iconv_open("UTF-8", opt_encoding);
7779                 if (opt_iconv_in == ICONV_NONE)
7780                         die("Failed to initialize character set conversion");
7781         }
7783         if (*opt_codeset && strcmp(opt_codeset, "UTF-8")) {
7784                 opt_iconv_out = iconv_open(opt_codeset, "UTF-8");
7785                 if (opt_iconv_out == ICONV_NONE)
7786                         die("Failed to initialize character set conversion");
7787         }
7789         if (load_refs() == ERR)
7790                 die("Failed to load refs.");
7792         foreach_view (view, i)
7793                 argv_from_env(view->ops->argv, view->cmd_env);
7795         init_display();
7797         if (request != REQ_NONE)
7798                 open_view(NULL, request, OPEN_PREPARED);
7799         request = request == REQ_NONE ? REQ_VIEW_MAIN : REQ_NONE;
7801         while (view_driver(display[current_view], request)) {
7802                 int key = get_input(0);
7804                 view = display[current_view];
7805                 request = get_keybinding(view->keymap, key);
7807                 /* Some low-level request handling. This keeps access to
7808                  * status_win restricted. */
7809                 switch (request) {
7810                 case REQ_PROMPT:
7811                 {
7812                         char *cmd = read_prompt(":");
7814                         if (cmd && isdigit(*cmd)) {
7815                                 int lineno = view->lineno + 1;
7817                                 if (parse_int(&lineno, cmd, 1, view->lines + 1) == OK) {
7818                                         select_view_line(view, lineno - 1);
7819                                         report("");
7820                                 } else {
7821                                         report("Unable to parse '%s' as a line number", cmd);
7822                                 }
7824                         } else if (cmd) {
7825                                 struct view *next = VIEW(REQ_VIEW_PAGER);
7826                                 const char *argv[SIZEOF_ARG] = { "git" };
7827                                 int argc = 1;
7829                                 /* When running random commands, initially show the
7830                                  * command in the title. However, it maybe later be
7831                                  * overwritten if a commit line is selected. */
7832                                 string_ncopy(next->ref, cmd, strlen(cmd));
7834                                 if (!argv_from_string(argv, &argc, cmd)) {
7835                                         report("Too many arguments");
7836                                 } else if (!prepare_update(next, argv, NULL, FORMAT_DASH)) {
7837                                         report("Failed to format command");
7838                                 } else {
7839                                         open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
7840                                 }
7841                         }
7843                         request = REQ_NONE;
7844                         break;
7845                 }
7846                 case REQ_SEARCH:
7847                 case REQ_SEARCH_BACK:
7848                 {
7849                         const char *prompt = request == REQ_SEARCH ? "/" : "?";
7850                         char *search = read_prompt(prompt);
7852                         if (search)
7853                                 string_ncopy(opt_search, search, strlen(search));
7854                         else if (*opt_search)
7855                                 request = request == REQ_SEARCH ?
7856                                         REQ_FIND_NEXT :
7857                                         REQ_FIND_PREV;
7858                         else
7859                                 request = REQ_NONE;
7860                         break;
7861                 }
7862                 default:
7863                         break;
7864                 }
7865         }
7867         quit(0);
7869         return 0;