Code

Move nodelay logic to the get_input read loop
[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 size_t utf8_length(const char **start, size_t skip, int *width, size_t max_width, int *trimmed, bool reserve, int tab_size);
72 static inline unsigned char utf8_char_length(const char *string, const char *end);
74 #define ABS(x)          ((x) >= 0  ? (x) : -(x))
75 #define MIN(x, y)       ((x) < (y) ? (x) :  (y))
76 #define MAX(x, y)       ((x) > (y) ? (x) :  (y))
78 #define ARRAY_SIZE(x)   (sizeof(x) / sizeof(x[0]))
79 #define STRING_SIZE(x)  (sizeof(x) - 1)
81 #define SIZEOF_STR      1024    /* Default string size. */
82 #define SIZEOF_REF      256     /* Size of symbolic or SHA1 ID. */
83 #define SIZEOF_REV      41      /* Holds a SHA-1 and an ending NUL. */
84 #define SIZEOF_ARG      32      /* Default argument array size. */
86 /* Revision graph */
88 #define REVGRAPH_INIT   'I'
89 #define REVGRAPH_MERGE  'M'
90 #define REVGRAPH_BRANCH '+'
91 #define REVGRAPH_COMMIT '*'
92 #define REVGRAPH_BOUND  '^'
94 #define SIZEOF_REVGRAPH 19      /* Size of revision ancestry graphics. */
96 /* This color name can be used to refer to the default term colors. */
97 #define COLOR_DEFAULT   (-1)
99 #define ICONV_NONE      ((iconv_t) -1)
100 #ifndef ICONV_CONST
101 #define ICONV_CONST     /* nothing */
102 #endif
104 /* The format and size of the date column in the main view. */
105 #define DATE_FORMAT     "%Y-%m-%d %H:%M"
106 #define DATE_COLS       STRING_SIZE("2006-04-29 14:21 ")
107 #define DATE_SHORT_COLS STRING_SIZE("2006-04-29 ")
109 #define ID_COLS         8
110 #define AUTHOR_COLS     19
112 #define MIN_VIEW_HEIGHT 4
114 #define NULL_ID         "0000000000000000000000000000000000000000"
116 #define S_ISGITLINK(mode) (((mode) & S_IFMT) == 0160000)
118 /* Some ASCII-shorthands fitted into the ncurses namespace. */
119 #define KEY_TAB         '\t'
120 #define KEY_RETURN      '\r'
121 #define KEY_ESC         27
124 struct ref {
125         char id[SIZEOF_REV];    /* Commit SHA1 ID */
126         unsigned int head:1;    /* Is it the current HEAD? */
127         unsigned int tag:1;     /* Is it a tag? */
128         unsigned int ltag:1;    /* If so, is the tag local? */
129         unsigned int remote:1;  /* Is it a remote ref? */
130         unsigned int tracked:1; /* Is it the remote for the current HEAD? */
131         char name[1];           /* Ref name; tag or head names are shortened. */
132 };
134 struct ref_list {
135         char id[SIZEOF_REV];    /* Commit SHA1 ID */
136         size_t size;            /* Number of refs. */
137         struct ref **refs;      /* References for this ID. */
138 };
140 static struct ref *get_ref_head();
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_remote[SIZEOF_REF]      = "";
1065 static char opt_encoding[20]            = "UTF-8";
1066 static iconv_t opt_iconv_in             = ICONV_NONE;
1067 static iconv_t opt_iconv_out            = ICONV_NONE;
1068 static char opt_search[SIZEOF_STR]      = "";
1069 static char opt_cdup[SIZEOF_STR]        = "";
1070 static char opt_prefix[SIZEOF_STR]      = "";
1071 static char opt_git_dir[SIZEOF_STR]     = "";
1072 static signed char opt_is_inside_work_tree      = -1; /* set to TRUE or FALSE */
1073 static char opt_editor[SIZEOF_STR]      = "";
1074 static FILE *opt_tty                    = NULL;
1076 #define is_initial_commit()     (!get_ref_head())
1077 #define is_head_commit(rev)     (!strcmp((rev), "HEAD") || (get_ref_head() && !strcmp(rev, get_ref_head()->id)))
1078 #define mkdate(time)            string_date(time, opt_date)
1081 /*
1082  * Line-oriented content detection.
1083  */
1085 #define LINE_INFO \
1086 LINE(DIFF_HEADER,  "diff --git ",       COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1087 LINE(DIFF_CHUNK,   "@@",                COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1088 LINE(DIFF_ADD,     "+",                 COLOR_GREEN,    COLOR_DEFAULT,  0), \
1089 LINE(DIFF_DEL,     "-",                 COLOR_RED,      COLOR_DEFAULT,  0), \
1090 LINE(DIFF_INDEX,        "index ",         COLOR_BLUE,   COLOR_DEFAULT,  0), \
1091 LINE(DIFF_OLDMODE,      "old file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
1092 LINE(DIFF_NEWMODE,      "new file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
1093 LINE(DIFF_COPY_FROM,    "copy from",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
1094 LINE(DIFF_COPY_TO,      "copy to",        COLOR_YELLOW, COLOR_DEFAULT,  0), \
1095 LINE(DIFF_RENAME_FROM,  "rename from",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
1096 LINE(DIFF_RENAME_TO,    "rename to",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
1097 LINE(DIFF_SIMILARITY,   "similarity ",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
1098 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
1099 LINE(DIFF_TREE,         "diff-tree ",     COLOR_BLUE,   COLOR_DEFAULT,  0), \
1100 LINE(PP_AUTHOR,    "Author: ",          COLOR_CYAN,     COLOR_DEFAULT,  0), \
1101 LINE(PP_COMMIT,    "Commit: ",          COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1102 LINE(PP_MERGE,     "Merge: ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
1103 LINE(PP_DATE,      "Date:   ",          COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1104 LINE(PP_ADATE,     "AuthorDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1105 LINE(PP_CDATE,     "CommitDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1106 LINE(PP_REFS,      "Refs: ",            COLOR_RED,      COLOR_DEFAULT,  0), \
1107 LINE(COMMIT,       "commit ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
1108 LINE(PARENT,       "parent ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
1109 LINE(TREE,         "tree ",             COLOR_BLUE,     COLOR_DEFAULT,  0), \
1110 LINE(AUTHOR,       "author ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
1111 LINE(COMMITTER,    "committer ",        COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1112 LINE(SIGNOFF,      "    Signed-off-by", COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1113 LINE(ACKED,        "    Acked-by",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1114 LINE(DEFAULT,      "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
1115 LINE(CURSOR,       "",                  COLOR_WHITE,    COLOR_GREEN,    A_BOLD), \
1116 LINE(STATUS,       "",                  COLOR_GREEN,    COLOR_DEFAULT,  0), \
1117 LINE(DELIMITER,    "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1118 LINE(DATE,         "",                  COLOR_BLUE,     COLOR_DEFAULT,  0), \
1119 LINE(MODE,         "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1120 LINE(LINE_NUMBER,  "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1121 LINE(TITLE_BLUR,   "",                  COLOR_WHITE,    COLOR_BLUE,     0), \
1122 LINE(TITLE_FOCUS,  "",                  COLOR_WHITE,    COLOR_BLUE,     A_BOLD), \
1123 LINE(MAIN_COMMIT,  "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
1124 LINE(MAIN_TAG,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  A_BOLD), \
1125 LINE(MAIN_LOCAL_TAG,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1126 LINE(MAIN_REMOTE,  "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1127 LINE(MAIN_TRACKED, "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_BOLD), \
1128 LINE(MAIN_REF,     "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1129 LINE(MAIN_HEAD,    "",                  COLOR_CYAN,     COLOR_DEFAULT,  A_BOLD), \
1130 LINE(MAIN_REVGRAPH,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1131 LINE(TREE_HEAD,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_BOLD), \
1132 LINE(TREE_DIR,     "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_NORMAL), \
1133 LINE(TREE_FILE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
1134 LINE(STAT_HEAD,    "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1135 LINE(STAT_SECTION, "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1136 LINE(STAT_NONE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
1137 LINE(STAT_STAGED,  "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1138 LINE(STAT_UNSTAGED,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1139 LINE(STAT_UNTRACKED,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1140 LINE(HELP_KEYMAP,  "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1141 LINE(HELP_GROUP,   "",                  COLOR_BLUE,     COLOR_DEFAULT,  0), \
1142 LINE(BLAME_ID,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0)
1144 enum line_type {
1145 #define LINE(type, line, fg, bg, attr) \
1146         LINE_##type
1147         LINE_INFO,
1148         LINE_NONE
1149 #undef  LINE
1150 };
1152 struct line_info {
1153         const char *name;       /* Option name. */
1154         int namelen;            /* Size of option name. */
1155         const char *line;       /* The start of line to match. */
1156         int linelen;            /* Size of string to match. */
1157         int fg, bg, attr;       /* Color and text attributes for the lines. */
1158 };
1160 static struct line_info line_info[] = {
1161 #define LINE(type, line, fg, bg, attr) \
1162         { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
1163         LINE_INFO
1164 #undef  LINE
1165 };
1167 static enum line_type
1168 get_line_type(const char *line)
1170         int linelen = strlen(line);
1171         enum line_type type;
1173         for (type = 0; type < ARRAY_SIZE(line_info); type++)
1174                 /* Case insensitive search matches Signed-off-by lines better. */
1175                 if (linelen >= line_info[type].linelen &&
1176                     !strncasecmp(line_info[type].line, line, line_info[type].linelen))
1177                         return type;
1179         return LINE_DEFAULT;
1182 static inline int
1183 get_line_attr(enum line_type type)
1185         assert(type < ARRAY_SIZE(line_info));
1186         return COLOR_PAIR(type) | line_info[type].attr;
1189 static struct line_info *
1190 get_line_info(const char *name)
1192         size_t namelen = strlen(name);
1193         enum line_type type;
1195         for (type = 0; type < ARRAY_SIZE(line_info); type++)
1196                 if (enum_equals(line_info[type], name, namelen))
1197                         return &line_info[type];
1199         return NULL;
1202 static void
1203 init_colors(void)
1205         int default_bg = line_info[LINE_DEFAULT].bg;
1206         int default_fg = line_info[LINE_DEFAULT].fg;
1207         enum line_type type;
1209         start_color();
1211         if (assume_default_colors(default_fg, default_bg) == ERR) {
1212                 default_bg = COLOR_BLACK;
1213                 default_fg = COLOR_WHITE;
1214         }
1216         for (type = 0; type < ARRAY_SIZE(line_info); type++) {
1217                 struct line_info *info = &line_info[type];
1218                 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
1219                 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
1221                 init_pair(type, fg, bg);
1222         }
1225 struct line {
1226         enum line_type type;
1228         /* State flags */
1229         unsigned int selected:1;
1230         unsigned int dirty:1;
1231         unsigned int cleareol:1;
1232         unsigned int other:16;
1234         void *data;             /* User data */
1235 };
1238 /*
1239  * Keys
1240  */
1242 struct keybinding {
1243         int alias;
1244         enum request request;
1245 };
1247 static const struct keybinding default_keybindings[] = {
1248         /* View switching */
1249         { 'm',          REQ_VIEW_MAIN },
1250         { 'd',          REQ_VIEW_DIFF },
1251         { 'l',          REQ_VIEW_LOG },
1252         { 't',          REQ_VIEW_TREE },
1253         { 'f',          REQ_VIEW_BLOB },
1254         { 'B',          REQ_VIEW_BLAME },
1255         { 'H',          REQ_VIEW_BRANCH },
1256         { 'p',          REQ_VIEW_PAGER },
1257         { 'h',          REQ_VIEW_HELP },
1258         { 'S',          REQ_VIEW_STATUS },
1259         { 'c',          REQ_VIEW_STAGE },
1261         /* View manipulation */
1262         { 'q',          REQ_VIEW_CLOSE },
1263         { KEY_TAB,      REQ_VIEW_NEXT },
1264         { KEY_RETURN,   REQ_ENTER },
1265         { KEY_UP,       REQ_PREVIOUS },
1266         { KEY_DOWN,     REQ_NEXT },
1267         { 'R',          REQ_REFRESH },
1268         { KEY_F(5),     REQ_REFRESH },
1269         { 'O',          REQ_MAXIMIZE },
1271         /* Cursor navigation */
1272         { 'k',          REQ_MOVE_UP },
1273         { 'j',          REQ_MOVE_DOWN },
1274         { KEY_HOME,     REQ_MOVE_FIRST_LINE },
1275         { KEY_END,      REQ_MOVE_LAST_LINE },
1276         { KEY_NPAGE,    REQ_MOVE_PAGE_DOWN },
1277         { ' ',          REQ_MOVE_PAGE_DOWN },
1278         { KEY_PPAGE,    REQ_MOVE_PAGE_UP },
1279         { 'b',          REQ_MOVE_PAGE_UP },
1280         { '-',          REQ_MOVE_PAGE_UP },
1282         /* Scrolling */
1283         { KEY_LEFT,     REQ_SCROLL_LEFT },
1284         { KEY_RIGHT,    REQ_SCROLL_RIGHT },
1285         { KEY_IC,       REQ_SCROLL_LINE_UP },
1286         { KEY_DC,       REQ_SCROLL_LINE_DOWN },
1287         { 'w',          REQ_SCROLL_PAGE_UP },
1288         { 's',          REQ_SCROLL_PAGE_DOWN },
1290         /* Searching */
1291         { '/',          REQ_SEARCH },
1292         { '?',          REQ_SEARCH_BACK },
1293         { 'n',          REQ_FIND_NEXT },
1294         { 'N',          REQ_FIND_PREV },
1296         /* Misc */
1297         { 'Q',          REQ_QUIT },
1298         { 'z',          REQ_STOP_LOADING },
1299         { 'v',          REQ_SHOW_VERSION },
1300         { 'r',          REQ_SCREEN_REDRAW },
1301         { 'o',          REQ_OPTIONS },
1302         { '.',          REQ_TOGGLE_LINENO },
1303         { 'D',          REQ_TOGGLE_DATE },
1304         { 'A',          REQ_TOGGLE_AUTHOR },
1305         { 'g',          REQ_TOGGLE_REV_GRAPH },
1306         { 'F',          REQ_TOGGLE_REFS },
1307         { 'I',          REQ_TOGGLE_SORT_ORDER },
1308         { 'i',          REQ_TOGGLE_SORT_FIELD },
1309         { ':',          REQ_PROMPT },
1310         { 'u',          REQ_STATUS_UPDATE },
1311         { '!',          REQ_STATUS_REVERT },
1312         { 'M',          REQ_STATUS_MERGE },
1313         { '@',          REQ_STAGE_NEXT },
1314         { ',',          REQ_PARENT },
1315         { 'e',          REQ_EDIT },
1316 };
1318 #define KEYMAP_INFO \
1319         KEYMAP_(GENERIC), \
1320         KEYMAP_(MAIN), \
1321         KEYMAP_(DIFF), \
1322         KEYMAP_(LOG), \
1323         KEYMAP_(TREE), \
1324         KEYMAP_(BLOB), \
1325         KEYMAP_(BLAME), \
1326         KEYMAP_(BRANCH), \
1327         KEYMAP_(PAGER), \
1328         KEYMAP_(HELP), \
1329         KEYMAP_(STATUS), \
1330         KEYMAP_(STAGE)
1332 enum keymap {
1333 #define KEYMAP_(name) KEYMAP_##name
1334         KEYMAP_INFO
1335 #undef  KEYMAP_
1336 };
1338 static const struct enum_map keymap_table[] = {
1339 #define KEYMAP_(name) ENUM_MAP(#name, KEYMAP_##name)
1340         KEYMAP_INFO
1341 #undef  KEYMAP_
1342 };
1344 #define set_keymap(map, name) map_enum(map, keymap_table, name)
1346 struct keybinding_table {
1347         struct keybinding *data;
1348         size_t size;
1349 };
1351 static struct keybinding_table keybindings[ARRAY_SIZE(keymap_table)];
1353 static void
1354 add_keybinding(enum keymap keymap, enum request request, int key)
1356         struct keybinding_table *table = &keybindings[keymap];
1358         table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data));
1359         if (!table->data)
1360                 die("Failed to allocate keybinding");
1361         table->data[table->size].alias = key;
1362         table->data[table->size++].request = request;
1365 /* Looks for a key binding first in the given map, then in the generic map, and
1366  * lastly in the default keybindings. */
1367 static enum request
1368 get_keybinding(enum keymap keymap, int key)
1370         size_t i;
1372         for (i = 0; i < keybindings[keymap].size; i++)
1373                 if (keybindings[keymap].data[i].alias == key)
1374                         return keybindings[keymap].data[i].request;
1376         for (i = 0; i < keybindings[KEYMAP_GENERIC].size; i++)
1377                 if (keybindings[KEYMAP_GENERIC].data[i].alias == key)
1378                         return keybindings[KEYMAP_GENERIC].data[i].request;
1380         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1381                 if (default_keybindings[i].alias == key)
1382                         return default_keybindings[i].request;
1384         return (enum request) key;
1388 struct key {
1389         const char *name;
1390         int value;
1391 };
1393 static const struct key key_table[] = {
1394         { "Enter",      KEY_RETURN },
1395         { "Space",      ' ' },
1396         { "Backspace",  KEY_BACKSPACE },
1397         { "Tab",        KEY_TAB },
1398         { "Escape",     KEY_ESC },
1399         { "Left",       KEY_LEFT },
1400         { "Right",      KEY_RIGHT },
1401         { "Up",         KEY_UP },
1402         { "Down",       KEY_DOWN },
1403         { "Insert",     KEY_IC },
1404         { "Delete",     KEY_DC },
1405         { "Hash",       '#' },
1406         { "Home",       KEY_HOME },
1407         { "End",        KEY_END },
1408         { "PageUp",     KEY_PPAGE },
1409         { "PageDown",   KEY_NPAGE },
1410         { "F1",         KEY_F(1) },
1411         { "F2",         KEY_F(2) },
1412         { "F3",         KEY_F(3) },
1413         { "F4",         KEY_F(4) },
1414         { "F5",         KEY_F(5) },
1415         { "F6",         KEY_F(6) },
1416         { "F7",         KEY_F(7) },
1417         { "F8",         KEY_F(8) },
1418         { "F9",         KEY_F(9) },
1419         { "F10",        KEY_F(10) },
1420         { "F11",        KEY_F(11) },
1421         { "F12",        KEY_F(12) },
1422 };
1424 static int
1425 get_key_value(const char *name)
1427         int i;
1429         for (i = 0; i < ARRAY_SIZE(key_table); i++)
1430                 if (!strcasecmp(key_table[i].name, name))
1431                         return key_table[i].value;
1433         if (strlen(name) == 1 && isprint(*name))
1434                 return (int) *name;
1436         return ERR;
1439 static const char *
1440 get_key_name(int key_value)
1442         static char key_char[] = "'X'";
1443         const char *seq = NULL;
1444         int key;
1446         for (key = 0; key < ARRAY_SIZE(key_table); key++)
1447                 if (key_table[key].value == key_value)
1448                         seq = key_table[key].name;
1450         if (seq == NULL &&
1451             key_value < 127 &&
1452             isprint(key_value)) {
1453                 key_char[1] = (char) key_value;
1454                 seq = key_char;
1455         }
1457         return seq ? seq : "(no key)";
1460 static bool
1461 append_key(char *buf, size_t *pos, const struct keybinding *keybinding)
1463         const char *sep = *pos > 0 ? ", " : "";
1464         const char *keyname = get_key_name(keybinding->alias);
1466         return string_nformat(buf, BUFSIZ, pos, "%s%s", sep, keyname);
1469 static bool
1470 append_keymap_request_keys(char *buf, size_t *pos, enum request request,
1471                            enum keymap keymap, bool all)
1473         int i;
1475         for (i = 0; i < keybindings[keymap].size; i++) {
1476                 if (keybindings[keymap].data[i].request == request) {
1477                         if (!append_key(buf, pos, &keybindings[keymap].data[i]))
1478                                 return FALSE;
1479                         if (!all)
1480                                 break;
1481                 }
1482         }
1484         return TRUE;
1487 #define get_key(keymap, request) get_keys(keymap, request, FALSE)
1489 static const char *
1490 get_keys(enum keymap keymap, enum request request, bool all)
1492         static char buf[BUFSIZ];
1493         size_t pos = 0;
1494         int i;
1496         buf[pos] = 0;
1498         if (!append_keymap_request_keys(buf, &pos, request, keymap, all))
1499                 return "Too many keybindings!";
1500         if (pos > 0 && !all)
1501                 return buf;
1503         if (keymap != KEYMAP_GENERIC) {
1504                 /* Only the generic keymap includes the default keybindings when
1505                  * listing all keys. */
1506                 if (all)
1507                         return buf;
1509                 if (!append_keymap_request_keys(buf, &pos, request, KEYMAP_GENERIC, all))
1510                         return "Too many keybindings!";
1511                 if (pos)
1512                         return buf;
1513         }
1515         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1516                 if (default_keybindings[i].request == request) {
1517                         if (!append_key(buf, &pos, &default_keybindings[i]))
1518                                 return "Too many keybindings!";
1519                         if (!all)
1520                                 return buf;
1521                 }
1522         }
1524         return buf;
1527 struct run_request {
1528         enum keymap keymap;
1529         int key;
1530         const char *argv[SIZEOF_ARG];
1531 };
1533 static struct run_request *run_request;
1534 static size_t run_requests;
1536 DEFINE_ALLOCATOR(realloc_run_requests, struct run_request, 8)
1538 static enum request
1539 add_run_request(enum keymap keymap, int key, int argc, const char **argv)
1541         struct run_request *req;
1543         if (argc >= ARRAY_SIZE(req->argv) - 1)
1544                 return REQ_NONE;
1546         if (!realloc_run_requests(&run_request, run_requests, 1))
1547                 return REQ_NONE;
1549         req = &run_request[run_requests];
1550         req->keymap = keymap;
1551         req->key = key;
1552         req->argv[0] = NULL;
1554         if (!format_argv(req->argv, argv, FORMAT_NONE))
1555                 return REQ_NONE;
1557         return REQ_NONE + ++run_requests;
1560 static struct run_request *
1561 get_run_request(enum request request)
1563         if (request <= REQ_NONE)
1564                 return NULL;
1565         return &run_request[request - REQ_NONE - 1];
1568 static void
1569 add_builtin_run_requests(void)
1571         const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1572         const char *commit[] = { "git", "commit", NULL };
1573         const char *gc[] = { "git", "gc", NULL };
1574         struct {
1575                 enum keymap keymap;
1576                 int key;
1577                 int argc;
1578                 const char **argv;
1579         } reqs[] = {
1580                 { KEYMAP_MAIN,    'C', ARRAY_SIZE(cherry_pick) - 1, cherry_pick },
1581                 { KEYMAP_STATUS,  'C', ARRAY_SIZE(commit) - 1, commit },
1582                 { KEYMAP_GENERIC, 'G', ARRAY_SIZE(gc) - 1, gc },
1583         };
1584         int i;
1586         for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1587                 enum request req;
1589                 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argc, reqs[i].argv);
1590                 if (req != REQ_NONE)
1591                         add_keybinding(reqs[i].keymap, req, reqs[i].key);
1592         }
1595 /*
1596  * User config file handling.
1597  */
1599 static int   config_lineno;
1600 static bool  config_errors;
1601 static const char *config_msg;
1603 static const struct enum_map color_map[] = {
1604 #define COLOR_MAP(name) ENUM_MAP(#name, COLOR_##name)
1605         COLOR_MAP(DEFAULT),
1606         COLOR_MAP(BLACK),
1607         COLOR_MAP(BLUE),
1608         COLOR_MAP(CYAN),
1609         COLOR_MAP(GREEN),
1610         COLOR_MAP(MAGENTA),
1611         COLOR_MAP(RED),
1612         COLOR_MAP(WHITE),
1613         COLOR_MAP(YELLOW),
1614 };
1616 static const struct enum_map attr_map[] = {
1617 #define ATTR_MAP(name) ENUM_MAP(#name, A_##name)
1618         ATTR_MAP(NORMAL),
1619         ATTR_MAP(BLINK),
1620         ATTR_MAP(BOLD),
1621         ATTR_MAP(DIM),
1622         ATTR_MAP(REVERSE),
1623         ATTR_MAP(STANDOUT),
1624         ATTR_MAP(UNDERLINE),
1625 };
1627 #define set_attribute(attr, name)       map_enum(attr, attr_map, name)
1629 static int parse_step(double *opt, const char *arg)
1631         *opt = atoi(arg);
1632         if (!strchr(arg, '%'))
1633                 return OK;
1635         /* "Shift down" so 100% and 1 does not conflict. */
1636         *opt = (*opt - 1) / 100;
1637         if (*opt >= 1.0) {
1638                 *opt = 0.99;
1639                 config_msg = "Step value larger than 100%";
1640                 return ERR;
1641         }
1642         if (*opt < 0.0) {
1643                 *opt = 1;
1644                 config_msg = "Invalid step value";
1645                 return ERR;
1646         }
1647         return OK;
1650 static int
1651 parse_int(int *opt, const char *arg, int min, int max)
1653         int value = atoi(arg);
1655         if (min <= value && value <= max) {
1656                 *opt = value;
1657                 return OK;
1658         }
1660         config_msg = "Integer value out of bound";
1661         return ERR;
1664 static bool
1665 set_color(int *color, const char *name)
1667         if (map_enum(color, color_map, name))
1668                 return TRUE;
1669         if (!prefixcmp(name, "color"))
1670                 return parse_int(color, name + 5, 0, 255) == OK;
1671         return FALSE;
1674 /* Wants: object fgcolor bgcolor [attribute] */
1675 static int
1676 option_color_command(int argc, const char *argv[])
1678         struct line_info *info;
1680         if (argc < 3) {
1681                 config_msg = "Wrong number of arguments given to color command";
1682                 return ERR;
1683         }
1685         info = get_line_info(argv[0]);
1686         if (!info) {
1687                 static const struct enum_map obsolete[] = {
1688                         ENUM_MAP("main-delim",  LINE_DELIMITER),
1689                         ENUM_MAP("main-date",   LINE_DATE),
1690                         ENUM_MAP("main-author", LINE_AUTHOR),
1691                 };
1692                 int index;
1694                 if (!map_enum(&index, obsolete, argv[0])) {
1695                         config_msg = "Unknown color name";
1696                         return ERR;
1697                 }
1698                 info = &line_info[index];
1699         }
1701         if (!set_color(&info->fg, argv[1]) ||
1702             !set_color(&info->bg, argv[2])) {
1703                 config_msg = "Unknown color";
1704                 return ERR;
1705         }
1707         info->attr = 0;
1708         while (argc-- > 3) {
1709                 int attr;
1711                 if (!set_attribute(&attr, argv[argc])) {
1712                         config_msg = "Unknown attribute";
1713                         return ERR;
1714                 }
1715                 info->attr |= attr;
1716         }
1718         return OK;
1721 static int parse_bool(bool *opt, const char *arg)
1723         *opt = (!strcmp(arg, "1") || !strcmp(arg, "true") || !strcmp(arg, "yes"))
1724                 ? TRUE : FALSE;
1725         return OK;
1728 static int parse_enum_do(unsigned int *opt, const char *arg,
1729                          const struct enum_map *map, size_t map_size)
1731         bool is_true;
1733         assert(map_size > 1);
1735         if (map_enum_do(map, map_size, (int *) opt, arg))
1736                 return OK;
1738         if (parse_bool(&is_true, arg) != OK)
1739                 return ERR;
1741         *opt = is_true ? map[1].value : map[0].value;
1742         return OK;
1745 #define parse_enum(opt, arg, map) \
1746         parse_enum_do(opt, arg, map, ARRAY_SIZE(map))
1748 static int
1749 parse_string(char *opt, const char *arg, size_t optsize)
1751         int arglen = strlen(arg);
1753         switch (arg[0]) {
1754         case '\"':
1755         case '\'':
1756                 if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1757                         config_msg = "Unmatched quotation";
1758                         return ERR;
1759                 }
1760                 arg += 1; arglen -= 2;
1761         default:
1762                 string_ncopy_do(opt, optsize, arg, arglen);
1763                 return OK;
1764         }
1767 /* Wants: name = value */
1768 static int
1769 option_set_command(int argc, const char *argv[])
1771         if (argc != 3) {
1772                 config_msg = "Wrong number of arguments given to set command";
1773                 return ERR;
1774         }
1776         if (strcmp(argv[1], "=")) {
1777                 config_msg = "No value assigned";
1778                 return ERR;
1779         }
1781         if (!strcmp(argv[0], "show-author"))
1782                 return parse_enum(&opt_author, argv[2], author_map);
1784         if (!strcmp(argv[0], "show-date"))
1785                 return parse_enum(&opt_date, argv[2], date_map);
1787         if (!strcmp(argv[0], "show-rev-graph"))
1788                 return parse_bool(&opt_rev_graph, argv[2]);
1790         if (!strcmp(argv[0], "show-refs"))
1791                 return parse_bool(&opt_show_refs, argv[2]);
1793         if (!strcmp(argv[0], "show-line-numbers"))
1794                 return parse_bool(&opt_line_number, argv[2]);
1796         if (!strcmp(argv[0], "line-graphics"))
1797                 return parse_bool(&opt_line_graphics, argv[2]);
1799         if (!strcmp(argv[0], "line-number-interval"))
1800                 return parse_int(&opt_num_interval, argv[2], 1, 1024);
1802         if (!strcmp(argv[0], "author-width"))
1803                 return parse_int(&opt_author_cols, argv[2], 0, 1024);
1805         if (!strcmp(argv[0], "horizontal-scroll"))
1806                 return parse_step(&opt_hscroll, argv[2]);
1808         if (!strcmp(argv[0], "split-view-height"))
1809                 return parse_step(&opt_scale_split_view, argv[2]);
1811         if (!strcmp(argv[0], "tab-size"))
1812                 return parse_int(&opt_tab_size, argv[2], 1, 1024);
1814         if (!strcmp(argv[0], "commit-encoding"))
1815                 return parse_string(opt_encoding, argv[2], sizeof(opt_encoding));
1817         config_msg = "Unknown variable name";
1818         return ERR;
1821 /* Wants: mode request key */
1822 static int
1823 option_bind_command(int argc, const char *argv[])
1825         enum request request;
1826         int keymap = -1;
1827         int key;
1829         if (argc < 3) {
1830                 config_msg = "Wrong number of arguments given to bind command";
1831                 return ERR;
1832         }
1834         if (set_keymap(&keymap, argv[0]) == ERR) {
1835                 config_msg = "Unknown key map";
1836                 return ERR;
1837         }
1839         key = get_key_value(argv[1]);
1840         if (key == ERR) {
1841                 config_msg = "Unknown key";
1842                 return ERR;
1843         }
1845         request = get_request(argv[2]);
1846         if (request == REQ_NONE) {
1847                 static const struct enum_map obsolete[] = {
1848                         ENUM_MAP("cherry-pick",         REQ_NONE),
1849                         ENUM_MAP("screen-resize",       REQ_NONE),
1850                         ENUM_MAP("tree-parent",         REQ_PARENT),
1851                 };
1852                 int alias;
1854                 if (map_enum(&alias, obsolete, argv[2])) {
1855                         if (alias != REQ_NONE)
1856                                 add_keybinding(keymap, alias, key);
1857                         config_msg = "Obsolete request name";
1858                         return ERR;
1859                 }
1860         }
1861         if (request == REQ_NONE && *argv[2]++ == '!')
1862                 request = add_run_request(keymap, key, argc - 2, argv + 2);
1863         if (request == REQ_NONE) {
1864                 config_msg = "Unknown request name";
1865                 return ERR;
1866         }
1868         add_keybinding(keymap, request, key);
1870         return OK;
1873 static int
1874 set_option(const char *opt, char *value)
1876         const char *argv[SIZEOF_ARG];
1877         int argc = 0;
1879         if (!argv_from_string(argv, &argc, value)) {
1880                 config_msg = "Too many option arguments";
1881                 return ERR;
1882         }
1884         if (!strcmp(opt, "color"))
1885                 return option_color_command(argc, argv);
1887         if (!strcmp(opt, "set"))
1888                 return option_set_command(argc, argv);
1890         if (!strcmp(opt, "bind"))
1891                 return option_bind_command(argc, argv);
1893         config_msg = "Unknown option command";
1894         return ERR;
1897 static int
1898 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
1900         int status = OK;
1902         config_lineno++;
1903         config_msg = "Internal error";
1905         /* Check for comment markers, since read_properties() will
1906          * only ensure opt and value are split at first " \t". */
1907         optlen = strcspn(opt, "#");
1908         if (optlen == 0)
1909                 return OK;
1911         if (opt[optlen] != 0) {
1912                 config_msg = "No option value";
1913                 status = ERR;
1915         }  else {
1916                 /* Look for comment endings in the value. */
1917                 size_t len = strcspn(value, "#");
1919                 if (len < valuelen) {
1920                         valuelen = len;
1921                         value[valuelen] = 0;
1922                 }
1924                 status = set_option(opt, value);
1925         }
1927         if (status == ERR) {
1928                 warn("Error on line %d, near '%.*s': %s",
1929                      config_lineno, (int) optlen, opt, config_msg);
1930                 config_errors = TRUE;
1931         }
1933         /* Always keep going if errors are encountered. */
1934         return OK;
1937 static void
1938 load_option_file(const char *path)
1940         struct io io = {};
1942         /* It's OK that the file doesn't exist. */
1943         if (!io_open(&io, "%s", path))
1944                 return;
1946         config_lineno = 0;
1947         config_errors = FALSE;
1949         if (io_load(&io, " \t", read_option) == ERR ||
1950             config_errors == TRUE)
1951                 warn("Errors while loading %s.", path);
1954 static int
1955 load_options(void)
1957         const char *home = getenv("HOME");
1958         const char *tigrc_user = getenv("TIGRC_USER");
1959         const char *tigrc_system = getenv("TIGRC_SYSTEM");
1960         char buf[SIZEOF_STR];
1962         add_builtin_run_requests();
1964         if (!tigrc_system)
1965                 tigrc_system = SYSCONFDIR "/tigrc";
1966         load_option_file(tigrc_system);
1968         if (!tigrc_user) {
1969                 if (!home || !string_format(buf, "%s/.tigrc", home))
1970                         return ERR;
1971                 tigrc_user = buf;
1972         }
1973         load_option_file(tigrc_user);
1975         return OK;
1979 /*
1980  * The viewer
1981  */
1983 struct view;
1984 struct view_ops;
1986 /* The display array of active views and the index of the current view. */
1987 static struct view *display[2];
1988 static unsigned int current_view;
1990 #define foreach_displayed_view(view, i) \
1991         for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1993 #define displayed_views()       (display[1] != NULL ? 2 : 1)
1995 /* Current head and commit ID */
1996 static char ref_blob[SIZEOF_REF]        = "";
1997 static char ref_commit[SIZEOF_REF]      = "HEAD";
1998 static char ref_head[SIZEOF_REF]        = "HEAD";
2000 struct view {
2001         const char *name;       /* View name */
2002         const char *cmd_env;    /* Command line set via environment */
2003         const char *id;         /* Points to either of ref_{head,commit,blob} */
2005         struct view_ops *ops;   /* View operations */
2007         enum keymap keymap;     /* What keymap does this view have */
2008         bool git_dir;           /* Whether the view requires a git directory. */
2010         char ref[SIZEOF_REF];   /* Hovered commit reference */
2011         char vid[SIZEOF_REF];   /* View ID. Set to id member when updating. */
2013         int height, width;      /* The width and height of the main window */
2014         WINDOW *win;            /* The main window */
2015         WINDOW *title;          /* The title window living below the main window */
2017         /* Navigation */
2018         unsigned long offset;   /* Offset of the window top */
2019         unsigned long yoffset;  /* Offset from the window side. */
2020         unsigned long lineno;   /* Current line number */
2021         unsigned long p_offset; /* Previous offset of the window top */
2022         unsigned long p_yoffset;/* Previous offset from the window side */
2023         unsigned long p_lineno; /* Previous current line number */
2024         bool p_restore;         /* Should the previous position be restored. */
2026         /* Searching */
2027         char grep[SIZEOF_STR];  /* Search string */
2028         regex_t *regex;         /* Pre-compiled regexp */
2030         /* If non-NULL, points to the view that opened this view. If this view
2031          * is closed tig will switch back to the parent view. */
2032         struct view *parent;
2034         /* Buffering */
2035         size_t lines;           /* Total number of lines */
2036         struct line *line;      /* Line index */
2037         unsigned int digits;    /* Number of digits in the lines member. */
2039         /* Drawing */
2040         struct line *curline;   /* Line currently being drawn. */
2041         enum line_type curtype; /* Attribute currently used for drawing. */
2042         unsigned long col;      /* Column when drawing. */
2043         bool has_scrolled;      /* View was scrolled. */
2045         /* Loading */
2046         struct io io;
2047         struct io *pipe;
2048         time_t start_time;
2049         time_t update_secs;
2050 };
2052 struct view_ops {
2053         /* What type of content being displayed. Used in the title bar. */
2054         const char *type;
2055         /* Default command arguments. */
2056         const char **argv;
2057         /* Open and reads in all view content. */
2058         bool (*open)(struct view *view);
2059         /* Read one line; updates view->line. */
2060         bool (*read)(struct view *view, char *data);
2061         /* Draw one line; @lineno must be < view->height. */
2062         bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
2063         /* Depending on view handle a special requests. */
2064         enum request (*request)(struct view *view, enum request request, struct line *line);
2065         /* Search for regexp in a line. */
2066         bool (*grep)(struct view *view, struct line *line);
2067         /* Select line */
2068         void (*select)(struct view *view, struct line *line);
2069         /* Prepare view for loading */
2070         bool (*prepare)(struct view *view);
2071 };
2073 static struct view_ops blame_ops;
2074 static struct view_ops blob_ops;
2075 static struct view_ops diff_ops;
2076 static struct view_ops help_ops;
2077 static struct view_ops log_ops;
2078 static struct view_ops main_ops;
2079 static struct view_ops pager_ops;
2080 static struct view_ops stage_ops;
2081 static struct view_ops status_ops;
2082 static struct view_ops tree_ops;
2083 static struct view_ops branch_ops;
2085 #define VIEW_STR(name, env, ref, ops, map, git) \
2086         { name, #env, ref, ops, map, git }
2088 #define VIEW_(id, name, ops, git, ref) \
2089         VIEW_STR(name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
2092 static struct view views[] = {
2093         VIEW_(MAIN,   "main",   &main_ops,   TRUE,  ref_head),
2094         VIEW_(DIFF,   "diff",   &diff_ops,   TRUE,  ref_commit),
2095         VIEW_(LOG,    "log",    &log_ops,    TRUE,  ref_head),
2096         VIEW_(TREE,   "tree",   &tree_ops,   TRUE,  ref_commit),
2097         VIEW_(BLOB,   "blob",   &blob_ops,   TRUE,  ref_blob),
2098         VIEW_(BLAME,  "blame",  &blame_ops,  TRUE,  ref_commit),
2099         VIEW_(BRANCH, "branch", &branch_ops, TRUE,  ref_head),
2100         VIEW_(HELP,   "help",   &help_ops,   FALSE, ""),
2101         VIEW_(PAGER,  "pager",  &pager_ops,  FALSE, "stdin"),
2102         VIEW_(STATUS, "status", &status_ops, TRUE,  ""),
2103         VIEW_(STAGE,  "stage",  &stage_ops,  TRUE,  ""),
2104 };
2106 #define VIEW(req)       (&views[(req) - REQ_OFFSET - 1])
2107 #define VIEW_REQ(view)  ((view) - views + REQ_OFFSET + 1)
2109 #define foreach_view(view, i) \
2110         for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
2112 #define view_is_displayed(view) \
2113         (view == display[0] || view == display[1])
2116 static inline void
2117 set_view_attr(struct view *view, enum line_type type)
2119         if (!view->curline->selected && view->curtype != type) {
2120                 (void) wattrset(view->win, get_line_attr(type));
2121                 wchgat(view->win, -1, 0, type, NULL);
2122                 view->curtype = type;
2123         }
2126 static int
2127 draw_chars(struct view *view, enum line_type type, const char *string,
2128            int max_len, bool use_tilde)
2130         static char out_buffer[BUFSIZ * 2];
2131         int len = 0;
2132         int col = 0;
2133         int trimmed = FALSE;
2134         size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2136         if (max_len <= 0)
2137                 return 0;
2139         len = utf8_length(&string, skip, &col, max_len, &trimmed, use_tilde, opt_tab_size);
2141         set_view_attr(view, type);
2142         if (len > 0) {
2143                 if (opt_iconv_out != ICONV_NONE) {
2144                         ICONV_CONST char *inbuf = (ICONV_CONST char *) string;
2145                         size_t inlen = len + 1;
2147                         char *outbuf = out_buffer;
2148                         size_t outlen = sizeof(out_buffer);
2150                         size_t ret;
2152                         ret = iconv(opt_iconv_out, &inbuf, &inlen, &outbuf, &outlen);
2153                         if (ret != (size_t) -1) {
2154                                 string = out_buffer;
2155                                 len = sizeof(out_buffer) - outlen;
2156                         }
2157                 }
2159                 waddnstr(view->win, string, len);
2160         }
2161         if (trimmed && use_tilde) {
2162                 set_view_attr(view, LINE_DELIMITER);
2163                 waddch(view->win, '~');
2164                 col++;
2165         }
2167         return col;
2170 static int
2171 draw_space(struct view *view, enum line_type type, int max, int spaces)
2173         static char space[] = "                    ";
2174         int col = 0;
2176         spaces = MIN(max, spaces);
2178         while (spaces > 0) {
2179                 int len = MIN(spaces, sizeof(space) - 1);
2181                 col += draw_chars(view, type, space, len, FALSE);
2182                 spaces -= len;
2183         }
2185         return col;
2188 static bool
2189 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
2191         view->col += draw_chars(view, type, string, view->width + view->yoffset - view->col, trim);
2192         return view->width + view->yoffset <= view->col;
2195 static bool
2196 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
2198         size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2199         int max = view->width + view->yoffset - view->col;
2200         int i;
2202         if (max < size)
2203                 size = max;
2205         set_view_attr(view, type);
2206         /* Using waddch() instead of waddnstr() ensures that
2207          * they'll be rendered correctly for the cursor line. */
2208         for (i = skip; i < size; i++)
2209                 waddch(view->win, graphic[i]);
2211         view->col += size;
2212         if (size < max && skip <= size)
2213                 waddch(view->win, ' ');
2214         view->col++;
2216         return view->width + view->yoffset <= view->col;
2219 static bool
2220 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
2222         int max = MIN(view->width + view->yoffset - view->col, len);
2223         int col;
2225         if (text)
2226                 col = draw_chars(view, type, text, max - 1, trim);
2227         else
2228                 col = draw_space(view, type, max - 1, max - 1);
2230         view->col += col;
2231         view->col += draw_space(view, LINE_DEFAULT, max - col, max - col);
2232         return view->width + view->yoffset <= view->col;
2235 static bool
2236 draw_date(struct view *view, struct time *time)
2238         const char *date = time && time->sec ? mkdate(time) : "";
2239         int cols = opt_date == DATE_SHORT ? DATE_SHORT_COLS : DATE_COLS;
2241         return draw_field(view, LINE_DATE, date, cols, FALSE);
2244 static bool
2245 draw_author(struct view *view, const char *author)
2247         bool trim = opt_author_cols == 0 || opt_author_cols > 5;
2248         bool abbreviate = opt_author == AUTHOR_ABBREVIATED || !trim;
2250         if (abbreviate && author)
2251                 author = get_author_initials(author);
2253         return draw_field(view, LINE_AUTHOR, author, opt_author_cols, trim);
2256 static bool
2257 draw_mode(struct view *view, mode_t mode)
2259         const char *str;
2261         if (S_ISDIR(mode))
2262                 str = "drwxr-xr-x";
2263         else if (S_ISLNK(mode))
2264                 str = "lrwxrwxrwx";
2265         else if (S_ISGITLINK(mode))
2266                 str = "m---------";
2267         else if (S_ISREG(mode) && mode & S_IXUSR)
2268                 str = "-rwxr-xr-x";
2269         else if (S_ISREG(mode))
2270                 str = "-rw-r--r--";
2271         else
2272                 str = "----------";
2274         return draw_field(view, LINE_MODE, str, STRING_SIZE("-rw-r--r-- "), FALSE);
2277 static bool
2278 draw_lineno(struct view *view, unsigned int lineno)
2280         char number[10];
2281         int digits3 = view->digits < 3 ? 3 : view->digits;
2282         int max = MIN(view->width + view->yoffset - view->col, digits3);
2283         char *text = NULL;
2284         chtype separator = opt_line_graphics ? ACS_VLINE : '|';
2286         lineno += view->offset + 1;
2287         if (lineno == 1 || (lineno % opt_num_interval) == 0) {
2288                 static char fmt[] = "%1ld";
2290                 fmt[1] = '0' + (view->digits <= 9 ? digits3 : 1);
2291                 if (string_format(number, fmt, lineno))
2292                         text = number;
2293         }
2294         if (text)
2295                 view->col += draw_chars(view, LINE_LINE_NUMBER, text, max, TRUE);
2296         else
2297                 view->col += draw_space(view, LINE_LINE_NUMBER, max, digits3);
2298         return draw_graphic(view, LINE_DEFAULT, &separator, 1);
2301 static bool
2302 draw_view_line(struct view *view, unsigned int lineno)
2304         struct line *line;
2305         bool selected = (view->offset + lineno == view->lineno);
2307         assert(view_is_displayed(view));
2309         if (view->offset + lineno >= view->lines)
2310                 return FALSE;
2312         line = &view->line[view->offset + lineno];
2314         wmove(view->win, lineno, 0);
2315         if (line->cleareol)
2316                 wclrtoeol(view->win);
2317         view->col = 0;
2318         view->curline = line;
2319         view->curtype = LINE_NONE;
2320         line->selected = FALSE;
2321         line->dirty = line->cleareol = 0;
2323         if (selected) {
2324                 set_view_attr(view, LINE_CURSOR);
2325                 line->selected = TRUE;
2326                 view->ops->select(view, line);
2327         }
2329         return view->ops->draw(view, line, lineno);
2332 static void
2333 redraw_view_dirty(struct view *view)
2335         bool dirty = FALSE;
2336         int lineno;
2338         for (lineno = 0; lineno < view->height; lineno++) {
2339                 if (view->offset + lineno >= view->lines)
2340                         break;
2341                 if (!view->line[view->offset + lineno].dirty)
2342                         continue;
2343                 dirty = TRUE;
2344                 if (!draw_view_line(view, lineno))
2345                         break;
2346         }
2348         if (!dirty)
2349                 return;
2350         wnoutrefresh(view->win);
2353 static void
2354 redraw_view_from(struct view *view, int lineno)
2356         assert(0 <= lineno && lineno < view->height);
2358         for (; lineno < view->height; lineno++) {
2359                 if (!draw_view_line(view, lineno))
2360                         break;
2361         }
2363         wnoutrefresh(view->win);
2366 static void
2367 redraw_view(struct view *view)
2369         werase(view->win);
2370         redraw_view_from(view, 0);
2374 static void
2375 update_view_title(struct view *view)
2377         char buf[SIZEOF_STR];
2378         char state[SIZEOF_STR];
2379         size_t bufpos = 0, statelen = 0;
2381         assert(view_is_displayed(view));
2383         if (view != VIEW(REQ_VIEW_STATUS) && view->lines) {
2384                 unsigned int view_lines = view->offset + view->height;
2385                 unsigned int lines = view->lines
2386                                    ? MIN(view_lines, view->lines) * 100 / view->lines
2387                                    : 0;
2389                 string_format_from(state, &statelen, " - %s %d of %d (%d%%)",
2390                                    view->ops->type,
2391                                    view->lineno + 1,
2392                                    view->lines,
2393                                    lines);
2395         }
2397         if (view->pipe) {
2398                 time_t secs = time(NULL) - view->start_time;
2400                 /* Three git seconds are a long time ... */
2401                 if (secs > 2)
2402                         string_format_from(state, &statelen, " loading %lds", secs);
2403         }
2405         string_format_from(buf, &bufpos, "[%s]", view->name);
2406         if (*view->ref && bufpos < view->width) {
2407                 size_t refsize = strlen(view->ref);
2408                 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2410                 if (minsize < view->width)
2411                         refsize = view->width - minsize + 7;
2412                 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2413         }
2415         if (statelen && bufpos < view->width) {
2416                 string_format_from(buf, &bufpos, "%s", state);
2417         }
2419         if (view == display[current_view])
2420                 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2421         else
2422                 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2424         mvwaddnstr(view->title, 0, 0, buf, bufpos);
2425         wclrtoeol(view->title);
2426         wnoutrefresh(view->title);
2429 static int
2430 apply_step(double step, int value)
2432         if (step >= 1)
2433                 return (int) step;
2434         value *= step + 0.01;
2435         return value ? value : 1;
2438 static void
2439 resize_display(void)
2441         int offset, i;
2442         struct view *base = display[0];
2443         struct view *view = display[1] ? display[1] : display[0];
2445         /* Setup window dimensions */
2447         getmaxyx(stdscr, base->height, base->width);
2449         /* Make room for the status window. */
2450         base->height -= 1;
2452         if (view != base) {
2453                 /* Horizontal split. */
2454                 view->width   = base->width;
2455                 view->height  = apply_step(opt_scale_split_view, base->height);
2456                 view->height  = MAX(view->height, MIN_VIEW_HEIGHT);
2457                 view->height  = MIN(view->height, base->height - MIN_VIEW_HEIGHT);
2458                 base->height -= view->height;
2460                 /* Make room for the title bar. */
2461                 view->height -= 1;
2462         }
2464         /* Make room for the title bar. */
2465         base->height -= 1;
2467         offset = 0;
2469         foreach_displayed_view (view, i) {
2470                 if (!view->win) {
2471                         view->win = newwin(view->height, 0, offset, 0);
2472                         if (!view->win)
2473                                 die("Failed to create %s view", view->name);
2475                         scrollok(view->win, FALSE);
2477                         view->title = newwin(1, 0, offset + view->height, 0);
2478                         if (!view->title)
2479                                 die("Failed to create title window");
2481                 } else {
2482                         wresize(view->win, view->height, view->width);
2483                         mvwin(view->win,   offset, 0);
2484                         mvwin(view->title, offset + view->height, 0);
2485                 }
2487                 offset += view->height + 1;
2488         }
2491 static void
2492 redraw_display(bool clear)
2494         struct view *view;
2495         int i;
2497         foreach_displayed_view (view, i) {
2498                 if (clear)
2499                         wclear(view->win);
2500                 redraw_view(view);
2501                 update_view_title(view);
2502         }
2505 static void
2506 toggle_enum_option_do(unsigned int *opt, const char *help,
2507                       const struct enum_map *map, size_t size)
2509         *opt = (*opt + 1) % size;
2510         redraw_display(FALSE);
2511         report("Displaying %s %s", enum_name(map[*opt]), help);
2514 #define toggle_enum_option(opt, help, map) \
2515         toggle_enum_option_do(opt, help, map, ARRAY_SIZE(map))
2517 #define toggle_date() toggle_enum_option(&opt_date, "dates", date_map)
2518 #define toggle_author() toggle_enum_option(&opt_author, "author names", author_map)
2520 static void
2521 toggle_view_option(bool *option, const char *help)
2523         *option = !*option;
2524         redraw_display(FALSE);
2525         report("%sabling %s", *option ? "En" : "Dis", help);
2528 static void
2529 open_option_menu(void)
2531         const struct menu_item menu[] = {
2532                 { '.', "line numbers", &opt_line_number },
2533                 { 'D', "date display", &opt_date },
2534                 { 'A', "author display", &opt_author },
2535                 { 'g', "revision graph display", &opt_rev_graph },
2536                 { 'F', "reference display", &opt_show_refs },
2537                 { 0 }
2538         };
2539         int selected = 0;
2541         if (prompt_menu("Toggle option", menu, &selected)) {
2542                 if (menu[selected].data == &opt_date)
2543                         toggle_date();
2544                 else if (menu[selected].data == &opt_author)
2545                         toggle_author();
2546                 else
2547                         toggle_view_option(menu[selected].data, menu[selected].text);
2548         }
2551 static void
2552 maximize_view(struct view *view)
2554         memset(display, 0, sizeof(display));
2555         current_view = 0;
2556         display[current_view] = view;
2557         resize_display();
2558         redraw_display(FALSE);
2559         report("");
2563 /*
2564  * Navigation
2565  */
2567 static bool
2568 goto_view_line(struct view *view, unsigned long offset, unsigned long lineno)
2570         if (lineno >= view->lines)
2571                 lineno = view->lines > 0 ? view->lines - 1 : 0;
2573         if (offset > lineno || offset + view->height <= lineno) {
2574                 unsigned long half = view->height / 2;
2576                 if (lineno > half)
2577                         offset = lineno - half;
2578                 else
2579                         offset = 0;
2580         }
2582         if (offset != view->offset || lineno != view->lineno) {
2583                 view->offset = offset;
2584                 view->lineno = lineno;
2585                 return TRUE;
2586         }
2588         return FALSE;
2591 /* Scrolling backend */
2592 static void
2593 do_scroll_view(struct view *view, int lines)
2595         bool redraw_current_line = FALSE;
2597         /* The rendering expects the new offset. */
2598         view->offset += lines;
2600         assert(0 <= view->offset && view->offset < view->lines);
2601         assert(lines);
2603         /* Move current line into the view. */
2604         if (view->lineno < view->offset) {
2605                 view->lineno = view->offset;
2606                 redraw_current_line = TRUE;
2607         } else if (view->lineno >= view->offset + view->height) {
2608                 view->lineno = view->offset + view->height - 1;
2609                 redraw_current_line = TRUE;
2610         }
2612         assert(view->offset <= view->lineno && view->lineno < view->lines);
2614         /* Redraw the whole screen if scrolling is pointless. */
2615         if (view->height < ABS(lines)) {
2616                 redraw_view(view);
2618         } else {
2619                 int line = lines > 0 ? view->height - lines : 0;
2620                 int end = line + ABS(lines);
2622                 scrollok(view->win, TRUE);
2623                 wscrl(view->win, lines);
2624                 scrollok(view->win, FALSE);
2626                 while (line < end && draw_view_line(view, line))
2627                         line++;
2629                 if (redraw_current_line)
2630                         draw_view_line(view, view->lineno - view->offset);
2631                 wnoutrefresh(view->win);
2632         }
2634         view->has_scrolled = TRUE;
2635         report("");
2638 /* Scroll frontend */
2639 static void
2640 scroll_view(struct view *view, enum request request)
2642         int lines = 1;
2644         assert(view_is_displayed(view));
2646         switch (request) {
2647         case REQ_SCROLL_LEFT:
2648                 if (view->yoffset == 0) {
2649                         report("Cannot scroll beyond the first column");
2650                         return;
2651                 }
2652                 if (view->yoffset <= apply_step(opt_hscroll, view->width))
2653                         view->yoffset = 0;
2654                 else
2655                         view->yoffset -= apply_step(opt_hscroll, view->width);
2656                 redraw_view_from(view, 0);
2657                 report("");
2658                 return;
2659         case REQ_SCROLL_RIGHT:
2660                 view->yoffset += apply_step(opt_hscroll, view->width);
2661                 redraw_view(view);
2662                 report("");
2663                 return;
2664         case REQ_SCROLL_PAGE_DOWN:
2665                 lines = view->height;
2666         case REQ_SCROLL_LINE_DOWN:
2667                 if (view->offset + lines > view->lines)
2668                         lines = view->lines - view->offset;
2670                 if (lines == 0 || view->offset + view->height >= view->lines) {
2671                         report("Cannot scroll beyond the last line");
2672                         return;
2673                 }
2674                 break;
2676         case REQ_SCROLL_PAGE_UP:
2677                 lines = view->height;
2678         case REQ_SCROLL_LINE_UP:
2679                 if (lines > view->offset)
2680                         lines = view->offset;
2682                 if (lines == 0) {
2683                         report("Cannot scroll beyond the first line");
2684                         return;
2685                 }
2687                 lines = -lines;
2688                 break;
2690         default:
2691                 die("request %d not handled in switch", request);
2692         }
2694         do_scroll_view(view, lines);
2697 /* Cursor moving */
2698 static void
2699 move_view(struct view *view, enum request request)
2701         int scroll_steps = 0;
2702         int steps;
2704         switch (request) {
2705         case REQ_MOVE_FIRST_LINE:
2706                 steps = -view->lineno;
2707                 break;
2709         case REQ_MOVE_LAST_LINE:
2710                 steps = view->lines - view->lineno - 1;
2711                 break;
2713         case REQ_MOVE_PAGE_UP:
2714                 steps = view->height > view->lineno
2715                       ? -view->lineno : -view->height;
2716                 break;
2718         case REQ_MOVE_PAGE_DOWN:
2719                 steps = view->lineno + view->height >= view->lines
2720                       ? view->lines - view->lineno - 1 : view->height;
2721                 break;
2723         case REQ_MOVE_UP:
2724                 steps = -1;
2725                 break;
2727         case REQ_MOVE_DOWN:
2728                 steps = 1;
2729                 break;
2731         default:
2732                 die("request %d not handled in switch", request);
2733         }
2735         if (steps <= 0 && view->lineno == 0) {
2736                 report("Cannot move beyond the first line");
2737                 return;
2739         } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2740                 report("Cannot move beyond the last line");
2741                 return;
2742         }
2744         /* Move the current line */
2745         view->lineno += steps;
2746         assert(0 <= view->lineno && view->lineno < view->lines);
2748         /* Check whether the view needs to be scrolled */
2749         if (view->lineno < view->offset ||
2750             view->lineno >= view->offset + view->height) {
2751                 scroll_steps = steps;
2752                 if (steps < 0 && -steps > view->offset) {
2753                         scroll_steps = -view->offset;
2755                 } else if (steps > 0) {
2756                         if (view->lineno == view->lines - 1 &&
2757                             view->lines > view->height) {
2758                                 scroll_steps = view->lines - view->offset - 1;
2759                                 if (scroll_steps >= view->height)
2760                                         scroll_steps -= view->height - 1;
2761                         }
2762                 }
2763         }
2765         if (!view_is_displayed(view)) {
2766                 view->offset += scroll_steps;
2767                 assert(0 <= view->offset && view->offset < view->lines);
2768                 view->ops->select(view, &view->line[view->lineno]);
2769                 return;
2770         }
2772         /* Repaint the old "current" line if we be scrolling */
2773         if (ABS(steps) < view->height)
2774                 draw_view_line(view, view->lineno - steps - view->offset);
2776         if (scroll_steps) {
2777                 do_scroll_view(view, scroll_steps);
2778                 return;
2779         }
2781         /* Draw the current line */
2782         draw_view_line(view, view->lineno - view->offset);
2784         wnoutrefresh(view->win);
2785         report("");
2789 /*
2790  * Searching
2791  */
2793 static void search_view(struct view *view, enum request request);
2795 static bool
2796 grep_text(struct view *view, const char *text[])
2798         regmatch_t pmatch;
2799         size_t i;
2801         for (i = 0; text[i]; i++)
2802                 if (*text[i] &&
2803                     regexec(view->regex, text[i], 1, &pmatch, 0) != REG_NOMATCH)
2804                         return TRUE;
2805         return FALSE;
2808 static void
2809 select_view_line(struct view *view, unsigned long lineno)
2811         unsigned long old_lineno = view->lineno;
2812         unsigned long old_offset = view->offset;
2814         if (goto_view_line(view, view->offset, lineno)) {
2815                 if (view_is_displayed(view)) {
2816                         if (old_offset != view->offset) {
2817                                 redraw_view(view);
2818                         } else {
2819                                 draw_view_line(view, old_lineno - view->offset);
2820                                 draw_view_line(view, view->lineno - view->offset);
2821                                 wnoutrefresh(view->win);
2822                         }
2823                 } else {
2824                         view->ops->select(view, &view->line[view->lineno]);
2825                 }
2826         }
2829 static void
2830 find_next(struct view *view, enum request request)
2832         unsigned long lineno = view->lineno;
2833         int direction;
2835         if (!*view->grep) {
2836                 if (!*opt_search)
2837                         report("No previous search");
2838                 else
2839                         search_view(view, request);
2840                 return;
2841         }
2843         switch (request) {
2844         case REQ_SEARCH:
2845         case REQ_FIND_NEXT:
2846                 direction = 1;
2847                 break;
2849         case REQ_SEARCH_BACK:
2850         case REQ_FIND_PREV:
2851                 direction = -1;
2852                 break;
2854         default:
2855                 return;
2856         }
2858         if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
2859                 lineno += direction;
2861         /* Note, lineno is unsigned long so will wrap around in which case it
2862          * will become bigger than view->lines. */
2863         for (; lineno < view->lines; lineno += direction) {
2864                 if (view->ops->grep(view, &view->line[lineno])) {
2865                         select_view_line(view, lineno);
2866                         report("Line %ld matches '%s'", lineno + 1, view->grep);
2867                         return;
2868                 }
2869         }
2871         report("No match found for '%s'", view->grep);
2874 static void
2875 search_view(struct view *view, enum request request)
2877         int regex_err;
2879         if (view->regex) {
2880                 regfree(view->regex);
2881                 *view->grep = 0;
2882         } else {
2883                 view->regex = calloc(1, sizeof(*view->regex));
2884                 if (!view->regex)
2885                         return;
2886         }
2888         regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
2889         if (regex_err != 0) {
2890                 char buf[SIZEOF_STR] = "unknown error";
2892                 regerror(regex_err, view->regex, buf, sizeof(buf));
2893                 report("Search failed: %s", buf);
2894                 return;
2895         }
2897         string_copy(view->grep, opt_search);
2899         find_next(view, request);
2902 /*
2903  * Incremental updating
2904  */
2906 static void
2907 reset_view(struct view *view)
2909         int i;
2911         for (i = 0; i < view->lines; i++)
2912                 free(view->line[i].data);
2913         free(view->line);
2915         view->p_offset = view->offset;
2916         view->p_yoffset = view->yoffset;
2917         view->p_lineno = view->lineno;
2919         view->line = NULL;
2920         view->offset = 0;
2921         view->yoffset = 0;
2922         view->lines  = 0;
2923         view->lineno = 0;
2924         view->vid[0] = 0;
2925         view->update_secs = 0;
2928 static void
2929 free_argv(const char *argv[])
2931         int argc;
2933         for (argc = 0; argv[argc]; argc++)
2934                 free((void *) argv[argc]);
2937 static const char *
2938 format_arg(const char *name)
2940         static struct {
2941                 const char *name;
2942                 size_t namelen;
2943                 const char *value;
2944                 const char *value_if_empty;
2945         } vars[] = {
2946 #define FORMAT_VAR(name, value, value_if_empty) \
2947         { name, STRING_SIZE(name), value, value_if_empty }
2948                 FORMAT_VAR("%(directory)",      opt_path,       ""),
2949                 FORMAT_VAR("%(file)",           opt_file,       ""),
2950                 FORMAT_VAR("%(ref)",            opt_ref,        "HEAD"),
2951                 FORMAT_VAR("%(head)",           ref_head,       ""),
2952                 FORMAT_VAR("%(commit)",         ref_commit,     ""),
2953                 FORMAT_VAR("%(blob)",           ref_blob,       ""),
2954         };
2955         int i;
2957         for (i = 0; i < ARRAY_SIZE(vars); i++)
2958                 if (!strncmp(name, vars[i].name, vars[i].namelen))
2959                         return *vars[i].value ? vars[i].value : vars[i].value_if_empty;
2961         return NULL;
2963 static bool
2964 format_argv(const char *dst_argv[], const char *src_argv[], enum format_flags flags)
2966         char buf[SIZEOF_STR];
2967         int argc;
2968         bool noreplace = flags == FORMAT_NONE;
2970         free_argv(dst_argv);
2972         for (argc = 0; src_argv[argc]; argc++) {
2973                 const char *arg = src_argv[argc];
2974                 size_t bufpos = 0;
2976                 while (arg) {
2977                         char *next = strstr(arg, "%(");
2978                         int len = next - arg;
2979                         const char *value;
2981                         if (!next || noreplace) {
2982                                 if (flags == FORMAT_DASH && !strcmp(arg, "--"))
2983                                         noreplace = TRUE;
2984                                 len = strlen(arg);
2985                                 value = "";
2987                         } else {
2988                                 value = format_arg(next);
2990                                 if (!value) {
2991                                         report("Unknown replacement: `%s`", next);
2992                                         return FALSE;
2993                                 }
2994                         }
2996                         if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
2997                                 return FALSE;
2999                         arg = next && !noreplace ? strchr(next, ')') + 1 : NULL;
3000                 }
3002                 dst_argv[argc] = strdup(buf);
3003                 if (!dst_argv[argc])
3004                         break;
3005         }
3007         dst_argv[argc] = NULL;
3009         return src_argv[argc] == NULL;
3012 static bool
3013 restore_view_position(struct view *view)
3015         if (!view->p_restore || (view->pipe && view->lines <= view->p_lineno))
3016                 return FALSE;
3018         /* Changing the view position cancels the restoring. */
3019         /* FIXME: Changing back to the first line is not detected. */
3020         if (view->offset != 0 || view->lineno != 0) {
3021                 view->p_restore = FALSE;
3022                 return FALSE;
3023         }
3025         if (goto_view_line(view, view->p_offset, view->p_lineno) &&
3026             view_is_displayed(view))
3027                 werase(view->win);
3029         view->yoffset = view->p_yoffset;
3030         view->p_restore = FALSE;
3032         return TRUE;
3035 static void
3036 end_update(struct view *view, bool force)
3038         if (!view->pipe)
3039                 return;
3040         while (!view->ops->read(view, NULL))
3041                 if (!force)
3042                         return;
3043         if (force)
3044                 kill_io(view->pipe);
3045         done_io(view->pipe);
3046         view->pipe = NULL;
3049 static void
3050 setup_update(struct view *view, const char *vid)
3052         reset_view(view);
3053         string_copy_rev(view->vid, vid);
3054         view->pipe = &view->io;
3055         view->start_time = time(NULL);
3058 static bool
3059 prepare_update(struct view *view, const char *argv[], const char *dir,
3060                enum format_flags flags)
3062         if (view->pipe)
3063                 end_update(view, TRUE);
3064         return init_io_rd(&view->io, argv, dir, flags);
3067 static bool
3068 prepare_update_file(struct view *view, const char *name)
3070         if (view->pipe)
3071                 end_update(view, TRUE);
3072         return io_open(&view->io, "%s/%s", opt_cdup[0] ? opt_cdup : ".", name);
3075 static bool
3076 begin_update(struct view *view, bool refresh)
3078         if (view->pipe)
3079                 end_update(view, TRUE);
3081         if (!refresh) {
3082                 if (view->ops->prepare) {
3083                         if (!view->ops->prepare(view))
3084                                 return FALSE;
3085                 } else if (!init_io_rd(&view->io, view->ops->argv, NULL, FORMAT_ALL)) {
3086                         return FALSE;
3087                 }
3089                 /* Put the current ref_* value to the view title ref
3090                  * member. This is needed by the blob view. Most other
3091                  * views sets it automatically after loading because the
3092                  * first line is a commit line. */
3093                 string_copy_rev(view->ref, view->id);
3094         }
3096         if (!start_io(&view->io))
3097                 return FALSE;
3099         setup_update(view, view->id);
3101         return TRUE;
3104 static bool
3105 update_view(struct view *view)
3107         char out_buffer[BUFSIZ * 2];
3108         char *line;
3109         /* Clear the view and redraw everything since the tree sorting
3110          * might have rearranged things. */
3111         bool redraw = view->lines == 0;
3112         bool can_read = TRUE;
3114         if (!view->pipe)
3115                 return TRUE;
3117         if (!io_can_read(view->pipe)) {
3118                 if (view->lines == 0 && view_is_displayed(view)) {
3119                         time_t secs = time(NULL) - view->start_time;
3121                         if (secs > 1 && secs > view->update_secs) {
3122                                 if (view->update_secs == 0)
3123                                         redraw_view(view);
3124                                 update_view_title(view);
3125                                 view->update_secs = secs;
3126                         }
3127                 }
3128                 return TRUE;
3129         }
3131         for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
3132                 if (opt_iconv_in != ICONV_NONE) {
3133                         ICONV_CONST char *inbuf = line;
3134                         size_t inlen = strlen(line) + 1;
3136                         char *outbuf = out_buffer;
3137                         size_t outlen = sizeof(out_buffer);
3139                         size_t ret;
3141                         ret = iconv(opt_iconv_in, &inbuf, &inlen, &outbuf, &outlen);
3142                         if (ret != (size_t) -1)
3143                                 line = out_buffer;
3144                 }
3146                 if (!view->ops->read(view, line)) {
3147                         report("Allocation failure");
3148                         end_update(view, TRUE);
3149                         return FALSE;
3150                 }
3151         }
3153         {
3154                 unsigned long lines = view->lines;
3155                 int digits;
3157                 for (digits = 0; lines; digits++)
3158                         lines /= 10;
3160                 /* Keep the displayed view in sync with line number scaling. */
3161                 if (digits != view->digits) {
3162                         view->digits = digits;
3163                         if (opt_line_number || view == VIEW(REQ_VIEW_BLAME))
3164                                 redraw = TRUE;
3165                 }
3166         }
3168         if (io_error(view->pipe)) {
3169                 report("Failed to read: %s", io_strerror(view->pipe));
3170                 end_update(view, TRUE);
3172         } else if (io_eof(view->pipe)) {
3173                 report("");
3174                 end_update(view, FALSE);
3175         }
3177         if (restore_view_position(view))
3178                 redraw = TRUE;
3180         if (!view_is_displayed(view))
3181                 return TRUE;
3183         if (redraw)
3184                 redraw_view_from(view, 0);
3185         else
3186                 redraw_view_dirty(view);
3188         /* Update the title _after_ the redraw so that if the redraw picks up a
3189          * commit reference in view->ref it'll be available here. */
3190         update_view_title(view);
3191         return TRUE;
3194 DEFINE_ALLOCATOR(realloc_lines, struct line, 256)
3196 static struct line *
3197 add_line_data(struct view *view, void *data, enum line_type type)
3199         struct line *line;
3201         if (!realloc_lines(&view->line, view->lines, 1))
3202                 return NULL;
3204         line = &view->line[view->lines++];
3205         memset(line, 0, sizeof(*line));
3206         line->type = type;
3207         line->data = data;
3208         line->dirty = 1;
3210         return line;
3213 static struct line *
3214 add_line_text(struct view *view, const char *text, enum line_type type)
3216         char *data = text ? strdup(text) : NULL;
3218         return data ? add_line_data(view, data, type) : NULL;
3221 static struct line *
3222 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
3224         char buf[SIZEOF_STR];
3225         va_list args;
3227         va_start(args, fmt);
3228         if (vsnprintf(buf, sizeof(buf), fmt, args) >= sizeof(buf))
3229                 buf[0] = 0;
3230         va_end(args);
3232         return buf[0] ? add_line_text(view, buf, type) : NULL;
3235 /*
3236  * View opening
3237  */
3239 enum open_flags {
3240         OPEN_DEFAULT = 0,       /* Use default view switching. */
3241         OPEN_SPLIT = 1,         /* Split current view. */
3242         OPEN_RELOAD = 4,        /* Reload view even if it is the current. */
3243         OPEN_REFRESH = 16,      /* Refresh view using previous command. */
3244         OPEN_PREPARED = 32,     /* Open already prepared command. */
3245 };
3247 static void
3248 open_view(struct view *prev, enum request request, enum open_flags flags)
3250         bool split = !!(flags & OPEN_SPLIT);
3251         bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
3252         bool nomaximize = !!(flags & OPEN_REFRESH);
3253         struct view *view = VIEW(request);
3254         int nviews = displayed_views();
3255         struct view *base_view = display[0];
3257         if (view == prev && nviews == 1 && !reload) {
3258                 report("Already in %s view", view->name);
3259                 return;
3260         }
3262         if (view->git_dir && !opt_git_dir[0]) {
3263                 report("The %s view is disabled in pager view", view->name);
3264                 return;
3265         }
3267         if (split) {
3268                 display[1] = view;
3269                 current_view = 1;
3270         } else if (!nomaximize) {
3271                 /* Maximize the current view. */
3272                 memset(display, 0, sizeof(display));
3273                 current_view = 0;
3274                 display[current_view] = view;
3275         }
3277         /* No parent signals that this is the first loaded view. */
3278         if (prev && view != prev) {
3279                 view->parent = prev;
3280         }
3282         /* Resize the view when switching between split- and full-screen,
3283          * or when switching between two different full-screen views. */
3284         if (nviews != displayed_views() ||
3285             (nviews == 1 && base_view != display[0]))
3286                 resize_display();
3288         if (view->ops->open) {
3289                 if (view->pipe)
3290                         end_update(view, TRUE);
3291                 if (!view->ops->open(view)) {
3292                         report("Failed to load %s view", view->name);
3293                         return;
3294                 }
3295                 restore_view_position(view);
3297         } else if ((reload || strcmp(view->vid, view->id)) &&
3298                    !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
3299                 report("Failed to load %s view", view->name);
3300                 return;
3301         }
3303         if (split && prev->lineno - prev->offset >= prev->height) {
3304                 /* Take the title line into account. */
3305                 int lines = prev->lineno - prev->offset - prev->height + 1;
3307                 /* Scroll the view that was split if the current line is
3308                  * outside the new limited view. */
3309                 do_scroll_view(prev, lines);
3310         }
3312         if (prev && view != prev && split && view_is_displayed(prev)) {
3313                 /* "Blur" the previous view. */
3314                 update_view_title(prev);
3315         }
3317         if (view->pipe && view->lines == 0) {
3318                 /* Clear the old view and let the incremental updating refill
3319                  * the screen. */
3320                 werase(view->win);
3321                 view->p_restore = flags & (OPEN_RELOAD | OPEN_REFRESH);
3322                 report("");
3323         } else if (view_is_displayed(view)) {
3324                 redraw_view(view);
3325                 report("");
3326         }
3329 static void
3330 open_external_viewer(const char *argv[], const char *dir)
3332         def_prog_mode();           /* save current tty modes */
3333         endwin();                  /* restore original tty modes */
3334         run_io_fg(argv, dir);
3335         fprintf(stderr, "Press Enter to continue");
3336         getc(opt_tty);
3337         reset_prog_mode();
3338         redraw_display(TRUE);
3341 static void
3342 open_mergetool(const char *file)
3344         const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
3346         open_external_viewer(mergetool_argv, opt_cdup);
3349 static void
3350 open_editor(const char *file)
3352         const char *editor_argv[] = { "vi", file, NULL };
3353         const char *editor;
3355         editor = getenv("GIT_EDITOR");
3356         if (!editor && *opt_editor)
3357                 editor = opt_editor;
3358         if (!editor)
3359                 editor = getenv("VISUAL");
3360         if (!editor)
3361                 editor = getenv("EDITOR");
3362         if (!editor)
3363                 editor = "vi";
3365         editor_argv[0] = editor;
3366         open_external_viewer(editor_argv, opt_cdup);
3369 static void
3370 open_run_request(enum request request)
3372         struct run_request *req = get_run_request(request);
3373         const char *argv[ARRAY_SIZE(req->argv)] = { NULL };
3375         if (!req) {
3376                 report("Unknown run request");
3377                 return;
3378         }
3380         if (format_argv(argv, req->argv, FORMAT_ALL))
3381                 open_external_viewer(argv, NULL);
3382         free_argv(argv);
3385 /*
3386  * User request switch noodle
3387  */
3389 static int
3390 view_driver(struct view *view, enum request request)
3392         int i;
3394         if (request == REQ_NONE)
3395                 return TRUE;
3397         if (request > REQ_NONE) {
3398                 open_run_request(request);
3399                 /* FIXME: When all views can refresh always do this. */
3400                 if (view == VIEW(REQ_VIEW_STATUS) ||
3401                     view == VIEW(REQ_VIEW_MAIN) ||
3402                     view == VIEW(REQ_VIEW_LOG) ||
3403                     view == VIEW(REQ_VIEW_BRANCH) ||
3404                     view == VIEW(REQ_VIEW_STAGE))
3405                         request = REQ_REFRESH;
3406                 else
3407                         return TRUE;
3408         }
3410         if (view && view->lines) {
3411                 request = view->ops->request(view, request, &view->line[view->lineno]);
3412                 if (request == REQ_NONE)
3413                         return TRUE;
3414         }
3416         switch (request) {
3417         case REQ_MOVE_UP:
3418         case REQ_MOVE_DOWN:
3419         case REQ_MOVE_PAGE_UP:
3420         case REQ_MOVE_PAGE_DOWN:
3421         case REQ_MOVE_FIRST_LINE:
3422         case REQ_MOVE_LAST_LINE:
3423                 move_view(view, request);
3424                 break;
3426         case REQ_SCROLL_LEFT:
3427         case REQ_SCROLL_RIGHT:
3428         case REQ_SCROLL_LINE_DOWN:
3429         case REQ_SCROLL_LINE_UP:
3430         case REQ_SCROLL_PAGE_DOWN:
3431         case REQ_SCROLL_PAGE_UP:
3432                 scroll_view(view, request);
3433                 break;
3435         case REQ_VIEW_BLAME:
3436                 if (!opt_file[0]) {
3437                         report("No file chosen, press %s to open tree view",
3438                                get_key(view->keymap, REQ_VIEW_TREE));
3439                         break;
3440                 }
3441                 open_view(view, request, OPEN_DEFAULT);
3442                 break;
3444         case REQ_VIEW_BLOB:
3445                 if (!ref_blob[0]) {
3446                         report("No file chosen, press %s to open tree view",
3447                                get_key(view->keymap, REQ_VIEW_TREE));
3448                         break;
3449                 }
3450                 open_view(view, request, OPEN_DEFAULT);
3451                 break;
3453         case REQ_VIEW_PAGER:
3454                 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3455                         report("No pager content, press %s to run command from prompt",
3456                                get_key(view->keymap, REQ_PROMPT));
3457                         break;
3458                 }
3459                 open_view(view, request, OPEN_DEFAULT);
3460                 break;
3462         case REQ_VIEW_STAGE:
3463                 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3464                         report("No stage content, press %s to open the status view and choose file",
3465                                get_key(view->keymap, REQ_VIEW_STATUS));
3466                         break;
3467                 }
3468                 open_view(view, request, OPEN_DEFAULT);
3469                 break;
3471         case REQ_VIEW_STATUS:
3472                 if (opt_is_inside_work_tree == FALSE) {
3473                         report("The status view requires a working tree");
3474                         break;
3475                 }
3476                 open_view(view, request, OPEN_DEFAULT);
3477                 break;
3479         case REQ_VIEW_MAIN:
3480         case REQ_VIEW_DIFF:
3481         case REQ_VIEW_LOG:
3482         case REQ_VIEW_TREE:
3483         case REQ_VIEW_HELP:
3484         case REQ_VIEW_BRANCH:
3485                 open_view(view, request, OPEN_DEFAULT);
3486                 break;
3488         case REQ_NEXT:
3489         case REQ_PREVIOUS:
3490                 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3492                 if ((view == VIEW(REQ_VIEW_DIFF) &&
3493                      view->parent == VIEW(REQ_VIEW_MAIN)) ||
3494                    (view == VIEW(REQ_VIEW_DIFF) &&
3495                      view->parent == VIEW(REQ_VIEW_BLAME)) ||
3496                    (view == VIEW(REQ_VIEW_STAGE) &&
3497                      view->parent == VIEW(REQ_VIEW_STATUS)) ||
3498                    (view == VIEW(REQ_VIEW_BLOB) &&
3499                      view->parent == VIEW(REQ_VIEW_TREE)) ||
3500                    (view == VIEW(REQ_VIEW_MAIN) &&
3501                      view->parent == VIEW(REQ_VIEW_BRANCH))) {
3502                         int line;
3504                         view = view->parent;
3505                         line = view->lineno;
3506                         move_view(view, request);
3507                         if (view_is_displayed(view))
3508                                 update_view_title(view);
3509                         if (line != view->lineno)
3510                                 view->ops->request(view, REQ_ENTER,
3511                                                    &view->line[view->lineno]);
3513                 } else {
3514                         move_view(view, request);
3515                 }
3516                 break;
3518         case REQ_VIEW_NEXT:
3519         {
3520                 int nviews = displayed_views();
3521                 int next_view = (current_view + 1) % nviews;
3523                 if (next_view == current_view) {
3524                         report("Only one view is displayed");
3525                         break;
3526                 }
3528                 current_view = next_view;
3529                 /* Blur out the title of the previous view. */
3530                 update_view_title(view);
3531                 report("");
3532                 break;
3533         }
3534         case REQ_REFRESH:
3535                 report("Refreshing is not yet supported for the %s view", view->name);
3536                 break;
3538         case REQ_MAXIMIZE:
3539                 if (displayed_views() == 2)
3540                         maximize_view(view);
3541                 break;
3543         case REQ_OPTIONS:
3544                 open_option_menu();
3545                 break;
3547         case REQ_TOGGLE_LINENO:
3548                 toggle_view_option(&opt_line_number, "line numbers");
3549                 break;
3551         case REQ_TOGGLE_DATE:
3552                 toggle_date();
3553                 break;
3555         case REQ_TOGGLE_AUTHOR:
3556                 toggle_author();
3557                 break;
3559         case REQ_TOGGLE_REV_GRAPH:
3560                 toggle_view_option(&opt_rev_graph, "revision graph display");
3561                 break;
3563         case REQ_TOGGLE_REFS:
3564                 toggle_view_option(&opt_show_refs, "reference display");
3565                 break;
3567         case REQ_TOGGLE_SORT_FIELD:
3568         case REQ_TOGGLE_SORT_ORDER:
3569                 report("Sorting is not yet supported for the %s view", view->name);
3570                 break;
3572         case REQ_SEARCH:
3573         case REQ_SEARCH_BACK:
3574                 search_view(view, request);
3575                 break;
3577         case REQ_FIND_NEXT:
3578         case REQ_FIND_PREV:
3579                 find_next(view, request);
3580                 break;
3582         case REQ_STOP_LOADING:
3583                 for (i = 0; i < ARRAY_SIZE(views); i++) {
3584                         view = &views[i];
3585                         if (view->pipe)
3586                                 report("Stopped loading the %s view", view->name),
3587                         end_update(view, TRUE);
3588                 }
3589                 break;
3591         case REQ_SHOW_VERSION:
3592                 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3593                 return TRUE;
3595         case REQ_SCREEN_REDRAW:
3596                 redraw_display(TRUE);
3597                 break;
3599         case REQ_EDIT:
3600                 report("Nothing to edit");
3601                 break;
3603         case REQ_ENTER:
3604                 report("Nothing to enter");
3605                 break;
3607         case REQ_VIEW_CLOSE:
3608                 /* XXX: Mark closed views by letting view->parent point to the
3609                  * view itself. Parents to closed view should never be
3610                  * followed. */
3611                 if (view->parent &&
3612                     view->parent->parent != view->parent) {
3613                         maximize_view(view->parent);
3614                         view->parent = view;
3615                         break;
3616                 }
3617                 /* Fall-through */
3618         case REQ_QUIT:
3619                 return FALSE;
3621         default:
3622                 report("Unknown key, press %s for help",
3623                        get_key(view->keymap, REQ_VIEW_HELP));
3624                 return TRUE;
3625         }
3627         return TRUE;
3631 /*
3632  * View backend utilities
3633  */
3635 enum sort_field {
3636         ORDERBY_NAME,
3637         ORDERBY_DATE,
3638         ORDERBY_AUTHOR,
3639 };
3641 struct sort_state {
3642         const enum sort_field *fields;
3643         size_t size, current;
3644         bool reverse;
3645 };
3647 #define SORT_STATE(fields) { fields, ARRAY_SIZE(fields), 0 }
3648 #define get_sort_field(state) ((state).fields[(state).current])
3649 #define sort_order(state, result) ((state).reverse ? -(result) : (result))
3651 static void
3652 sort_view(struct view *view, enum request request, struct sort_state *state,
3653           int (*compare)(const void *, const void *))
3655         switch (request) {
3656         case REQ_TOGGLE_SORT_FIELD:
3657                 state->current = (state->current + 1) % state->size;
3658                 break;
3660         case REQ_TOGGLE_SORT_ORDER:
3661                 state->reverse = !state->reverse;
3662                 break;
3663         default:
3664                 die("Not a sort request");
3665         }
3667         qsort(view->line, view->lines, sizeof(*view->line), compare);
3668         redraw_view(view);
3671 DEFINE_ALLOCATOR(realloc_authors, const char *, 256)
3673 /* Small author cache to reduce memory consumption. It uses binary
3674  * search to lookup or find place to position new entries. No entries
3675  * are ever freed. */
3676 static const char *
3677 get_author(const char *name)
3679         static const char **authors;
3680         static size_t authors_size;
3681         int from = 0, to = authors_size - 1;
3683         while (from <= to) {
3684                 size_t pos = (to + from) / 2;
3685                 int cmp = strcmp(name, authors[pos]);
3687                 if (!cmp)
3688                         return authors[pos];
3690                 if (cmp < 0)
3691                         to = pos - 1;
3692                 else
3693                         from = pos + 1;
3694         }
3696         if (!realloc_authors(&authors, authors_size, 1))
3697                 return NULL;
3698         name = strdup(name);
3699         if (!name)
3700                 return NULL;
3702         memmove(authors + from + 1, authors + from, (authors_size - from) * sizeof(*authors));
3703         authors[from] = name;
3704         authors_size++;
3706         return name;
3709 static void
3710 parse_timesec(struct time *time, const char *sec)
3712         time->sec = (time_t) atol(sec);
3715 static void
3716 parse_timezone(struct time *time, const char *zone)
3718         long tz;
3720         tz  = ('0' - zone[1]) * 60 * 60 * 10;
3721         tz += ('0' - zone[2]) * 60 * 60;
3722         tz += ('0' - zone[3]) * 60;
3723         tz += ('0' - zone[4]);
3725         if (zone[0] == '-')
3726                 tz = -tz;
3728         time->tz = tz;
3729         time->sec -= tz;
3732 /* Parse author lines where the name may be empty:
3733  *      author  <email@address.tld> 1138474660 +0100
3734  */
3735 static void
3736 parse_author_line(char *ident, const char **author, struct time *time)
3738         char *nameend = strchr(ident, '<');
3739         char *emailend = strchr(ident, '>');
3741         if (nameend && emailend)
3742                 *nameend = *emailend = 0;
3743         ident = chomp_string(ident);
3744         if (!*ident) {
3745                 if (nameend)
3746                         ident = chomp_string(nameend + 1);
3747                 if (!*ident)
3748                         ident = "Unknown";
3749         }
3751         *author = get_author(ident);
3753         /* Parse epoch and timezone */
3754         if (emailend && emailend[1] == ' ') {
3755                 char *secs = emailend + 2;
3756                 char *zone = strchr(secs, ' ');
3758                 parse_timesec(time, secs);
3760                 if (zone && strlen(zone) == STRING_SIZE(" +0700"))
3761                         parse_timezone(time, zone + 1);
3762         }
3765 static bool
3766 open_commit_parent_menu(char buf[SIZEOF_STR], int *parents)
3768         char rev[SIZEOF_REV];
3769         const char *revlist_argv[] = {
3770                 "git", "log", "--no-color", "-1", "--pretty=format:%s", rev, NULL
3771         };
3772         struct menu_item *items;
3773         char text[SIZEOF_STR];
3774         bool ok = TRUE;
3775         int i;
3777         items = calloc(*parents + 1, sizeof(*items));
3778         if (!items)
3779                 return FALSE;
3781         for (i = 0; i < *parents; i++) {
3782                 string_copy_rev(rev, &buf[SIZEOF_REV * i]);
3783                 if (!run_io_buf(revlist_argv, text, sizeof(text)) ||
3784                     !(items[i].text = strdup(text))) {
3785                         ok = FALSE;
3786                         break;
3787                 }
3788         }
3790         if (ok) {
3791                 *parents = 0;
3792                 ok = prompt_menu("Select parent", items, parents);
3793         }
3794         for (i = 0; items[i].text; i++)
3795                 free((char *) items[i].text);
3796         free(items);
3797         return ok;
3800 static bool
3801 select_commit_parent(const char *id, char rev[SIZEOF_REV], const char *path)
3803         char buf[SIZEOF_STR * 4];
3804         const char *revlist_argv[] = {
3805                 "git", "log", "--no-color", "-1",
3806                         "--pretty=format:%P", id, "--", path, NULL
3807         };
3808         int parents;
3810         if (!run_io_buf(revlist_argv, buf, sizeof(buf)) ||
3811             (parents = strlen(buf) / 40) < 0) {
3812                 report("Failed to get parent information");
3813                 return FALSE;
3815         } else if (parents == 0) {
3816                 if (path)
3817                         report("Path '%s' does not exist in the parent", path);
3818                 else
3819                         report("The selected commit has no parents");
3820                 return FALSE;
3821         }
3823         if (parents > 1 && !open_commit_parent_menu(buf, &parents))
3824                 return FALSE;
3826         string_copy_rev(rev, &buf[41 * parents]);
3827         return TRUE;
3830 /*
3831  * Pager backend
3832  */
3834 static bool
3835 pager_draw(struct view *view, struct line *line, unsigned int lineno)
3837         char text[SIZEOF_STR];
3839         if (opt_line_number && draw_lineno(view, lineno))
3840                 return TRUE;
3842         string_expand(text, sizeof(text), line->data, opt_tab_size);
3843         draw_text(view, line->type, text, TRUE);
3844         return TRUE;
3847 static bool
3848 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
3850         const char *describe_argv[] = { "git", "describe", commit_id, NULL };
3851         char ref[SIZEOF_STR];
3853         if (!run_io_buf(describe_argv, ref, sizeof(ref)) || !*ref)
3854                 return TRUE;
3856         /* This is the only fatal call, since it can "corrupt" the buffer. */
3857         if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
3858                 return FALSE;
3860         return TRUE;
3863 static void
3864 add_pager_refs(struct view *view, struct line *line)
3866         char buf[SIZEOF_STR];
3867         char *commit_id = (char *)line->data + STRING_SIZE("commit ");
3868         struct ref_list *list;
3869         size_t bufpos = 0, i;
3870         const char *sep = "Refs: ";
3871         bool is_tag = FALSE;
3873         assert(line->type == LINE_COMMIT);
3875         list = get_ref_list(commit_id);
3876         if (!list) {
3877                 if (view == VIEW(REQ_VIEW_DIFF))
3878                         goto try_add_describe_ref;
3879                 return;
3880         }
3882         for (i = 0; i < list->size; i++) {
3883                 struct ref *ref = list->refs[i];
3884                 const char *fmt = ref->tag    ? "%s[%s]" :
3885                                   ref->remote ? "%s<%s>" : "%s%s";
3887                 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
3888                         return;
3889                 sep = ", ";
3890                 if (ref->tag)
3891                         is_tag = TRUE;
3892         }
3894         if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
3895 try_add_describe_ref:
3896                 /* Add <tag>-g<commit_id> "fake" reference. */
3897                 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
3898                         return;
3899         }
3901         if (bufpos == 0)
3902                 return;
3904         add_line_text(view, buf, LINE_PP_REFS);
3907 static bool
3908 pager_read(struct view *view, char *data)
3910         struct line *line;
3912         if (!data)
3913                 return TRUE;
3915         line = add_line_text(view, data, get_line_type(data));
3916         if (!line)
3917                 return FALSE;
3919         if (line->type == LINE_COMMIT &&
3920             (view == VIEW(REQ_VIEW_DIFF) ||
3921              view == VIEW(REQ_VIEW_LOG)))
3922                 add_pager_refs(view, line);
3924         return TRUE;
3927 static enum request
3928 pager_request(struct view *view, enum request request, struct line *line)
3930         int split = 0;
3932         if (request != REQ_ENTER)
3933                 return request;
3935         if (line->type == LINE_COMMIT &&
3936            (view == VIEW(REQ_VIEW_LOG) ||
3937             view == VIEW(REQ_VIEW_PAGER))) {
3938                 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
3939                 split = 1;
3940         }
3942         /* Always scroll the view even if it was split. That way
3943          * you can use Enter to scroll through the log view and
3944          * split open each commit diff. */
3945         scroll_view(view, REQ_SCROLL_LINE_DOWN);
3947         /* FIXME: A minor workaround. Scrolling the view will call report("")
3948          * but if we are scrolling a non-current view this won't properly
3949          * update the view title. */
3950         if (split)
3951                 update_view_title(view);
3953         return REQ_NONE;
3956 static bool
3957 pager_grep(struct view *view, struct line *line)
3959         const char *text[] = { line->data, NULL };
3961         return grep_text(view, text);
3964 static void
3965 pager_select(struct view *view, struct line *line)
3967         if (line->type == LINE_COMMIT) {
3968                 char *text = (char *)line->data + STRING_SIZE("commit ");
3970                 if (view != VIEW(REQ_VIEW_PAGER))
3971                         string_copy_rev(view->ref, text);
3972                 string_copy_rev(ref_commit, text);
3973         }
3976 static struct view_ops pager_ops = {
3977         "line",
3978         NULL,
3979         NULL,
3980         pager_read,
3981         pager_draw,
3982         pager_request,
3983         pager_grep,
3984         pager_select,
3985 };
3987 static const char *log_argv[SIZEOF_ARG] = {
3988         "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
3989 };
3991 static enum request
3992 log_request(struct view *view, enum request request, struct line *line)
3994         switch (request) {
3995         case REQ_REFRESH:
3996                 load_refs();
3997                 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
3998                 return REQ_NONE;
3999         default:
4000                 return pager_request(view, request, line);
4001         }
4004 static struct view_ops log_ops = {
4005         "line",
4006         log_argv,
4007         NULL,
4008         pager_read,
4009         pager_draw,
4010         log_request,
4011         pager_grep,
4012         pager_select,
4013 };
4015 static const char *diff_argv[SIZEOF_ARG] = {
4016         "git", "show", "--pretty=fuller", "--no-color", "--root",
4017                 "--patch-with-stat", "--find-copies-harder", "-C", "%(commit)", NULL
4018 };
4020 static struct view_ops diff_ops = {
4021         "line",
4022         diff_argv,
4023         NULL,
4024         pager_read,
4025         pager_draw,
4026         pager_request,
4027         pager_grep,
4028         pager_select,
4029 };
4031 /*
4032  * Help backend
4033  */
4035 static bool help_keymap_hidden[ARRAY_SIZE(keymap_table)];
4037 static bool
4038 help_open_keymap_title(struct view *view, enum keymap keymap)
4040         struct line *line;
4042         line = add_line_format(view, LINE_HELP_KEYMAP, "[%c] %s bindings",
4043                                help_keymap_hidden[keymap] ? '+' : '-',
4044                                enum_name(keymap_table[keymap]));
4045         if (line)
4046                 line->other = keymap;
4048         return help_keymap_hidden[keymap];
4051 static void
4052 help_open_keymap(struct view *view, enum keymap keymap)
4054         const char *group = NULL;
4055         char buf[SIZEOF_STR];
4056         size_t bufpos;
4057         bool add_title = TRUE;
4058         int i;
4060         for (i = 0; i < ARRAY_SIZE(req_info); i++) {
4061                 const char *key = NULL;
4063                 if (req_info[i].request == REQ_NONE)
4064                         continue;
4066                 if (!req_info[i].request) {
4067                         group = req_info[i].help;
4068                         continue;
4069                 }
4071                 key = get_keys(keymap, req_info[i].request, TRUE);
4072                 if (!key || !*key)
4073                         continue;
4075                 if (add_title && help_open_keymap_title(view, keymap))
4076                         return;
4077                 add_title = FALSE;
4079                 if (group) {
4080                         add_line_text(view, group, LINE_HELP_GROUP);
4081                         group = NULL;
4082                 }
4084                 add_line_format(view, LINE_DEFAULT, "    %-25s %-20s %s", key,
4085                                 enum_name(req_info[i]), req_info[i].help);
4086         }
4088         group = "External commands:";
4090         for (i = 0; i < run_requests; i++) {
4091                 struct run_request *req = get_run_request(REQ_NONE + i + 1);
4092                 const char *key;
4093                 int argc;
4095                 if (!req || req->keymap != keymap)
4096                         continue;
4098                 key = get_key_name(req->key);
4099                 if (!*key)
4100                         key = "(no key defined)";
4102                 if (add_title && help_open_keymap_title(view, keymap))
4103                         return;
4104                 if (group) {
4105                         add_line_text(view, group, LINE_HELP_GROUP);
4106                         group = NULL;
4107                 }
4109                 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
4110                         if (!string_format_from(buf, &bufpos, "%s%s",
4111                                                 argc ? " " : "", req->argv[argc]))
4112                                 return;
4114                 add_line_format(view, LINE_DEFAULT, "    %-25s `%s`", key, buf);
4115         }
4118 static bool
4119 help_open(struct view *view)
4121         enum keymap keymap;
4123         reset_view(view);
4124         add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
4125         add_line_text(view, "", LINE_DEFAULT);
4127         for (keymap = 0; keymap < ARRAY_SIZE(keymap_table); keymap++)
4128                 help_open_keymap(view, keymap);
4130         return TRUE;
4133 static enum request
4134 help_request(struct view *view, enum request request, struct line *line)
4136         switch (request) {
4137         case REQ_ENTER:
4138                 if (line->type == LINE_HELP_KEYMAP) {
4139                         help_keymap_hidden[line->other] =
4140                                 !help_keymap_hidden[line->other];
4141                         view->p_restore = TRUE;
4142                         open_view(view, REQ_VIEW_HELP, OPEN_REFRESH);
4143                 }
4145                 return REQ_NONE;
4146         default:
4147                 return pager_request(view, request, line);
4148         }
4151 static struct view_ops help_ops = {
4152         "line",
4153         NULL,
4154         help_open,
4155         NULL,
4156         pager_draw,
4157         help_request,
4158         pager_grep,
4159         pager_select,
4160 };
4163 /*
4164  * Tree backend
4165  */
4167 struct tree_stack_entry {
4168         struct tree_stack_entry *prev;  /* Entry below this in the stack */
4169         unsigned long lineno;           /* Line number to restore */
4170         char *name;                     /* Position of name in opt_path */
4171 };
4173 /* The top of the path stack. */
4174 static struct tree_stack_entry *tree_stack = NULL;
4175 unsigned long tree_lineno = 0;
4177 static void
4178 pop_tree_stack_entry(void)
4180         struct tree_stack_entry *entry = tree_stack;
4182         tree_lineno = entry->lineno;
4183         entry->name[0] = 0;
4184         tree_stack = entry->prev;
4185         free(entry);
4188 static void
4189 push_tree_stack_entry(const char *name, unsigned long lineno)
4191         struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
4192         size_t pathlen = strlen(opt_path);
4194         if (!entry)
4195                 return;
4197         entry->prev = tree_stack;
4198         entry->name = opt_path + pathlen;
4199         tree_stack = entry;
4201         if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
4202                 pop_tree_stack_entry();
4203                 return;
4204         }
4206         /* Move the current line to the first tree entry. */
4207         tree_lineno = 1;
4208         entry->lineno = lineno;
4211 /* Parse output from git-ls-tree(1):
4212  *
4213  * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
4214  */
4216 #define SIZEOF_TREE_ATTR \
4217         STRING_SIZE("100644 blob f931e1d229c3e185caad4449bf5b66ed72462657\t")
4219 #define SIZEOF_TREE_MODE \
4220         STRING_SIZE("100644 ")
4222 #define TREE_ID_OFFSET \
4223         STRING_SIZE("100644 blob ")
4225 struct tree_entry {
4226         char id[SIZEOF_REV];
4227         mode_t mode;
4228         struct time time;               /* Date from the author ident. */
4229         const char *author;             /* Author of the commit. */
4230         char name[1];
4231 };
4233 static const char *
4234 tree_path(const struct line *line)
4236         return ((struct tree_entry *) line->data)->name;
4239 static int
4240 tree_compare_entry(const struct line *line1, const struct line *line2)
4242         if (line1->type != line2->type)
4243                 return line1->type == LINE_TREE_DIR ? -1 : 1;
4244         return strcmp(tree_path(line1), tree_path(line2));
4247 static const enum sort_field tree_sort_fields[] = {
4248         ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
4249 };
4250 static struct sort_state tree_sort_state = SORT_STATE(tree_sort_fields);
4252 static int
4253 tree_compare(const void *l1, const void *l2)
4255         const struct line *line1 = (const struct line *) l1;
4256         const struct line *line2 = (const struct line *) l2;
4257         const struct tree_entry *entry1 = ((const struct line *) l1)->data;
4258         const struct tree_entry *entry2 = ((const struct line *) l2)->data;
4260         if (line1->type == LINE_TREE_HEAD)
4261                 return -1;
4262         if (line2->type == LINE_TREE_HEAD)
4263                 return 1;
4265         switch (get_sort_field(tree_sort_state)) {
4266         case ORDERBY_DATE:
4267                 return sort_order(tree_sort_state, timecmp(&entry1->time, &entry2->time));
4269         case ORDERBY_AUTHOR:
4270                 return sort_order(tree_sort_state, strcmp(entry1->author, entry2->author));
4272         case ORDERBY_NAME:
4273         default:
4274                 return sort_order(tree_sort_state, tree_compare_entry(line1, line2));
4275         }
4279 static struct line *
4280 tree_entry(struct view *view, enum line_type type, const char *path,
4281            const char *mode, const char *id)
4283         struct tree_entry *entry = calloc(1, sizeof(*entry) + strlen(path));
4284         struct line *line = entry ? add_line_data(view, entry, type) : NULL;
4286         if (!entry || !line) {
4287                 free(entry);
4288                 return NULL;
4289         }
4291         strncpy(entry->name, path, strlen(path));
4292         if (mode)
4293                 entry->mode = strtoul(mode, NULL, 8);
4294         if (id)
4295                 string_copy_rev(entry->id, id);
4297         return line;
4300 static bool
4301 tree_read_date(struct view *view, char *text, bool *read_date)
4303         static const char *author_name;
4304         static struct time author_time;
4306         if (!text && *read_date) {
4307                 *read_date = FALSE;
4308                 return TRUE;
4310         } else if (!text) {
4311                 char *path = *opt_path ? opt_path : ".";
4312                 /* Find next entry to process */
4313                 const char *log_file[] = {
4314                         "git", "log", "--no-color", "--pretty=raw",
4315                                 "--cc", "--raw", view->id, "--", path, NULL
4316                 };
4317                 struct io io = {};
4319                 if (!view->lines) {
4320                         tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL);
4321                         report("Tree is empty");
4322                         return TRUE;
4323                 }
4325                 if (!run_io_rd(&io, log_file, opt_cdup, FORMAT_NONE)) {
4326                         report("Failed to load tree data");
4327                         return TRUE;
4328                 }
4330                 done_io(view->pipe);
4331                 view->io = io;
4332                 *read_date = TRUE;
4333                 return FALSE;
4335         } else if (*text == 'a' && get_line_type(text) == LINE_AUTHOR) {
4336                 parse_author_line(text + STRING_SIZE("author "),
4337                                   &author_name, &author_time);
4339         } else if (*text == ':') {
4340                 char *pos;
4341                 size_t annotated = 1;
4342                 size_t i;
4344                 pos = strchr(text, '\t');
4345                 if (!pos)
4346                         return TRUE;
4347                 text = pos + 1;
4348                 if (*opt_path && !strncmp(text, opt_path, strlen(opt_path)))
4349                         text += strlen(opt_path);
4350                 pos = strchr(text, '/');
4351                 if (pos)
4352                         *pos = 0;
4354                 for (i = 1; i < view->lines; i++) {
4355                         struct line *line = &view->line[i];
4356                         struct tree_entry *entry = line->data;
4358                         annotated += !!entry->author;
4359                         if (entry->author || strcmp(entry->name, text))
4360                                 continue;
4362                         entry->author = author_name;
4363                         entry->time = author_time;
4364                         line->dirty = 1;
4365                         break;
4366                 }
4368                 if (annotated == view->lines)
4369                         kill_io(view->pipe);
4370         }
4371         return TRUE;
4374 static bool
4375 tree_read(struct view *view, char *text)
4377         static bool read_date = FALSE;
4378         struct tree_entry *data;
4379         struct line *entry, *line;
4380         enum line_type type;
4381         size_t textlen = text ? strlen(text) : 0;
4382         char *path = text + SIZEOF_TREE_ATTR;
4384         if (read_date || !text)
4385                 return tree_read_date(view, text, &read_date);
4387         if (textlen <= SIZEOF_TREE_ATTR)
4388                 return FALSE;
4389         if (view->lines == 0 &&
4390             !tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL))
4391                 return FALSE;
4393         /* Strip the path part ... */
4394         if (*opt_path) {
4395                 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
4396                 size_t striplen = strlen(opt_path);
4398                 if (pathlen > striplen)
4399                         memmove(path, path + striplen,
4400                                 pathlen - striplen + 1);
4402                 /* Insert "link" to parent directory. */
4403                 if (view->lines == 1 &&
4404                     !tree_entry(view, LINE_TREE_DIR, "..", "040000", view->ref))
4405                         return FALSE;
4406         }
4408         type = text[SIZEOF_TREE_MODE] == 't' ? LINE_TREE_DIR : LINE_TREE_FILE;
4409         entry = tree_entry(view, type, path, text, text + TREE_ID_OFFSET);
4410         if (!entry)
4411                 return FALSE;
4412         data = entry->data;
4414         /* Skip "Directory ..." and ".." line. */
4415         for (line = &view->line[1 + !!*opt_path]; line < entry; line++) {
4416                 if (tree_compare_entry(line, entry) <= 0)
4417                         continue;
4419                 memmove(line + 1, line, (entry - line) * sizeof(*entry));
4421                 line->data = data;
4422                 line->type = type;
4423                 for (; line <= entry; line++)
4424                         line->dirty = line->cleareol = 1;
4425                 return TRUE;
4426         }
4428         if (tree_lineno > view->lineno) {
4429                 view->lineno = tree_lineno;
4430                 tree_lineno = 0;
4431         }
4433         return TRUE;
4436 static bool
4437 tree_draw(struct view *view, struct line *line, unsigned int lineno)
4439         struct tree_entry *entry = line->data;
4441         if (line->type == LINE_TREE_HEAD) {
4442                 if (draw_text(view, line->type, "Directory path /", TRUE))
4443                         return TRUE;
4444         } else {
4445                 if (draw_mode(view, entry->mode))
4446                         return TRUE;
4448                 if (opt_author && draw_author(view, entry->author))
4449                         return TRUE;
4451                 if (opt_date && draw_date(view, &entry->time))
4452                         return TRUE;
4453         }
4454         if (draw_text(view, line->type, entry->name, TRUE))
4455                 return TRUE;
4456         return TRUE;
4459 static void
4460 open_blob_editor()
4462         char file[SIZEOF_STR] = "/tmp/tigblob.XXXXXX";
4463         int fd = mkstemp(file);
4465         if (fd == -1)
4466                 report("Failed to create temporary file");
4467         else if (!run_io_append(blob_ops.argv, FORMAT_ALL, fd))
4468                 report("Failed to save blob data to file");
4469         else
4470                 open_editor(file);
4471         if (fd != -1)
4472                 unlink(file);
4475 static enum request
4476 tree_request(struct view *view, enum request request, struct line *line)
4478         enum open_flags flags;
4480         switch (request) {
4481         case REQ_VIEW_BLAME:
4482                 if (line->type != LINE_TREE_FILE) {
4483                         report("Blame only supported for files");
4484                         return REQ_NONE;
4485                 }
4487                 string_copy(opt_ref, view->vid);
4488                 return request;
4490         case REQ_EDIT:
4491                 if (line->type != LINE_TREE_FILE) {
4492                         report("Edit only supported for files");
4493                 } else if (!is_head_commit(view->vid)) {
4494                         open_blob_editor();
4495                 } else {
4496                         open_editor(opt_file);
4497                 }
4498                 return REQ_NONE;
4500         case REQ_TOGGLE_SORT_FIELD:
4501         case REQ_TOGGLE_SORT_ORDER:
4502                 sort_view(view, request, &tree_sort_state, tree_compare);
4503                 return REQ_NONE;
4505         case REQ_PARENT:
4506                 if (!*opt_path) {
4507                         /* quit view if at top of tree */
4508                         return REQ_VIEW_CLOSE;
4509                 }
4510                 /* fake 'cd  ..' */
4511                 line = &view->line[1];
4512                 break;
4514         case REQ_ENTER:
4515                 break;
4517         default:
4518                 return request;
4519         }
4521         /* Cleanup the stack if the tree view is at a different tree. */
4522         while (!*opt_path && tree_stack)
4523                 pop_tree_stack_entry();
4525         switch (line->type) {
4526         case LINE_TREE_DIR:
4527                 /* Depending on whether it is a subdirectory or parent link
4528                  * mangle the path buffer. */
4529                 if (line == &view->line[1] && *opt_path) {
4530                         pop_tree_stack_entry();
4532                 } else {
4533                         const char *basename = tree_path(line);
4535                         push_tree_stack_entry(basename, view->lineno);
4536                 }
4538                 /* Trees and subtrees share the same ID, so they are not not
4539                  * unique like blobs. */
4540                 flags = OPEN_RELOAD;
4541                 request = REQ_VIEW_TREE;
4542                 break;
4544         case LINE_TREE_FILE:
4545                 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4546                 request = REQ_VIEW_BLOB;
4547                 break;
4549         default:
4550                 return REQ_NONE;
4551         }
4553         open_view(view, request, flags);
4554         if (request == REQ_VIEW_TREE)
4555                 view->lineno = tree_lineno;
4557         return REQ_NONE;
4560 static bool
4561 tree_grep(struct view *view, struct line *line)
4563         struct tree_entry *entry = line->data;
4564         const char *text[] = {
4565                 entry->name,
4566                 opt_author ? entry->author : "",
4567                 opt_date ? mkdate(&entry->time) : "",
4568                 NULL
4569         };
4571         return grep_text(view, text);
4574 static void
4575 tree_select(struct view *view, struct line *line)
4577         struct tree_entry *entry = line->data;
4579         if (line->type == LINE_TREE_FILE) {
4580                 string_copy_rev(ref_blob, entry->id);
4581                 string_format(opt_file, "%s%s", opt_path, tree_path(line));
4583         } else if (line->type != LINE_TREE_DIR) {
4584                 return;
4585         }
4587         string_copy_rev(view->ref, entry->id);
4590 static bool
4591 tree_prepare(struct view *view)
4593         if (view->lines == 0 && opt_prefix[0]) {
4594                 char *pos = opt_prefix;
4596                 while (pos && *pos) {
4597                         char *end = strchr(pos, '/');
4599                         if (end)
4600                                 *end = 0;
4601                         push_tree_stack_entry(pos, 0);
4602                         pos = end;
4603                         if (end) {
4604                                 *end = '/';
4605                                 pos++;
4606                         }
4607                 }
4609         } else if (strcmp(view->vid, view->id)) {
4610                 opt_path[0] = 0;
4611         }
4613         return init_io_rd(&view->io, view->ops->argv, opt_cdup, FORMAT_ALL);
4616 static const char *tree_argv[SIZEOF_ARG] = {
4617         "git", "ls-tree", "%(commit)", "%(directory)", NULL
4618 };
4620 static struct view_ops tree_ops = {
4621         "file",
4622         tree_argv,
4623         NULL,
4624         tree_read,
4625         tree_draw,
4626         tree_request,
4627         tree_grep,
4628         tree_select,
4629         tree_prepare,
4630 };
4632 static bool
4633 blob_read(struct view *view, char *line)
4635         if (!line)
4636                 return TRUE;
4637         return add_line_text(view, line, LINE_DEFAULT) != NULL;
4640 static enum request
4641 blob_request(struct view *view, enum request request, struct line *line)
4643         switch (request) {
4644         case REQ_EDIT:
4645                 open_blob_editor();
4646                 return REQ_NONE;
4647         default:
4648                 return pager_request(view, request, line);
4649         }
4652 static const char *blob_argv[SIZEOF_ARG] = {
4653         "git", "cat-file", "blob", "%(blob)", NULL
4654 };
4656 static struct view_ops blob_ops = {
4657         "line",
4658         blob_argv,
4659         NULL,
4660         blob_read,
4661         pager_draw,
4662         blob_request,
4663         pager_grep,
4664         pager_select,
4665 };
4667 /*
4668  * Blame backend
4669  *
4670  * Loading the blame view is a two phase job:
4671  *
4672  *  1. File content is read either using opt_file from the
4673  *     filesystem or using git-cat-file.
4674  *  2. Then blame information is incrementally added by
4675  *     reading output from git-blame.
4676  */
4678 static const char *blame_head_argv[] = {
4679         "git", "blame", "--incremental", "--", "%(file)", NULL
4680 };
4682 static const char *blame_ref_argv[] = {
4683         "git", "blame", "--incremental", "%(ref)", "--", "%(file)", NULL
4684 };
4686 static const char *blame_cat_file_argv[] = {
4687         "git", "cat-file", "blob", "%(ref):%(file)", NULL
4688 };
4690 struct blame_commit {
4691         char id[SIZEOF_REV];            /* SHA1 ID. */
4692         char title[128];                /* First line of the commit message. */
4693         const char *author;             /* Author of the commit. */
4694         struct time time;               /* Date from the author ident. */
4695         char filename[128];             /* Name of file. */
4696         bool has_previous;              /* Was a "previous" line detected. */
4697 };
4699 struct blame {
4700         struct blame_commit *commit;
4701         unsigned long lineno;
4702         char text[1];
4703 };
4705 static bool
4706 blame_open(struct view *view)
4708         char path[SIZEOF_STR];
4710         if (!view->parent && *opt_prefix) {
4711                 string_copy(path, opt_file);
4712                 if (!string_format(opt_file, "%s%s", opt_prefix, path))
4713                         return FALSE;
4714         }
4716         if (*opt_ref || !io_open(&view->io, "%s%s", opt_cdup, opt_file)) {
4717                 if (!run_io_rd(&view->io, blame_cat_file_argv, opt_cdup, FORMAT_ALL))
4718                         return FALSE;
4719         }
4721         setup_update(view, opt_file);
4722         string_format(view->ref, "%s ...", opt_file);
4724         return TRUE;
4727 static struct blame_commit *
4728 get_blame_commit(struct view *view, const char *id)
4730         size_t i;
4732         for (i = 0; i < view->lines; i++) {
4733                 struct blame *blame = view->line[i].data;
4735                 if (!blame->commit)
4736                         continue;
4738                 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
4739                         return blame->commit;
4740         }
4742         {
4743                 struct blame_commit *commit = calloc(1, sizeof(*commit));
4745                 if (commit)
4746                         string_ncopy(commit->id, id, SIZEOF_REV);
4747                 return commit;
4748         }
4751 static bool
4752 parse_number(const char **posref, size_t *number, size_t min, size_t max)
4754         const char *pos = *posref;
4756         *posref = NULL;
4757         pos = strchr(pos + 1, ' ');
4758         if (!pos || !isdigit(pos[1]))
4759                 return FALSE;
4760         *number = atoi(pos + 1);
4761         if (*number < min || *number > max)
4762                 return FALSE;
4764         *posref = pos;
4765         return TRUE;
4768 static struct blame_commit *
4769 parse_blame_commit(struct view *view, const char *text, int *blamed)
4771         struct blame_commit *commit;
4772         struct blame *blame;
4773         const char *pos = text + SIZEOF_REV - 2;
4774         size_t orig_lineno = 0;
4775         size_t lineno;
4776         size_t group;
4778         if (strlen(text) <= SIZEOF_REV || pos[1] != ' ')
4779                 return NULL;
4781         if (!parse_number(&pos, &orig_lineno, 1, 9999999) ||
4782             !parse_number(&pos, &lineno, 1, view->lines) ||
4783             !parse_number(&pos, &group, 1, view->lines - lineno + 1))
4784                 return NULL;
4786         commit = get_blame_commit(view, text);
4787         if (!commit)
4788                 return NULL;
4790         *blamed += group;
4791         while (group--) {
4792                 struct line *line = &view->line[lineno + group - 1];
4794                 blame = line->data;
4795                 blame->commit = commit;
4796                 blame->lineno = orig_lineno + group - 1;
4797                 line->dirty = 1;
4798         }
4800         return commit;
4803 static bool
4804 blame_read_file(struct view *view, const char *line, bool *read_file)
4806         if (!line) {
4807                 const char **argv = *opt_ref ? blame_ref_argv : blame_head_argv;
4808                 struct io io = {};
4810                 if (view->lines == 0 && !view->parent)
4811                         die("No blame exist for %s", view->vid);
4813                 if (view->lines == 0 || !run_io_rd(&io, argv, opt_cdup, FORMAT_ALL)) {
4814                         report("Failed to load blame data");
4815                         return TRUE;
4816                 }
4818                 done_io(view->pipe);
4819                 view->io = io;
4820                 *read_file = FALSE;
4821                 return FALSE;
4823         } else {
4824                 size_t linelen = strlen(line);
4825                 struct blame *blame = malloc(sizeof(*blame) + linelen);
4827                 if (!blame)
4828                         return FALSE;
4830                 blame->commit = NULL;
4831                 strncpy(blame->text, line, linelen);
4832                 blame->text[linelen] = 0;
4833                 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
4834         }
4837 static bool
4838 match_blame_header(const char *name, char **line)
4840         size_t namelen = strlen(name);
4841         bool matched = !strncmp(name, *line, namelen);
4843         if (matched)
4844                 *line += namelen;
4846         return matched;
4849 static bool
4850 blame_read(struct view *view, char *line)
4852         static struct blame_commit *commit = NULL;
4853         static int blamed = 0;
4854         static bool read_file = TRUE;
4856         if (read_file)
4857                 return blame_read_file(view, line, &read_file);
4859         if (!line) {
4860                 /* Reset all! */
4861                 commit = NULL;
4862                 blamed = 0;
4863                 read_file = TRUE;
4864                 string_format(view->ref, "%s", view->vid);
4865                 if (view_is_displayed(view)) {
4866                         update_view_title(view);
4867                         redraw_view_from(view, 0);
4868                 }
4869                 return TRUE;
4870         }
4872         if (!commit) {
4873                 commit = parse_blame_commit(view, line, &blamed);
4874                 string_format(view->ref, "%s %2d%%", view->vid,
4875                               view->lines ? blamed * 100 / view->lines : 0);
4877         } else if (match_blame_header("author ", &line)) {
4878                 commit->author = get_author(line);
4880         } else if (match_blame_header("author-time ", &line)) {
4881                 parse_timesec(&commit->time, line);
4883         } else if (match_blame_header("author-tz ", &line)) {
4884                 parse_timezone(&commit->time, line);
4886         } else if (match_blame_header("summary ", &line)) {
4887                 string_ncopy(commit->title, line, strlen(line));
4889         } else if (match_blame_header("previous ", &line)) {
4890                 commit->has_previous = TRUE;
4892         } else if (match_blame_header("filename ", &line)) {
4893                 string_ncopy(commit->filename, line, strlen(line));
4894                 commit = NULL;
4895         }
4897         return TRUE;
4900 static bool
4901 blame_draw(struct view *view, struct line *line, unsigned int lineno)
4903         struct blame *blame = line->data;
4904         struct time *time = NULL;
4905         const char *id = NULL, *author = NULL;
4906         char text[SIZEOF_STR];
4908         if (blame->commit && *blame->commit->filename) {
4909                 id = blame->commit->id;
4910                 author = blame->commit->author;
4911                 time = &blame->commit->time;
4912         }
4914         if (opt_date && draw_date(view, time))
4915                 return TRUE;
4917         if (opt_author && draw_author(view, author))
4918                 return TRUE;
4920         if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
4921                 return TRUE;
4923         if (draw_lineno(view, lineno))
4924                 return TRUE;
4926         string_expand(text, sizeof(text), blame->text, opt_tab_size);
4927         draw_text(view, LINE_DEFAULT, text, TRUE);
4928         return TRUE;
4931 static bool
4932 check_blame_commit(struct blame *blame, bool check_null_id)
4934         if (!blame->commit)
4935                 report("Commit data not loaded yet");
4936         else if (check_null_id && !strcmp(blame->commit->id, NULL_ID))
4937                 report("No commit exist for the selected line");
4938         else
4939                 return TRUE;
4940         return FALSE;
4943 static void
4944 setup_blame_parent_line(struct view *view, struct blame *blame)
4946         const char *diff_tree_argv[] = {
4947                 "git", "diff-tree", "-U0", blame->commit->id,
4948                         "--", blame->commit->filename, NULL
4949         };
4950         struct io io = {};
4951         int parent_lineno = -1;
4952         int blamed_lineno = -1;
4953         char *line;
4955         if (!run_io(&io, diff_tree_argv, NULL, IO_RD))
4956                 return;
4958         while ((line = io_get(&io, '\n', TRUE))) {
4959                 if (*line == '@') {
4960                         char *pos = strchr(line, '+');
4962                         parent_lineno = atoi(line + 4);
4963                         if (pos)
4964                                 blamed_lineno = atoi(pos + 1);
4966                 } else if (*line == '+' && parent_lineno != -1) {
4967                         if (blame->lineno == blamed_lineno - 1 &&
4968                             !strcmp(blame->text, line + 1)) {
4969                                 view->lineno = parent_lineno ? parent_lineno - 1 : 0;
4970                                 break;
4971                         }
4972                         blamed_lineno++;
4973                 }
4974         }
4976         done_io(&io);
4979 static enum request
4980 blame_request(struct view *view, enum request request, struct line *line)
4982         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4983         struct blame *blame = line->data;
4985         switch (request) {
4986         case REQ_VIEW_BLAME:
4987                 if (check_blame_commit(blame, TRUE)) {
4988                         string_copy(opt_ref, blame->commit->id);
4989                         string_copy(opt_file, blame->commit->filename);
4990                         if (blame->lineno)
4991                                 view->lineno = blame->lineno;
4992                         open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4993                 }
4994                 break;
4996         case REQ_PARENT:
4997                 if (check_blame_commit(blame, TRUE) &&
4998                     select_commit_parent(blame->commit->id, opt_ref,
4999                                          blame->commit->filename)) {
5000                         string_copy(opt_file, blame->commit->filename);
5001                         setup_blame_parent_line(view, blame);
5002                         open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
5003                 }
5004                 break;
5006         case REQ_ENTER:
5007                 if (!check_blame_commit(blame, FALSE))
5008                         break;
5010                 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
5011                     !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
5012                         break;
5014                 if (!strcmp(blame->commit->id, NULL_ID)) {
5015                         struct view *diff = VIEW(REQ_VIEW_DIFF);
5016                         const char *diff_index_argv[] = {
5017                                 "git", "diff-index", "--root", "--patch-with-stat",
5018                                         "-C", "-M", "HEAD", "--", view->vid, NULL
5019                         };
5021                         if (!blame->commit->has_previous) {
5022                                 diff_index_argv[1] = "diff";
5023                                 diff_index_argv[2] = "--no-color";
5024                                 diff_index_argv[6] = "--";
5025                                 diff_index_argv[7] = "/dev/null";
5026                         }
5028                         if (!prepare_update(diff, diff_index_argv, NULL, FORMAT_DASH)) {
5029                                 report("Failed to allocate diff command");
5030                                 break;
5031                         }
5032                         flags |= OPEN_PREPARED;
5033                 }
5035                 open_view(view, REQ_VIEW_DIFF, flags);
5036                 if (VIEW(REQ_VIEW_DIFF)->pipe && !strcmp(blame->commit->id, NULL_ID))
5037                         string_copy_rev(VIEW(REQ_VIEW_DIFF)->ref, NULL_ID);
5038                 break;
5040         default:
5041                 return request;
5042         }
5044         return REQ_NONE;
5047 static bool
5048 blame_grep(struct view *view, struct line *line)
5050         struct blame *blame = line->data;
5051         struct blame_commit *commit = blame->commit;
5052         const char *text[] = {
5053                 blame->text,
5054                 commit ? commit->title : "",
5055                 commit ? commit->id : "",
5056                 commit && opt_author ? commit->author : "",
5057                 commit && opt_date ? mkdate(&commit->time) : "",
5058                 NULL
5059         };
5061         return grep_text(view, text);
5064 static void
5065 blame_select(struct view *view, struct line *line)
5067         struct blame *blame = line->data;
5068         struct blame_commit *commit = blame->commit;
5070         if (!commit)
5071                 return;
5073         if (!strcmp(commit->id, NULL_ID))
5074                 string_ncopy(ref_commit, "HEAD", 4);
5075         else
5076                 string_copy_rev(ref_commit, commit->id);
5079 static struct view_ops blame_ops = {
5080         "line",
5081         NULL,
5082         blame_open,
5083         blame_read,
5084         blame_draw,
5085         blame_request,
5086         blame_grep,
5087         blame_select,
5088 };
5090 /*
5091  * Branch backend
5092  */
5094 struct branch {
5095         const char *author;             /* Author of the last commit. */
5096         struct time time;               /* Date of the last activity. */
5097         const struct ref *ref;          /* Name and commit ID information. */
5098 };
5100 static const struct ref branch_all;
5102 static const enum sort_field branch_sort_fields[] = {
5103         ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
5104 };
5105 static struct sort_state branch_sort_state = SORT_STATE(branch_sort_fields);
5107 static int
5108 branch_compare(const void *l1, const void *l2)
5110         const struct branch *branch1 = ((const struct line *) l1)->data;
5111         const struct branch *branch2 = ((const struct line *) l2)->data;
5113         switch (get_sort_field(branch_sort_state)) {
5114         case ORDERBY_DATE:
5115                 return sort_order(branch_sort_state, timecmp(&branch1->time, &branch2->time));
5117         case ORDERBY_AUTHOR:
5118                 return sort_order(branch_sort_state, strcmp(branch1->author, branch2->author));
5120         case ORDERBY_NAME:
5121         default:
5122                 return sort_order(branch_sort_state, strcmp(branch1->ref->name, branch2->ref->name));
5123         }
5126 static bool
5127 branch_draw(struct view *view, struct line *line, unsigned int lineno)
5129         struct branch *branch = line->data;
5130         enum line_type type = branch->ref->head ? LINE_MAIN_HEAD : LINE_DEFAULT;
5132         if (opt_date && draw_date(view, &branch->time))
5133                 return TRUE;
5135         if (opt_author && draw_author(view, branch->author))
5136                 return TRUE;
5138         draw_text(view, type, branch->ref == &branch_all ? "All branches" : branch->ref->name, TRUE);
5139         return TRUE;
5142 static enum request
5143 branch_request(struct view *view, enum request request, struct line *line)
5145         struct branch *branch = line->data;
5147         switch (request) {
5148         case REQ_REFRESH:
5149                 load_refs();
5150                 open_view(view, REQ_VIEW_BRANCH, OPEN_REFRESH);
5151                 return REQ_NONE;
5153         case REQ_TOGGLE_SORT_FIELD:
5154         case REQ_TOGGLE_SORT_ORDER:
5155                 sort_view(view, request, &branch_sort_state, branch_compare);
5156                 return REQ_NONE;
5158         case REQ_ENTER:
5159                 if (branch->ref == &branch_all) {
5160                         const char *all_branches_argv[] = {
5161                                 "git", "log", "--no-color", "--pretty=raw", "--parents",
5162                                       "--topo-order", "--all", NULL
5163                         };
5164                         struct view *main_view = VIEW(REQ_VIEW_MAIN);
5166                         if (!prepare_update(main_view, all_branches_argv, NULL, FORMAT_NONE)) {
5167                                 report("Failed to load view of all branches");
5168                                 return REQ_NONE;
5169                         }
5170                         open_view(view, REQ_VIEW_MAIN, OPEN_PREPARED | OPEN_SPLIT);
5171                 } else {
5172                         open_view(view, REQ_VIEW_MAIN, OPEN_SPLIT);
5173                 }
5174                 return REQ_NONE;
5176         default:
5177                 return request;
5178         }
5181 static bool
5182 branch_read(struct view *view, char *line)
5184         static char id[SIZEOF_REV];
5185         struct branch *reference;
5186         size_t i;
5188         if (!line)
5189                 return TRUE;
5191         switch (get_line_type(line)) {
5192         case LINE_COMMIT:
5193                 string_copy_rev(id, line + STRING_SIZE("commit "));
5194                 return TRUE;
5196         case LINE_AUTHOR:
5197                 for (i = 0, reference = NULL; i < view->lines; i++) {
5198                         struct branch *branch = view->line[i].data;
5200                         if (strcmp(branch->ref->id, id))
5201                                 continue;
5203                         view->line[i].dirty = TRUE;
5204                         if (reference) {
5205                                 branch->author = reference->author;
5206                                 branch->time = reference->time;
5207                                 continue;
5208                         }
5210                         parse_author_line(line + STRING_SIZE("author "),
5211                                           &branch->author, &branch->time);
5212                         reference = branch;
5213                 }
5214                 return TRUE;
5216         default:
5217                 return TRUE;
5218         }
5222 static bool
5223 branch_open_visitor(void *data, const struct ref *ref)
5225         struct view *view = data;
5226         struct branch *branch;
5228         if (ref->tag || ref->ltag || ref->remote)
5229                 return TRUE;
5231         branch = calloc(1, sizeof(*branch));
5232         if (!branch)
5233                 return FALSE;
5235         branch->ref = ref;
5236         return !!add_line_data(view, branch, LINE_DEFAULT);
5239 static bool
5240 branch_open(struct view *view)
5242         const char *branch_log[] = {
5243                 "git", "log", "--no-color", "--pretty=raw",
5244                         "--simplify-by-decoration", "--all", NULL
5245         };
5247         if (!run_io_rd(&view->io, branch_log, NULL, FORMAT_NONE)) {
5248                 report("Failed to load branch data");
5249                 return TRUE;
5250         }
5252         setup_update(view, view->id);
5253         branch_open_visitor(view, &branch_all);
5254         foreach_ref(branch_open_visitor, view);
5255         view->p_restore = TRUE;
5257         return TRUE;
5260 static bool
5261 branch_grep(struct view *view, struct line *line)
5263         struct branch *branch = line->data;
5264         const char *text[] = {
5265                 branch->ref->name,
5266                 branch->author,
5267                 NULL
5268         };
5270         return grep_text(view, text);
5273 static void
5274 branch_select(struct view *view, struct line *line)
5276         struct branch *branch = line->data;
5278         string_copy_rev(view->ref, branch->ref->id);
5279         string_copy_rev(ref_commit, branch->ref->id);
5280         string_copy_rev(ref_head, branch->ref->id);
5283 static struct view_ops branch_ops = {
5284         "branch",
5285         NULL,
5286         branch_open,
5287         branch_read,
5288         branch_draw,
5289         branch_request,
5290         branch_grep,
5291         branch_select,
5292 };
5294 /*
5295  * Status backend
5296  */
5298 struct status {
5299         char status;
5300         struct {
5301                 mode_t mode;
5302                 char rev[SIZEOF_REV];
5303                 char name[SIZEOF_STR];
5304         } old;
5305         struct {
5306                 mode_t mode;
5307                 char rev[SIZEOF_REV];
5308                 char name[SIZEOF_STR];
5309         } new;
5310 };
5312 static char status_onbranch[SIZEOF_STR];
5313 static struct status stage_status;
5314 static enum line_type stage_line_type;
5315 static size_t stage_chunks;
5316 static int *stage_chunk;
5318 DEFINE_ALLOCATOR(realloc_ints, int, 32)
5320 /* This should work even for the "On branch" line. */
5321 static inline bool
5322 status_has_none(struct view *view, struct line *line)
5324         return line < view->line + view->lines && !line[1].data;
5327 /* Get fields from the diff line:
5328  * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
5329  */
5330 static inline bool
5331 status_get_diff(struct status *file, const char *buf, size_t bufsize)
5333         const char *old_mode = buf +  1;
5334         const char *new_mode = buf +  8;
5335         const char *old_rev  = buf + 15;
5336         const char *new_rev  = buf + 56;
5337         const char *status   = buf + 97;
5339         if (bufsize < 98 ||
5340             old_mode[-1] != ':' ||
5341             new_mode[-1] != ' ' ||
5342             old_rev[-1]  != ' ' ||
5343             new_rev[-1]  != ' ' ||
5344             status[-1]   != ' ')
5345                 return FALSE;
5347         file->status = *status;
5349         string_copy_rev(file->old.rev, old_rev);
5350         string_copy_rev(file->new.rev, new_rev);
5352         file->old.mode = strtoul(old_mode, NULL, 8);
5353         file->new.mode = strtoul(new_mode, NULL, 8);
5355         file->old.name[0] = file->new.name[0] = 0;
5357         return TRUE;
5360 static bool
5361 status_run(struct view *view, const char *argv[], char status, enum line_type type)
5363         struct status *unmerged = NULL;
5364         char *buf;
5365         struct io io = {};
5367         if (!run_io(&io, argv, opt_cdup, IO_RD))
5368                 return FALSE;
5370         add_line_data(view, NULL, type);
5372         while ((buf = io_get(&io, 0, TRUE))) {
5373                 struct status *file = unmerged;
5375                 if (!file) {
5376                         file = calloc(1, sizeof(*file));
5377                         if (!file || !add_line_data(view, file, type))
5378                                 goto error_out;
5379                 }
5381                 /* Parse diff info part. */
5382                 if (status) {
5383                         file->status = status;
5384                         if (status == 'A')
5385                                 string_copy(file->old.rev, NULL_ID);
5387                 } else if (!file->status || file == unmerged) {
5388                         if (!status_get_diff(file, buf, strlen(buf)))
5389                                 goto error_out;
5391                         buf = io_get(&io, 0, TRUE);
5392                         if (!buf)
5393                                 break;
5395                         /* Collapse all modified entries that follow an
5396                          * associated unmerged entry. */
5397                         if (unmerged == file) {
5398                                 unmerged->status = 'U';
5399                                 unmerged = NULL;
5400                         } else if (file->status == 'U') {
5401                                 unmerged = file;
5402                         }
5403                 }
5405                 /* Grab the old name for rename/copy. */
5406                 if (!*file->old.name &&
5407                     (file->status == 'R' || file->status == 'C')) {
5408                         string_ncopy(file->old.name, buf, strlen(buf));
5410                         buf = io_get(&io, 0, TRUE);
5411                         if (!buf)
5412                                 break;
5413                 }
5415                 /* git-ls-files just delivers a NUL separated list of
5416                  * file names similar to the second half of the
5417                  * git-diff-* output. */
5418                 string_ncopy(file->new.name, buf, strlen(buf));
5419                 if (!*file->old.name)
5420                         string_copy(file->old.name, file->new.name);
5421                 file = NULL;
5422         }
5424         if (io_error(&io)) {
5425 error_out:
5426                 done_io(&io);
5427                 return FALSE;
5428         }
5430         if (!view->line[view->lines - 1].data)
5431                 add_line_data(view, NULL, LINE_STAT_NONE);
5433         done_io(&io);
5434         return TRUE;
5437 /* Don't show unmerged entries in the staged section. */
5438 static const char *status_diff_index_argv[] = {
5439         "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
5440                              "--cached", "-M", "HEAD", NULL
5441 };
5443 static const char *status_diff_files_argv[] = {
5444         "git", "diff-files", "-z", NULL
5445 };
5447 static const char *status_list_other_argv[] = {
5448         "git", "ls-files", "-z", "--others", "--exclude-standard", opt_prefix, NULL
5449 };
5451 static const char *status_list_no_head_argv[] = {
5452         "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
5453 };
5455 static const char *update_index_argv[] = {
5456         "git", "update-index", "-q", "--unmerged", "--refresh", NULL
5457 };
5459 /* Restore the previous line number to stay in the context or select a
5460  * line with something that can be updated. */
5461 static void
5462 status_restore(struct view *view)
5464         if (view->p_lineno >= view->lines)
5465                 view->p_lineno = view->lines - 1;
5466         while (view->p_lineno < view->lines && !view->line[view->p_lineno].data)
5467                 view->p_lineno++;
5468         while (view->p_lineno > 0 && !view->line[view->p_lineno].data)
5469                 view->p_lineno--;
5471         /* If the above fails, always skip the "On branch" line. */
5472         if (view->p_lineno < view->lines)
5473                 view->lineno = view->p_lineno;
5474         else
5475                 view->lineno = 1;
5477         if (view->lineno < view->offset)
5478                 view->offset = view->lineno;
5479         else if (view->offset + view->height <= view->lineno)
5480                 view->offset = view->lineno - view->height + 1;
5482         view->p_restore = FALSE;
5485 static void
5486 status_update_onbranch(void)
5488         static const char *paths[][2] = {
5489                 { "rebase-apply/rebasing",      "Rebasing" },
5490                 { "rebase-apply/applying",      "Applying mailbox" },
5491                 { "rebase-apply/",              "Rebasing mailbox" },
5492                 { "rebase-merge/interactive",   "Interactive rebase" },
5493                 { "rebase-merge/",              "Rebase merge" },
5494                 { "MERGE_HEAD",                 "Merging" },
5495                 { "BISECT_LOG",                 "Bisecting" },
5496                 { "HEAD",                       "On branch" },
5497         };
5498         char buf[SIZEOF_STR];
5499         struct stat stat;
5500         int i;
5502         if (is_initial_commit()) {
5503                 string_copy(status_onbranch, "Initial commit");
5504                 return;
5505         }
5507         for (i = 0; i < ARRAY_SIZE(paths); i++) {
5508                 char *head = opt_head;
5510                 if (!string_format(buf, "%s/%s", opt_git_dir, paths[i][0]) ||
5511                     lstat(buf, &stat) < 0)
5512                         continue;
5514                 if (!*opt_head) {
5515                         struct io io = {};
5517                         if (io_open(&io, "%s/rebase-merge/head-name", opt_git_dir) &&
5518                             io_read_buf(&io, buf, sizeof(buf))) {
5519                                 head = buf;
5520                                 if (!prefixcmp(head, "refs/heads/"))
5521                                         head += STRING_SIZE("refs/heads/");
5522                         }
5523                 }
5525                 if (!string_format(status_onbranch, "%s %s", paths[i][1], head))
5526                         string_copy(status_onbranch, opt_head);
5527                 return;
5528         }
5530         string_copy(status_onbranch, "Not currently on any branch");
5533 /* First parse staged info using git-diff-index(1), then parse unstaged
5534  * info using git-diff-files(1), and finally untracked files using
5535  * git-ls-files(1). */
5536 static bool
5537 status_open(struct view *view)
5539         reset_view(view);
5541         add_line_data(view, NULL, LINE_STAT_HEAD);
5542         status_update_onbranch();
5544         run_io_bg(update_index_argv);
5546         if (is_initial_commit()) {
5547                 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
5548                         return FALSE;
5549         } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
5550                 return FALSE;
5551         }
5553         if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
5554             !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
5555                 return FALSE;
5557         /* Restore the exact position or use the specialized restore
5558          * mode? */
5559         if (!view->p_restore)
5560                 status_restore(view);
5561         return TRUE;
5564 static bool
5565 status_draw(struct view *view, struct line *line, unsigned int lineno)
5567         struct status *status = line->data;
5568         enum line_type type;
5569         const char *text;
5571         if (!status) {
5572                 switch (line->type) {
5573                 case LINE_STAT_STAGED:
5574                         type = LINE_STAT_SECTION;
5575                         text = "Changes to be committed:";
5576                         break;
5578                 case LINE_STAT_UNSTAGED:
5579                         type = LINE_STAT_SECTION;
5580                         text = "Changed but not updated:";
5581                         break;
5583                 case LINE_STAT_UNTRACKED:
5584                         type = LINE_STAT_SECTION;
5585                         text = "Untracked files:";
5586                         break;
5588                 case LINE_STAT_NONE:
5589                         type = LINE_DEFAULT;
5590                         text = "  (no files)";
5591                         break;
5593                 case LINE_STAT_HEAD:
5594                         type = LINE_STAT_HEAD;
5595                         text = status_onbranch;
5596                         break;
5598                 default:
5599                         return FALSE;
5600                 }
5601         } else {
5602                 static char buf[] = { '?', ' ', ' ', ' ', 0 };
5604                 buf[0] = status->status;
5605                 if (draw_text(view, line->type, buf, TRUE))
5606                         return TRUE;
5607                 type = LINE_DEFAULT;
5608                 text = status->new.name;
5609         }
5611         draw_text(view, type, text, TRUE);
5612         return TRUE;
5615 static enum request
5616 status_load_error(struct view *view, struct view *stage, const char *path)
5618         if (displayed_views() == 2 || display[current_view] != view)
5619                 maximize_view(view);
5620         report("Failed to load '%s': %s", path, io_strerror(&stage->io));
5621         return REQ_NONE;
5624 static enum request
5625 status_enter(struct view *view, struct line *line)
5627         struct status *status = line->data;
5628         const char *oldpath = status ? status->old.name : NULL;
5629         /* Diffs for unmerged entries are empty when passing the new
5630          * path, so leave it empty. */
5631         const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
5632         const char *info;
5633         enum open_flags split;
5634         struct view *stage = VIEW(REQ_VIEW_STAGE);
5636         if (line->type == LINE_STAT_NONE ||
5637             (!status && line[1].type == LINE_STAT_NONE)) {
5638                 report("No file to diff");
5639                 return REQ_NONE;
5640         }
5642         switch (line->type) {
5643         case LINE_STAT_STAGED:
5644                 if (is_initial_commit()) {
5645                         const char *no_head_diff_argv[] = {
5646                                 "git", "diff", "--no-color", "--patch-with-stat",
5647                                         "--", "/dev/null", newpath, NULL
5648                         };
5650                         if (!prepare_update(stage, no_head_diff_argv, opt_cdup, FORMAT_DASH))
5651                                 return status_load_error(view, stage, newpath);
5652                 } else {
5653                         const char *index_show_argv[] = {
5654                                 "git", "diff-index", "--root", "--patch-with-stat",
5655                                         "-C", "-M", "--cached", "HEAD", "--",
5656                                         oldpath, newpath, NULL
5657                         };
5659                         if (!prepare_update(stage, index_show_argv, opt_cdup, FORMAT_DASH))
5660                                 return status_load_error(view, stage, newpath);
5661                 }
5663                 if (status)
5664                         info = "Staged changes to %s";
5665                 else
5666                         info = "Staged changes";
5667                 break;
5669         case LINE_STAT_UNSTAGED:
5670         {
5671                 const char *files_show_argv[] = {
5672                         "git", "diff-files", "--root", "--patch-with-stat",
5673                                 "-C", "-M", "--", oldpath, newpath, NULL
5674                 };
5676                 if (!prepare_update(stage, files_show_argv, opt_cdup, FORMAT_DASH))
5677                         return status_load_error(view, stage, newpath);
5678                 if (status)
5679                         info = "Unstaged changes to %s";
5680                 else
5681                         info = "Unstaged changes";
5682                 break;
5683         }
5684         case LINE_STAT_UNTRACKED:
5685                 if (!newpath) {
5686                         report("No file to show");
5687                         return REQ_NONE;
5688                 }
5690                 if (!suffixcmp(status->new.name, -1, "/")) {
5691                         report("Cannot display a directory");
5692                         return REQ_NONE;
5693                 }
5695                 if (!prepare_update_file(stage, newpath))
5696                         return status_load_error(view, stage, newpath);
5697                 info = "Untracked file %s";
5698                 break;
5700         case LINE_STAT_HEAD:
5701                 return REQ_NONE;
5703         default:
5704                 die("line type %d not handled in switch", line->type);
5705         }
5707         split = view_is_displayed(view) ? OPEN_SPLIT : 0;
5708         open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split);
5709         if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
5710                 if (status) {
5711                         stage_status = *status;
5712                 } else {
5713                         memset(&stage_status, 0, sizeof(stage_status));
5714                 }
5716                 stage_line_type = line->type;
5717                 stage_chunks = 0;
5718                 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
5719         }
5721         return REQ_NONE;
5724 static bool
5725 status_exists(struct status *status, enum line_type type)
5727         struct view *view = VIEW(REQ_VIEW_STATUS);
5728         unsigned long lineno;
5730         for (lineno = 0; lineno < view->lines; lineno++) {
5731                 struct line *line = &view->line[lineno];
5732                 struct status *pos = line->data;
5734                 if (line->type != type)
5735                         continue;
5736                 if (!pos && (!status || !status->status) && line[1].data) {
5737                         select_view_line(view, lineno);
5738                         return TRUE;
5739                 }
5740                 if (pos && !strcmp(status->new.name, pos->new.name)) {
5741                         select_view_line(view, lineno);
5742                         return TRUE;
5743                 }
5744         }
5746         return FALSE;
5750 static bool
5751 status_update_prepare(struct io *io, enum line_type type)
5753         const char *staged_argv[] = {
5754                 "git", "update-index", "-z", "--index-info", NULL
5755         };
5756         const char *others_argv[] = {
5757                 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
5758         };
5760         switch (type) {
5761         case LINE_STAT_STAGED:
5762                 return run_io(io, staged_argv, opt_cdup, IO_WR);
5764         case LINE_STAT_UNSTAGED:
5765         case LINE_STAT_UNTRACKED:
5766                 return run_io(io, others_argv, opt_cdup, IO_WR);
5768         default:
5769                 die("line type %d not handled in switch", type);
5770                 return FALSE;
5771         }
5774 static bool
5775 status_update_write(struct io *io, struct status *status, enum line_type type)
5777         char buf[SIZEOF_STR];
5778         size_t bufsize = 0;
5780         switch (type) {
5781         case LINE_STAT_STAGED:
5782                 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
5783                                         status->old.mode,
5784                                         status->old.rev,
5785                                         status->old.name, 0))
5786                         return FALSE;
5787                 break;
5789         case LINE_STAT_UNSTAGED:
5790         case LINE_STAT_UNTRACKED:
5791                 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
5792                         return FALSE;
5793                 break;
5795         default:
5796                 die("line type %d not handled in switch", type);
5797         }
5799         return io_write(io, buf, bufsize);
5802 static bool
5803 status_update_file(struct status *status, enum line_type type)
5805         struct io io = {};
5806         bool result;
5808         if (!status_update_prepare(&io, type))
5809                 return FALSE;
5811         result = status_update_write(&io, status, type);
5812         return done_io(&io) && result;
5815 static bool
5816 status_update_files(struct view *view, struct line *line)
5818         char buf[sizeof(view->ref)];
5819         struct io io = {};
5820         bool result = TRUE;
5821         struct line *pos = view->line + view->lines;
5822         int files = 0;
5823         int file, done;
5824         int cursor_y = -1, cursor_x = -1;
5826         if (!status_update_prepare(&io, line->type))
5827                 return FALSE;
5829         for (pos = line; pos < view->line + view->lines && pos->data; pos++)
5830                 files++;
5832         string_copy(buf, view->ref);
5833         getsyx(cursor_y, cursor_x);
5834         for (file = 0, done = 5; result && file < files; line++, file++) {
5835                 int almost_done = file * 100 / files;
5837                 if (almost_done > done) {
5838                         done = almost_done;
5839                         string_format(view->ref, "updating file %u of %u (%d%% done)",
5840                                       file, files, done);
5841                         update_view_title(view);
5842                         setsyx(cursor_y, cursor_x);
5843                         doupdate();
5844                 }
5845                 result = status_update_write(&io, line->data, line->type);
5846         }
5847         string_copy(view->ref, buf);
5849         return done_io(&io) && result;
5852 static bool
5853 status_update(struct view *view)
5855         struct line *line = &view->line[view->lineno];
5857         assert(view->lines);
5859         if (!line->data) {
5860                 /* This should work even for the "On branch" line. */
5861                 if (line < view->line + view->lines && !line[1].data) {
5862                         report("Nothing to update");
5863                         return FALSE;
5864                 }
5866                 if (!status_update_files(view, line + 1)) {
5867                         report("Failed to update file status");
5868                         return FALSE;
5869                 }
5871         } else if (!status_update_file(line->data, line->type)) {
5872                 report("Failed to update file status");
5873                 return FALSE;
5874         }
5876         return TRUE;
5879 static bool
5880 status_revert(struct status *status, enum line_type type, bool has_none)
5882         if (!status || type != LINE_STAT_UNSTAGED) {
5883                 if (type == LINE_STAT_STAGED) {
5884                         report("Cannot revert changes to staged files");
5885                 } else if (type == LINE_STAT_UNTRACKED) {
5886                         report("Cannot revert changes to untracked files");
5887                 } else if (has_none) {
5888                         report("Nothing to revert");
5889                 } else {
5890                         report("Cannot revert changes to multiple files");
5891                 }
5893         } else if (prompt_yesno("Are you sure you want to revert changes?")) {
5894                 char mode[10] = "100644";
5895                 const char *reset_argv[] = {
5896                         "git", "update-index", "--cacheinfo", mode,
5897                                 status->old.rev, status->old.name, NULL
5898                 };
5899                 const char *checkout_argv[] = {
5900                         "git", "checkout", "--", status->old.name, NULL
5901                 };
5903                 if (status->status == 'U') {
5904                         string_format(mode, "%5o", status->old.mode);
5906                         if (status->old.mode == 0 && status->new.mode == 0) {
5907                                 reset_argv[2] = "--force-remove";
5908                                 reset_argv[3] = status->old.name;
5909                                 reset_argv[4] = NULL;
5910                         }
5912                         if (!run_io_fg(reset_argv, opt_cdup))
5913                                 return FALSE;
5914                         if (status->old.mode == 0 && status->new.mode == 0)
5915                                 return TRUE;
5916                 }
5918                 return run_io_fg(checkout_argv, opt_cdup);
5919         }
5921         return FALSE;
5924 static enum request
5925 status_request(struct view *view, enum request request, struct line *line)
5927         struct status *status = line->data;
5929         switch (request) {
5930         case REQ_STATUS_UPDATE:
5931                 if (!status_update(view))
5932                         return REQ_NONE;
5933                 break;
5935         case REQ_STATUS_REVERT:
5936                 if (!status_revert(status, line->type, status_has_none(view, line)))
5937                         return REQ_NONE;
5938                 break;
5940         case REQ_STATUS_MERGE:
5941                 if (!status || status->status != 'U') {
5942                         report("Merging only possible for files with unmerged status ('U').");
5943                         return REQ_NONE;
5944                 }
5945                 open_mergetool(status->new.name);
5946                 break;
5948         case REQ_EDIT:
5949                 if (!status)
5950                         return request;
5951                 if (status->status == 'D') {
5952                         report("File has been deleted.");
5953                         return REQ_NONE;
5954                 }
5956                 open_editor(status->new.name);
5957                 break;
5959         case REQ_VIEW_BLAME:
5960                 if (status)
5961                         opt_ref[0] = 0;
5962                 return request;
5964         case REQ_ENTER:
5965                 /* After returning the status view has been split to
5966                  * show the stage view. No further reloading is
5967                  * necessary. */
5968                 return status_enter(view, line);
5970         case REQ_REFRESH:
5971                 /* Simply reload the view. */
5972                 break;
5974         default:
5975                 return request;
5976         }
5978         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
5980         return REQ_NONE;
5983 static void
5984 status_select(struct view *view, struct line *line)
5986         struct status *status = line->data;
5987         char file[SIZEOF_STR] = "all files";
5988         const char *text;
5989         const char *key;
5991         if (status && !string_format(file, "'%s'", status->new.name))
5992                 return;
5994         if (!status && line[1].type == LINE_STAT_NONE)
5995                 line++;
5997         switch (line->type) {
5998         case LINE_STAT_STAGED:
5999                 text = "Press %s to unstage %s for commit";
6000                 break;
6002         case LINE_STAT_UNSTAGED:
6003                 text = "Press %s to stage %s for commit";
6004                 break;
6006         case LINE_STAT_UNTRACKED:
6007                 text = "Press %s to stage %s for addition";
6008                 break;
6010         case LINE_STAT_HEAD:
6011         case LINE_STAT_NONE:
6012                 text = "Nothing to update";
6013                 break;
6015         default:
6016                 die("line type %d not handled in switch", line->type);
6017         }
6019         if (status && status->status == 'U') {
6020                 text = "Press %s to resolve conflict in %s";
6021                 key = get_key(KEYMAP_STATUS, REQ_STATUS_MERGE);
6023         } else {
6024                 key = get_key(KEYMAP_STATUS, REQ_STATUS_UPDATE);
6025         }
6027         string_format(view->ref, text, key, file);
6028         if (status)
6029                 string_copy(opt_file, status->new.name);
6032 static bool
6033 status_grep(struct view *view, struct line *line)
6035         struct status *status = line->data;
6037         if (status) {
6038                 const char buf[2] = { status->status, 0 };
6039                 const char *text[] = { status->new.name, buf, NULL };
6041                 return grep_text(view, text);
6042         }
6044         return FALSE;
6047 static struct view_ops status_ops = {
6048         "file",
6049         NULL,
6050         status_open,
6051         NULL,
6052         status_draw,
6053         status_request,
6054         status_grep,
6055         status_select,
6056 };
6059 static bool
6060 stage_diff_write(struct io *io, struct line *line, struct line *end)
6062         while (line < end) {
6063                 if (!io_write(io, line->data, strlen(line->data)) ||
6064                     !io_write(io, "\n", 1))
6065                         return FALSE;
6066                 line++;
6067                 if (line->type == LINE_DIFF_CHUNK ||
6068                     line->type == LINE_DIFF_HEADER)
6069                         break;
6070         }
6072         return TRUE;
6075 static struct line *
6076 stage_diff_find(struct view *view, struct line *line, enum line_type type)
6078         for (; view->line < line; line--)
6079                 if (line->type == type)
6080                         return line;
6082         return NULL;
6085 static bool
6086 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
6088         const char *apply_argv[SIZEOF_ARG] = {
6089                 "git", "apply", "--whitespace=nowarn", NULL
6090         };
6091         struct line *diff_hdr;
6092         struct io io = {};
6093         int argc = 3;
6095         diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
6096         if (!diff_hdr)
6097                 return FALSE;
6099         if (!revert)
6100                 apply_argv[argc++] = "--cached";
6101         if (revert || stage_line_type == LINE_STAT_STAGED)
6102                 apply_argv[argc++] = "-R";
6103         apply_argv[argc++] = "-";
6104         apply_argv[argc++] = NULL;
6105         if (!run_io(&io, apply_argv, opt_cdup, IO_WR))
6106                 return FALSE;
6108         if (!stage_diff_write(&io, diff_hdr, chunk) ||
6109             !stage_diff_write(&io, chunk, view->line + view->lines))
6110                 chunk = NULL;
6112         done_io(&io);
6113         run_io_bg(update_index_argv);
6115         return chunk ? TRUE : FALSE;
6118 static bool
6119 stage_update(struct view *view, struct line *line)
6121         struct line *chunk = NULL;
6123         if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
6124                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6126         if (chunk) {
6127                 if (!stage_apply_chunk(view, chunk, FALSE)) {
6128                         report("Failed to apply chunk");
6129                         return FALSE;
6130                 }
6132         } else if (!stage_status.status) {
6133                 view = VIEW(REQ_VIEW_STATUS);
6135                 for (line = view->line; line < view->line + view->lines; line++)
6136                         if (line->type == stage_line_type)
6137                                 break;
6139                 if (!status_update_files(view, line + 1)) {
6140                         report("Failed to update files");
6141                         return FALSE;
6142                 }
6144         } else if (!status_update_file(&stage_status, stage_line_type)) {
6145                 report("Failed to update file");
6146                 return FALSE;
6147         }
6149         return TRUE;
6152 static bool
6153 stage_revert(struct view *view, struct line *line)
6155         struct line *chunk = NULL;
6157         if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
6158                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6160         if (chunk) {
6161                 if (!prompt_yesno("Are you sure you want to revert changes?"))
6162                         return FALSE;
6164                 if (!stage_apply_chunk(view, chunk, TRUE)) {
6165                         report("Failed to revert chunk");
6166                         return FALSE;
6167                 }
6168                 return TRUE;
6170         } else {
6171                 return status_revert(stage_status.status ? &stage_status : NULL,
6172                                      stage_line_type, FALSE);
6173         }
6177 static void
6178 stage_next(struct view *view, struct line *line)
6180         int i;
6182         if (!stage_chunks) {
6183                 for (line = view->line; line < view->line + view->lines; line++) {
6184                         if (line->type != LINE_DIFF_CHUNK)
6185                                 continue;
6187                         if (!realloc_ints(&stage_chunk, stage_chunks, 1)) {
6188                                 report("Allocation failure");
6189                                 return;
6190                         }
6192                         stage_chunk[stage_chunks++] = line - view->line;
6193                 }
6194         }
6196         for (i = 0; i < stage_chunks; i++) {
6197                 if (stage_chunk[i] > view->lineno) {
6198                         do_scroll_view(view, stage_chunk[i] - view->lineno);
6199                         report("Chunk %d of %d", i + 1, stage_chunks);
6200                         return;
6201                 }
6202         }
6204         report("No next chunk found");
6207 static enum request
6208 stage_request(struct view *view, enum request request, struct line *line)
6210         switch (request) {
6211         case REQ_STATUS_UPDATE:
6212                 if (!stage_update(view, line))
6213                         return REQ_NONE;
6214                 break;
6216         case REQ_STATUS_REVERT:
6217                 if (!stage_revert(view, line))
6218                         return REQ_NONE;
6219                 break;
6221         case REQ_STAGE_NEXT:
6222                 if (stage_line_type == LINE_STAT_UNTRACKED) {
6223                         report("File is untracked; press %s to add",
6224                                get_key(KEYMAP_STAGE, REQ_STATUS_UPDATE));
6225                         return REQ_NONE;
6226                 }
6227                 stage_next(view, line);
6228                 return REQ_NONE;
6230         case REQ_EDIT:
6231                 if (!stage_status.new.name[0])
6232                         return request;
6233                 if (stage_status.status == 'D') {
6234                         report("File has been deleted.");
6235                         return REQ_NONE;
6236                 }
6238                 open_editor(stage_status.new.name);
6239                 break;
6241         case REQ_REFRESH:
6242                 /* Reload everything ... */
6243                 break;
6245         case REQ_VIEW_BLAME:
6246                 if (stage_status.new.name[0]) {
6247                         string_copy(opt_file, stage_status.new.name);
6248                         opt_ref[0] = 0;
6249                 }
6250                 return request;
6252         case REQ_ENTER:
6253                 return pager_request(view, request, line);
6255         default:
6256                 return request;
6257         }
6259         VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
6260         open_view(view, REQ_VIEW_STATUS, OPEN_REFRESH);
6262         /* Check whether the staged entry still exists, and close the
6263          * stage view if it doesn't. */
6264         if (!status_exists(&stage_status, stage_line_type)) {
6265                 status_restore(VIEW(REQ_VIEW_STATUS));
6266                 return REQ_VIEW_CLOSE;
6267         }
6269         if (stage_line_type == LINE_STAT_UNTRACKED) {
6270                 if (!suffixcmp(stage_status.new.name, -1, "/")) {
6271                         report("Cannot display a directory");
6272                         return REQ_NONE;
6273                 }
6275                 if (!prepare_update_file(view, stage_status.new.name)) {
6276                         report("Failed to open file: %s", strerror(errno));
6277                         return REQ_NONE;
6278                 }
6279         }
6280         open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
6282         return REQ_NONE;
6285 static struct view_ops stage_ops = {
6286         "line",
6287         NULL,
6288         NULL,
6289         pager_read,
6290         pager_draw,
6291         stage_request,
6292         pager_grep,
6293         pager_select,
6294 };
6297 /*
6298  * Revision graph
6299  */
6301 struct commit {
6302         char id[SIZEOF_REV];            /* SHA1 ID. */
6303         char title[128];                /* First line of the commit message. */
6304         const char *author;             /* Author of the commit. */
6305         struct time time;               /* Date from the author ident. */
6306         struct ref_list *refs;          /* Repository references. */
6307         chtype graph[SIZEOF_REVGRAPH];  /* Ancestry chain graphics. */
6308         size_t graph_size;              /* The width of the graph array. */
6309         bool has_parents;               /* Rewritten --parents seen. */
6310 };
6312 /* Size of rev graph with no  "padding" columns */
6313 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
6315 struct rev_graph {
6316         struct rev_graph *prev, *next, *parents;
6317         char rev[SIZEOF_REVITEMS][SIZEOF_REV];
6318         size_t size;
6319         struct commit *commit;
6320         size_t pos;
6321         unsigned int boundary:1;
6322 };
6324 /* Parents of the commit being visualized. */
6325 static struct rev_graph graph_parents[4];
6327 /* The current stack of revisions on the graph. */
6328 static struct rev_graph graph_stacks[4] = {
6329         { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
6330         { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
6331         { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
6332         { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
6333 };
6335 static inline bool
6336 graph_parent_is_merge(struct rev_graph *graph)
6338         return graph->parents->size > 1;
6341 static inline void
6342 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
6344         struct commit *commit = graph->commit;
6346         if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
6347                 commit->graph[commit->graph_size++] = symbol;
6350 static void
6351 clear_rev_graph(struct rev_graph *graph)
6353         graph->boundary = 0;
6354         graph->size = graph->pos = 0;
6355         graph->commit = NULL;
6356         memset(graph->parents, 0, sizeof(*graph->parents));
6359 static void
6360 done_rev_graph(struct rev_graph *graph)
6362         if (graph_parent_is_merge(graph) &&
6363             graph->pos < graph->size - 1 &&
6364             graph->next->size == graph->size + graph->parents->size - 1) {
6365                 size_t i = graph->pos + graph->parents->size - 1;
6367                 graph->commit->graph_size = i * 2;
6368                 while (i < graph->next->size - 1) {
6369                         append_to_rev_graph(graph, ' ');
6370                         append_to_rev_graph(graph, '\\');
6371                         i++;
6372                 }
6373         }
6375         clear_rev_graph(graph);
6378 static void
6379 push_rev_graph(struct rev_graph *graph, const char *parent)
6381         int i;
6383         /* "Collapse" duplicate parents lines.
6384          *
6385          * FIXME: This needs to also update update the drawn graph but
6386          * for now it just serves as a method for pruning graph lines. */
6387         for (i = 0; i < graph->size; i++)
6388                 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
6389                         return;
6391         if (graph->size < SIZEOF_REVITEMS) {
6392                 string_copy_rev(graph->rev[graph->size++], parent);
6393         }
6396 static chtype
6397 get_rev_graph_symbol(struct rev_graph *graph)
6399         chtype symbol;
6401         if (graph->boundary)
6402                 symbol = REVGRAPH_BOUND;
6403         else if (graph->parents->size == 0)
6404                 symbol = REVGRAPH_INIT;
6405         else if (graph_parent_is_merge(graph))
6406                 symbol = REVGRAPH_MERGE;
6407         else if (graph->pos >= graph->size)
6408                 symbol = REVGRAPH_BRANCH;
6409         else
6410                 symbol = REVGRAPH_COMMIT;
6412         return symbol;
6415 static void
6416 draw_rev_graph(struct rev_graph *graph)
6418         struct rev_filler {
6419                 chtype separator, line;
6420         };
6421         enum { DEFAULT, RSHARP, RDIAG, LDIAG };
6422         static struct rev_filler fillers[] = {
6423                 { ' ',  '|' },
6424                 { '`',  '.' },
6425                 { '\'', ' ' },
6426                 { '/',  ' ' },
6427         };
6428         chtype symbol = get_rev_graph_symbol(graph);
6429         struct rev_filler *filler;
6430         size_t i;
6432         fillers[DEFAULT].line = opt_line_graphics ? ACS_VLINE : '|';
6433         filler = &fillers[DEFAULT];
6435         for (i = 0; i < graph->pos; i++) {
6436                 append_to_rev_graph(graph, filler->line);
6437                 if (graph_parent_is_merge(graph->prev) &&
6438                     graph->prev->pos == i)
6439                         filler = &fillers[RSHARP];
6441                 append_to_rev_graph(graph, filler->separator);
6442         }
6444         /* Place the symbol for this revision. */
6445         append_to_rev_graph(graph, symbol);
6447         if (graph->prev->size > graph->size)
6448                 filler = &fillers[RDIAG];
6449         else
6450                 filler = &fillers[DEFAULT];
6452         i++;
6454         for (; i < graph->size; i++) {
6455                 append_to_rev_graph(graph, filler->separator);
6456                 append_to_rev_graph(graph, filler->line);
6457                 if (graph_parent_is_merge(graph->prev) &&
6458                     i < graph->prev->pos + graph->parents->size)
6459                         filler = &fillers[RSHARP];
6460                 if (graph->prev->size > graph->size)
6461                         filler = &fillers[LDIAG];
6462         }
6464         if (graph->prev->size > graph->size) {
6465                 append_to_rev_graph(graph, filler->separator);
6466                 if (filler->line != ' ')
6467                         append_to_rev_graph(graph, filler->line);
6468         }
6471 /* Prepare the next rev graph */
6472 static void
6473 prepare_rev_graph(struct rev_graph *graph)
6475         size_t i;
6477         /* First, traverse all lines of revisions up to the active one. */
6478         for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
6479                 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
6480                         break;
6482                 push_rev_graph(graph->next, graph->rev[graph->pos]);
6483         }
6485         /* Interleave the new revision parent(s). */
6486         for (i = 0; !graph->boundary && i < graph->parents->size; i++)
6487                 push_rev_graph(graph->next, graph->parents->rev[i]);
6489         /* Lastly, put any remaining revisions. */
6490         for (i = graph->pos + 1; i < graph->size; i++)
6491                 push_rev_graph(graph->next, graph->rev[i]);
6494 static void
6495 update_rev_graph(struct view *view, struct rev_graph *graph)
6497         /* If this is the finalizing update ... */
6498         if (graph->commit)
6499                 prepare_rev_graph(graph);
6501         /* Graph visualization needs a one rev look-ahead,
6502          * so the first update doesn't visualize anything. */
6503         if (!graph->prev->commit)
6504                 return;
6506         if (view->lines > 2)
6507                 view->line[view->lines - 3].dirty = 1;
6508         if (view->lines > 1)
6509                 view->line[view->lines - 2].dirty = 1;
6510         draw_rev_graph(graph->prev);
6511         done_rev_graph(graph->prev->prev);
6515 /*
6516  * Main view backend
6517  */
6519 static const char *main_argv[SIZEOF_ARG] = {
6520         "git", "log", "--no-color", "--pretty=raw", "--parents",
6521                       "--topo-order", "%(head)", NULL
6522 };
6524 static bool
6525 main_draw(struct view *view, struct line *line, unsigned int lineno)
6527         struct commit *commit = line->data;
6529         if (!commit->author)
6530                 return FALSE;
6532         if (opt_date && draw_date(view, &commit->time))
6533                 return TRUE;
6535         if (opt_author && draw_author(view, commit->author))
6536                 return TRUE;
6538         if (opt_rev_graph && commit->graph_size &&
6539             draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
6540                 return TRUE;
6542         if (opt_show_refs && commit->refs) {
6543                 size_t i;
6545                 for (i = 0; i < commit->refs->size; i++) {
6546                         struct ref *ref = commit->refs->refs[i];
6547                         enum line_type type;
6549                         if (ref->head)
6550                                 type = LINE_MAIN_HEAD;
6551                         else if (ref->ltag)
6552                                 type = LINE_MAIN_LOCAL_TAG;
6553                         else if (ref->tag)
6554                                 type = LINE_MAIN_TAG;
6555                         else if (ref->tracked)
6556                                 type = LINE_MAIN_TRACKED;
6557                         else if (ref->remote)
6558                                 type = LINE_MAIN_REMOTE;
6559                         else
6560                                 type = LINE_MAIN_REF;
6562                         if (draw_text(view, type, "[", TRUE) ||
6563                             draw_text(view, type, ref->name, TRUE) ||
6564                             draw_text(view, type, "]", TRUE))
6565                                 return TRUE;
6567                         if (draw_text(view, LINE_DEFAULT, " ", TRUE))
6568                                 return TRUE;
6569                 }
6570         }
6572         draw_text(view, LINE_DEFAULT, commit->title, TRUE);
6573         return TRUE;
6576 /* Reads git log --pretty=raw output and parses it into the commit struct. */
6577 static bool
6578 main_read(struct view *view, char *line)
6580         static struct rev_graph *graph = graph_stacks;
6581         enum line_type type;
6582         struct commit *commit;
6584         if (!line) {
6585                 int i;
6587                 if (!view->lines && !view->parent)
6588                         die("No revisions match the given arguments.");
6589                 if (view->lines > 0) {
6590                         commit = view->line[view->lines - 1].data;
6591                         view->line[view->lines - 1].dirty = 1;
6592                         if (!commit->author) {
6593                                 view->lines--;
6594                                 free(commit);
6595                                 graph->commit = NULL;
6596                         }
6597                 }
6598                 update_rev_graph(view, graph);
6600                 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
6601                         clear_rev_graph(&graph_stacks[i]);
6602                 return TRUE;
6603         }
6605         type = get_line_type(line);
6606         if (type == LINE_COMMIT) {
6607                 commit = calloc(1, sizeof(struct commit));
6608                 if (!commit)
6609                         return FALSE;
6611                 line += STRING_SIZE("commit ");
6612                 if (*line == '-') {
6613                         graph->boundary = 1;
6614                         line++;
6615                 }
6617                 string_copy_rev(commit->id, line);
6618                 commit->refs = get_ref_list(commit->id);
6619                 graph->commit = commit;
6620                 add_line_data(view, commit, LINE_MAIN_COMMIT);
6622                 while ((line = strchr(line, ' '))) {
6623                         line++;
6624                         push_rev_graph(graph->parents, line);
6625                         commit->has_parents = TRUE;
6626                 }
6627                 return TRUE;
6628         }
6630         if (!view->lines)
6631                 return TRUE;
6632         commit = view->line[view->lines - 1].data;
6634         switch (type) {
6635         case LINE_PARENT:
6636                 if (commit->has_parents)
6637                         break;
6638                 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
6639                 break;
6641         case LINE_AUTHOR:
6642                 parse_author_line(line + STRING_SIZE("author "),
6643                                   &commit->author, &commit->time);
6644                 update_rev_graph(view, graph);
6645                 graph = graph->next;
6646                 break;
6648         default:
6649                 /* Fill in the commit title if it has not already been set. */
6650                 if (commit->title[0])
6651                         break;
6653                 /* Require titles to start with a non-space character at the
6654                  * offset used by git log. */
6655                 if (strncmp(line, "    ", 4))
6656                         break;
6657                 line += 4;
6658                 /* Well, if the title starts with a whitespace character,
6659                  * try to be forgiving.  Otherwise we end up with no title. */
6660                 while (isspace(*line))
6661                         line++;
6662                 if (*line == '\0')
6663                         break;
6664                 /* FIXME: More graceful handling of titles; append "..." to
6665                  * shortened titles, etc. */
6667                 string_expand(commit->title, sizeof(commit->title), line, 1);
6668                 view->line[view->lines - 1].dirty = 1;
6669         }
6671         return TRUE;
6674 static enum request
6675 main_request(struct view *view, enum request request, struct line *line)
6677         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
6679         switch (request) {
6680         case REQ_ENTER:
6681                 open_view(view, REQ_VIEW_DIFF, flags);
6682                 break;
6683         case REQ_REFRESH:
6684                 load_refs();
6685                 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
6686                 break;
6687         default:
6688                 return request;
6689         }
6691         return REQ_NONE;
6694 static bool
6695 grep_refs(struct ref_list *list, regex_t *regex)
6697         regmatch_t pmatch;
6698         size_t i;
6700         if (!opt_show_refs || !list)
6701                 return FALSE;
6703         for (i = 0; i < list->size; i++) {
6704                 if (regexec(regex, list->refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
6705                         return TRUE;
6706         }
6708         return FALSE;
6711 static bool
6712 main_grep(struct view *view, struct line *line)
6714         struct commit *commit = line->data;
6715         const char *text[] = {
6716                 commit->title,
6717                 opt_author ? commit->author : "",
6718                 opt_date ? mkdate(&commit->time) : "",
6719                 NULL
6720         };
6722         return grep_text(view, text) || grep_refs(commit->refs, view->regex);
6725 static void
6726 main_select(struct view *view, struct line *line)
6728         struct commit *commit = line->data;
6730         string_copy_rev(view->ref, commit->id);
6731         string_copy_rev(ref_commit, view->ref);
6734 static struct view_ops main_ops = {
6735         "commit",
6736         main_argv,
6737         NULL,
6738         main_read,
6739         main_draw,
6740         main_request,
6741         main_grep,
6742         main_select,
6743 };
6746 /*
6747  * Unicode / UTF-8 handling
6748  *
6749  * NOTE: Much of the following code for dealing with Unicode is derived from
6750  * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
6751  * src/intl/charset.c from the UTF-8 branch commit elinks-0.11.0-g31f2c28.
6752  */
6754 static inline int
6755 unicode_width(unsigned long c, int tab_size)
6757         if (c >= 0x1100 &&
6758            (c <= 0x115f                         /* Hangul Jamo */
6759             || c == 0x2329
6760             || c == 0x232a
6761             || (c >= 0x2e80  && c <= 0xa4cf && c != 0x303f)
6762                                                 /* CJK ... Yi */
6763             || (c >= 0xac00  && c <= 0xd7a3)    /* Hangul Syllables */
6764             || (c >= 0xf900  && c <= 0xfaff)    /* CJK Compatibility Ideographs */
6765             || (c >= 0xfe30  && c <= 0xfe6f)    /* CJK Compatibility Forms */
6766             || (c >= 0xff00  && c <= 0xff60)    /* Fullwidth Forms */
6767             || (c >= 0xffe0  && c <= 0xffe6)
6768             || (c >= 0x20000 && c <= 0x2fffd)
6769             || (c >= 0x30000 && c <= 0x3fffd)))
6770                 return 2;
6772         if (c == '\t')
6773                 return tab_size;
6775         return 1;
6778 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
6779  * Illegal bytes are set one. */
6780 static const unsigned char utf8_bytes[256] = {
6781         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,
6782         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,
6783         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,
6784         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,
6785         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,
6786         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,
6787         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,
6788         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,
6789 };
6791 static inline unsigned char
6792 utf8_char_length(const char *string, const char *end)
6794         int c = *(unsigned char *) string;
6796         return utf8_bytes[c];
6799 /* Decode UTF-8 multi-byte representation into a Unicode character. */
6800 static inline unsigned long
6801 utf8_to_unicode(const char *string, size_t length)
6803         unsigned long unicode;
6805         switch (length) {
6806         case 1:
6807                 unicode  =   string[0];
6808                 break;
6809         case 2:
6810                 unicode  =  (string[0] & 0x1f) << 6;
6811                 unicode +=  (string[1] & 0x3f);
6812                 break;
6813         case 3:
6814                 unicode  =  (string[0] & 0x0f) << 12;
6815                 unicode += ((string[1] & 0x3f) << 6);
6816                 unicode +=  (string[2] & 0x3f);
6817                 break;
6818         case 4:
6819                 unicode  =  (string[0] & 0x0f) << 18;
6820                 unicode += ((string[1] & 0x3f) << 12);
6821                 unicode += ((string[2] & 0x3f) << 6);
6822                 unicode +=  (string[3] & 0x3f);
6823                 break;
6824         case 5:
6825                 unicode  =  (string[0] & 0x0f) << 24;
6826                 unicode += ((string[1] & 0x3f) << 18);
6827                 unicode += ((string[2] & 0x3f) << 12);
6828                 unicode += ((string[3] & 0x3f) << 6);
6829                 unicode +=  (string[4] & 0x3f);
6830                 break;
6831         case 6:
6832                 unicode  =  (string[0] & 0x01) << 30;
6833                 unicode += ((string[1] & 0x3f) << 24);
6834                 unicode += ((string[2] & 0x3f) << 18);
6835                 unicode += ((string[3] & 0x3f) << 12);
6836                 unicode += ((string[4] & 0x3f) << 6);
6837                 unicode +=  (string[5] & 0x3f);
6838                 break;
6839         default:
6840                 die("Invalid Unicode length");
6841         }
6843         /* Invalid characters could return the special 0xfffd value but NUL
6844          * should be just as good. */
6845         return unicode > 0xffff ? 0 : unicode;
6848 /* Calculates how much of string can be shown within the given maximum width
6849  * and sets trimmed parameter to non-zero value if all of string could not be
6850  * shown. If the reserve flag is TRUE, it will reserve at least one
6851  * trailing character, which can be useful when drawing a delimiter.
6852  *
6853  * Returns the number of bytes to output from string to satisfy max_width. */
6854 static size_t
6855 utf8_length(const char **start, size_t skip, int *width, size_t max_width, int *trimmed, bool reserve, int tab_size)
6857         const char *string = *start;
6858         const char *end = strchr(string, '\0');
6859         unsigned char last_bytes = 0;
6860         size_t last_ucwidth = 0;
6862         *width = 0;
6863         *trimmed = 0;
6865         while (string < end) {
6866                 unsigned char bytes = utf8_char_length(string, end);
6867                 size_t ucwidth;
6868                 unsigned long unicode;
6870                 if (string + bytes > end)
6871                         break;
6873                 /* Change representation to figure out whether
6874                  * it is a single- or double-width character. */
6876                 unicode = utf8_to_unicode(string, bytes);
6877                 /* FIXME: Graceful handling of invalid Unicode character. */
6878                 if (!unicode)
6879                         break;
6881                 ucwidth = unicode_width(unicode, tab_size);
6882                 if (skip > 0) {
6883                         skip -= ucwidth <= skip ? ucwidth : skip;
6884                         *start += bytes;
6885                 }
6886                 *width  += ucwidth;
6887                 if (*width > max_width) {
6888                         *trimmed = 1;
6889                         *width -= ucwidth;
6890                         if (reserve && *width == max_width) {
6891                                 string -= last_bytes;
6892                                 *width -= last_ucwidth;
6893                         }
6894                         break;
6895                 }
6897                 string  += bytes;
6898                 last_bytes = ucwidth ? bytes : 0;
6899                 last_ucwidth = ucwidth;
6900         }
6902         return string - *start;
6906 /*
6907  * Status management
6908  */
6910 /* Whether or not the curses interface has been initialized. */
6911 static bool cursed = FALSE;
6913 /* Terminal hacks and workarounds. */
6914 static bool use_scroll_redrawwin;
6915 static bool use_scroll_status_wclear;
6917 /* The status window is used for polling keystrokes. */
6918 static WINDOW *status_win;
6920 /* Reading from the prompt? */
6921 static bool input_mode = FALSE;
6923 static bool status_empty = FALSE;
6925 /* Update status and title window. */
6926 static void
6927 report(const char *msg, ...)
6929         struct view *view = display[current_view];
6931         if (input_mode)
6932                 return;
6934         if (!view) {
6935                 char buf[SIZEOF_STR];
6936                 va_list args;
6938                 va_start(args, msg);
6939                 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
6940                         buf[sizeof(buf) - 1] = 0;
6941                         buf[sizeof(buf) - 2] = '.';
6942                         buf[sizeof(buf) - 3] = '.';
6943                         buf[sizeof(buf) - 4] = '.';
6944                 }
6945                 va_end(args);
6946                 die("%s", buf);
6947         }
6949         if (!status_empty || *msg) {
6950                 va_list args;
6952                 va_start(args, msg);
6954                 wmove(status_win, 0, 0);
6955                 if (view->has_scrolled && use_scroll_status_wclear)
6956                         wclear(status_win);
6957                 if (*msg) {
6958                         vwprintw(status_win, msg, args);
6959                         status_empty = FALSE;
6960                 } else {
6961                         status_empty = TRUE;
6962                 }
6963                 wclrtoeol(status_win);
6964                 wnoutrefresh(status_win);
6966                 va_end(args);
6967         }
6969         update_view_title(view);
6972 static void
6973 init_display(void)
6975         const char *term;
6976         int x, y;
6978         /* Initialize the curses library */
6979         if (isatty(STDIN_FILENO)) {
6980                 cursed = !!initscr();
6981                 opt_tty = stdin;
6982         } else {
6983                 /* Leave stdin and stdout alone when acting as a pager. */
6984                 opt_tty = fopen("/dev/tty", "r+");
6985                 if (!opt_tty)
6986                         die("Failed to open /dev/tty");
6987                 cursed = !!newterm(NULL, opt_tty, opt_tty);
6988         }
6990         if (!cursed)
6991                 die("Failed to initialize curses");
6993         nonl();         /* Disable conversion and detect newlines from input. */
6994         cbreak();       /* Take input chars one at a time, no wait for \n */
6995         noecho();       /* Don't echo input */
6996         leaveok(stdscr, FALSE);
6998         if (has_colors())
6999                 init_colors();
7001         getmaxyx(stdscr, y, x);
7002         status_win = newwin(1, 0, y - 1, 0);
7003         if (!status_win)
7004                 die("Failed to create status window");
7006         /* Enable keyboard mapping */
7007         keypad(status_win, TRUE);
7008         wbkgdset(status_win, get_line_attr(LINE_STATUS));
7010         TABSIZE = opt_tab_size;
7012         term = getenv("XTERM_VERSION") ? NULL : getenv("COLORTERM");
7013         if (term && !strcmp(term, "gnome-terminal")) {
7014                 /* In the gnome-terminal-emulator, the message from
7015                  * scrolling up one line when impossible followed by
7016                  * scrolling down one line causes corruption of the
7017                  * status line. This is fixed by calling wclear. */
7018                 use_scroll_status_wclear = TRUE;
7019                 use_scroll_redrawwin = FALSE;
7021         } else if (term && !strcmp(term, "xrvt-xpm")) {
7022                 /* No problems with full optimizations in xrvt-(unicode)
7023                  * and aterm. */
7024                 use_scroll_status_wclear = use_scroll_redrawwin = FALSE;
7026         } else {
7027                 /* When scrolling in (u)xterm the last line in the
7028                  * scrolling direction will update slowly. */
7029                 use_scroll_redrawwin = TRUE;
7030                 use_scroll_status_wclear = FALSE;
7031         }
7034 static int
7035 get_input(int prompt_position)
7037         struct view *view;
7038         int i, key, cursor_y, cursor_x;
7039         bool loading = FALSE;
7041         if (prompt_position)
7042                 input_mode = TRUE;
7044         while (TRUE) {
7045                 foreach_view (view, i) {
7046                         update_view(view);
7047                         if (view_is_displayed(view) && view->has_scrolled &&
7048                             use_scroll_redrawwin)
7049                                 redrawwin(view->win);
7050                         view->has_scrolled = FALSE;
7051                         if (view->pipe)
7052                                 loading = TRUE;
7053                 }
7055                 /* Update the cursor position. */
7056                 if (prompt_position) {
7057                         getbegyx(status_win, cursor_y, cursor_x);
7058                         cursor_x = prompt_position;
7059                 } else {
7060                         view = display[current_view];
7061                         getbegyx(view->win, cursor_y, cursor_x);
7062                         cursor_x = view->width - 1;
7063                         cursor_y += view->lineno - view->offset;
7064                 }
7065                 setsyx(cursor_y, cursor_x);
7067                 /* Refresh, accept single keystroke of input */
7068                 doupdate();
7069                 nodelay(status_win, loading);
7070                 key = wgetch(status_win);
7072                 /* wgetch() with nodelay() enabled returns ERR when
7073                  * there's no input. */
7074                 if (key == ERR) {
7076                 } else if (key == KEY_RESIZE) {
7077                         int height, width;
7079                         getmaxyx(stdscr, height, width);
7081                         wresize(status_win, 1, width);
7082                         mvwin(status_win, height - 1, 0);
7083                         wnoutrefresh(status_win);
7084                         resize_display();
7085                         redraw_display(TRUE);
7087                 } else {
7088                         input_mode = FALSE;
7089                         return key;
7090                 }
7091         }
7094 static char *
7095 prompt_input(const char *prompt, input_handler handler, void *data)
7097         enum input_status status = INPUT_OK;
7098         static char buf[SIZEOF_STR];
7099         size_t pos = 0;
7101         buf[pos] = 0;
7103         while (status == INPUT_OK || status == INPUT_SKIP) {
7104                 int key;
7106                 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
7107                 wclrtoeol(status_win);
7109                 key = get_input(pos + 1);
7110                 switch (key) {
7111                 case KEY_RETURN:
7112                 case KEY_ENTER:
7113                 case '\n':
7114                         status = pos ? INPUT_STOP : INPUT_CANCEL;
7115                         break;
7117                 case KEY_BACKSPACE:
7118                         if (pos > 0)
7119                                 buf[--pos] = 0;
7120                         else
7121                                 status = INPUT_CANCEL;
7122                         break;
7124                 case KEY_ESC:
7125                         status = INPUT_CANCEL;
7126                         break;
7128                 default:
7129                         if (pos >= sizeof(buf)) {
7130                                 report("Input string too long");
7131                                 return NULL;
7132                         }
7134                         status = handler(data, buf, key);
7135                         if (status == INPUT_OK)
7136                                 buf[pos++] = (char) key;
7137                 }
7138         }
7140         /* Clear the status window */
7141         status_empty = FALSE;
7142         report("");
7144         if (status == INPUT_CANCEL)
7145                 return NULL;
7147         buf[pos++] = 0;
7149         return buf;
7152 static enum input_status
7153 prompt_yesno_handler(void *data, char *buf, int c)
7155         if (c == 'y' || c == 'Y')
7156                 return INPUT_STOP;
7157         if (c == 'n' || c == 'N')
7158                 return INPUT_CANCEL;
7159         return INPUT_SKIP;
7162 static bool
7163 prompt_yesno(const char *prompt)
7165         char prompt2[SIZEOF_STR];
7167         if (!string_format(prompt2, "%s [Yy/Nn]", prompt))
7168                 return FALSE;
7170         return !!prompt_input(prompt2, prompt_yesno_handler, NULL);
7173 static enum input_status
7174 read_prompt_handler(void *data, char *buf, int c)
7176         return isprint(c) ? INPUT_OK : INPUT_SKIP;
7179 static char *
7180 read_prompt(const char *prompt)
7182         return prompt_input(prompt, read_prompt_handler, NULL);
7185 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected)
7187         enum input_status status = INPUT_OK;
7188         int size = 0;
7190         while (items[size].text)
7191                 size++;
7193         while (status == INPUT_OK) {
7194                 const struct menu_item *item = &items[*selected];
7195                 int key;
7196                 int i;
7198                 mvwprintw(status_win, 0, 0, "%s (%d of %d) ",
7199                           prompt, *selected + 1, size);
7200                 if (item->hotkey)
7201                         wprintw(status_win, "[%c] ", (char) item->hotkey);
7202                 wprintw(status_win, "%s", item->text);
7203                 wclrtoeol(status_win);
7205                 key = get_input(COLS - 1);
7206                 switch (key) {
7207                 case KEY_RETURN:
7208                 case KEY_ENTER:
7209                 case '\n':
7210                         status = INPUT_STOP;
7211                         break;
7213                 case KEY_LEFT:
7214                 case KEY_UP:
7215                         *selected = *selected - 1;
7216                         if (*selected < 0)
7217                                 *selected = size - 1;
7218                         break;
7220                 case KEY_RIGHT:
7221                 case KEY_DOWN:
7222                         *selected = (*selected + 1) % size;
7223                         break;
7225                 case KEY_ESC:
7226                         status = INPUT_CANCEL;
7227                         break;
7229                 default:
7230                         for (i = 0; items[i].text; i++)
7231                                 if (items[i].hotkey == key) {
7232                                         *selected = i;
7233                                         status = INPUT_STOP;
7234                                         break;
7235                                 }
7236                 }
7237         }
7239         /* Clear the status window */
7240         status_empty = FALSE;
7241         report("");
7243         return status != INPUT_CANCEL;
7246 /*
7247  * Repository properties
7248  */
7250 static struct ref **refs = NULL;
7251 static size_t refs_size = 0;
7252 static struct ref *refs_head = NULL;
7254 static struct ref_list **ref_lists = NULL;
7255 static size_t ref_lists_size = 0;
7257 DEFINE_ALLOCATOR(realloc_refs, struct ref *, 256)
7258 DEFINE_ALLOCATOR(realloc_refs_list, struct ref *, 8)
7259 DEFINE_ALLOCATOR(realloc_ref_lists, struct ref_list *, 8)
7261 static int
7262 compare_refs(const void *ref1_, const void *ref2_)
7264         const struct ref *ref1 = *(const struct ref **)ref1_;
7265         const struct ref *ref2 = *(const struct ref **)ref2_;
7267         if (ref1->tag != ref2->tag)
7268                 return ref2->tag - ref1->tag;
7269         if (ref1->ltag != ref2->ltag)
7270                 return ref2->ltag - ref2->ltag;
7271         if (ref1->head != ref2->head)
7272                 return ref2->head - ref1->head;
7273         if (ref1->tracked != ref2->tracked)
7274                 return ref2->tracked - ref1->tracked;
7275         if (ref1->remote != ref2->remote)
7276                 return ref2->remote - ref1->remote;
7277         return strcmp(ref1->name, ref2->name);
7280 static void
7281 foreach_ref(bool (*visitor)(void *data, const struct ref *ref), void *data)
7283         size_t i;
7285         for (i = 0; i < refs_size; i++)
7286                 if (!visitor(data, refs[i]))
7287                         break;
7290 static struct ref *
7291 get_ref_head()
7293         return refs_head;
7296 static struct ref_list *
7297 get_ref_list(const char *id)
7299         struct ref_list *list;
7300         size_t i;
7302         for (i = 0; i < ref_lists_size; i++)
7303                 if (!strcmp(id, ref_lists[i]->id))
7304                         return ref_lists[i];
7306         if (!realloc_ref_lists(&ref_lists, ref_lists_size, 1))
7307                 return NULL;
7308         list = calloc(1, sizeof(*list));
7309         if (!list)
7310                 return NULL;
7312         for (i = 0; i < refs_size; i++) {
7313                 if (!strcmp(id, refs[i]->id) &&
7314                     realloc_refs_list(&list->refs, list->size, 1))
7315                         list->refs[list->size++] = refs[i];
7316         }
7318         if (!list->refs) {
7319                 free(list);
7320                 return NULL;
7321         }
7323         qsort(list->refs, list->size, sizeof(*list->refs), compare_refs);
7324         ref_lists[ref_lists_size++] = list;
7325         return list;
7328 static int
7329 read_ref(char *id, size_t idlen, char *name, size_t namelen)
7331         struct ref *ref = NULL;
7332         bool tag = FALSE;
7333         bool ltag = FALSE;
7334         bool remote = FALSE;
7335         bool tracked = FALSE;
7336         bool head = FALSE;
7337         int from = 0, to = refs_size - 1;
7339         if (!prefixcmp(name, "refs/tags/")) {
7340                 if (!suffixcmp(name, namelen, "^{}")) {
7341                         namelen -= 3;
7342                         name[namelen] = 0;
7343                 } else {
7344                         ltag = TRUE;
7345                 }
7347                 tag = TRUE;
7348                 namelen -= STRING_SIZE("refs/tags/");
7349                 name    += STRING_SIZE("refs/tags/");
7351         } else if (!prefixcmp(name, "refs/remotes/")) {
7352                 remote = TRUE;
7353                 namelen -= STRING_SIZE("refs/remotes/");
7354                 name    += STRING_SIZE("refs/remotes/");
7355                 tracked  = !strcmp(opt_remote, name);
7357         } else if (!prefixcmp(name, "refs/heads/")) {
7358                 namelen -= STRING_SIZE("refs/heads/");
7359                 name    += STRING_SIZE("refs/heads/");
7360                 if (!strncmp(opt_head, name, namelen))
7361                         return OK;
7363         } else if (!strcmp(name, "HEAD")) {
7364                 head     = TRUE;
7365                 if (*opt_head) {
7366                         namelen  = strlen(opt_head);
7367                         name     = opt_head;
7368                 }
7369         }
7371         /* If we are reloading or it's an annotated tag, replace the
7372          * previous SHA1 with the resolved commit id; relies on the fact
7373          * git-ls-remote lists the commit id of an annotated tag right
7374          * before the commit id it points to. */
7375         while (from <= to) {
7376                 size_t pos = (to + from) / 2;
7377                 int cmp = strcmp(name, refs[pos]->name);
7379                 if (!cmp) {
7380                         ref = refs[pos];
7381                         break;
7382                 }
7384                 if (cmp < 0)
7385                         to = pos - 1;
7386                 else
7387                         from = pos + 1;
7388         }
7390         if (!ref) {
7391                 if (!realloc_refs(&refs, refs_size, 1))
7392                         return ERR;
7393                 ref = calloc(1, sizeof(*ref) + namelen);
7394                 if (!ref)
7395                         return ERR;
7396                 memmove(refs + from + 1, refs + from,
7397                         (refs_size - from) * sizeof(*refs));
7398                 refs[from] = ref;
7399                 strncpy(ref->name, name, namelen);
7400                 refs_size++;
7401         }
7403         ref->head = head;
7404         ref->tag = tag;
7405         ref->ltag = ltag;
7406         ref->remote = remote;
7407         ref->tracked = tracked;
7408         string_copy_rev(ref->id, id);
7410         if (head)
7411                 refs_head = ref;
7412         return OK;
7415 static int
7416 load_refs(void)
7418         const char *head_argv[] = {
7419                 "git", "symbolic-ref", "HEAD", NULL
7420         };
7421         static const char *ls_remote_argv[SIZEOF_ARG] = {
7422                 "git", "ls-remote", opt_git_dir, NULL
7423         };
7424         static bool init = FALSE;
7425         size_t i;
7427         if (!init) {
7428                 argv_from_env(ls_remote_argv, "TIG_LS_REMOTE");
7429                 init = TRUE;
7430         }
7432         if (!*opt_git_dir)
7433                 return OK;
7435         if (run_io_buf(head_argv, opt_head, sizeof(opt_head)) &&
7436             !prefixcmp(opt_head, "refs/heads/")) {
7437                 char *offset = opt_head + STRING_SIZE("refs/heads/");
7439                 memmove(opt_head, offset, strlen(offset) + 1);
7440         }
7442         refs_head = NULL;
7443         for (i = 0; i < refs_size; i++)
7444                 refs[i]->id[0] = 0;
7446         if (run_io_load(ls_remote_argv, "\t", read_ref) == ERR)
7447                 return ERR;
7449         /* Update the ref lists to reflect changes. */
7450         for (i = 0; i < ref_lists_size; i++) {
7451                 struct ref_list *list = ref_lists[i];
7452                 size_t old, new;
7454                 for (old = new = 0; old < list->size; old++)
7455                         if (!strcmp(list->id, list->refs[old]->id))
7456                                 list->refs[new++] = list->refs[old];
7457                 list->size = new;
7458         }
7460         return OK;
7463 static void
7464 set_remote_branch(const char *name, const char *value, size_t valuelen)
7466         if (!strcmp(name, ".remote")) {
7467                 string_ncopy(opt_remote, value, valuelen);
7469         } else if (*opt_remote && !strcmp(name, ".merge")) {
7470                 size_t from = strlen(opt_remote);
7472                 if (!prefixcmp(value, "refs/heads/"))
7473                         value += STRING_SIZE("refs/heads/");
7475                 if (!string_format_from(opt_remote, &from, "/%s", value))
7476                         opt_remote[0] = 0;
7477         }
7480 static void
7481 set_repo_config_option(char *name, char *value, int (*cmd)(int, const char **))
7483         const char *argv[SIZEOF_ARG] = { name, "=" };
7484         int argc = 1 + (cmd == option_set_command);
7485         int error = ERR;
7487         if (!argv_from_string(argv, &argc, value))
7488                 config_msg = "Too many option arguments";
7489         else
7490                 error = cmd(argc, argv);
7492         if (error == ERR)
7493                 warn("Option 'tig.%s': %s", name, config_msg);
7496 static bool
7497 set_environment_variable(const char *name, const char *value)
7499         size_t len = strlen(name) + 1 + strlen(value) + 1;
7500         char *env = malloc(len);
7502         if (env &&
7503             string_nformat(env, len, NULL, "%s=%s", name, value) &&
7504             putenv(env) == 0)
7505                 return TRUE;
7506         free(env);
7507         return FALSE;
7510 static void
7511 set_work_tree(const char *value)
7513         char cwd[SIZEOF_STR];
7515         if (!getcwd(cwd, sizeof(cwd)))
7516                 die("Failed to get cwd path: %s", strerror(errno));
7517         if (chdir(opt_git_dir) < 0)
7518                 die("Failed to chdir(%s): %s", strerror(errno));
7519         if (!getcwd(opt_git_dir, sizeof(opt_git_dir)))
7520                 die("Failed to get git path: %s", strerror(errno));
7521         if (chdir(cwd) < 0)
7522                 die("Failed to chdir(%s): %s", cwd, strerror(errno));
7523         if (chdir(value) < 0)
7524                 die("Failed to chdir(%s): %s", value, strerror(errno));
7525         if (!getcwd(cwd, sizeof(cwd)))
7526                 die("Failed to get cwd path: %s", strerror(errno));
7527         if (!set_environment_variable("GIT_WORK_TREE", cwd))
7528                 die("Failed to set GIT_WORK_TREE to '%s'", cwd);
7529         if (!set_environment_variable("GIT_DIR", opt_git_dir))
7530                 die("Failed to set GIT_DIR to '%s'", opt_git_dir);
7531         opt_is_inside_work_tree = TRUE;
7534 static int
7535 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
7537         if (!strcmp(name, "i18n.commitencoding"))
7538                 string_ncopy(opt_encoding, value, valuelen);
7540         else if (!strcmp(name, "core.editor"))
7541                 string_ncopy(opt_editor, value, valuelen);
7543         else if (!strcmp(name, "core.worktree"))
7544                 set_work_tree(value);
7546         else if (!prefixcmp(name, "tig.color."))
7547                 set_repo_config_option(name + 10, value, option_color_command);
7549         else if (!prefixcmp(name, "tig.bind."))
7550                 set_repo_config_option(name + 9, value, option_bind_command);
7552         else if (!prefixcmp(name, "tig."))
7553                 set_repo_config_option(name + 4, value, option_set_command);
7555         else if (*opt_head && !prefixcmp(name, "branch.") &&
7556                  !strncmp(name + 7, opt_head, strlen(opt_head)))
7557                 set_remote_branch(name + 7 + strlen(opt_head), value, valuelen);
7559         return OK;
7562 static int
7563 load_git_config(void)
7565         const char *config_list_argv[] = { "git", "config", "--list", NULL };
7567         return run_io_load(config_list_argv, "=", read_repo_config_option);
7570 static int
7571 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
7573         if (!opt_git_dir[0]) {
7574                 string_ncopy(opt_git_dir, name, namelen);
7576         } else if (opt_is_inside_work_tree == -1) {
7577                 /* This can be 3 different values depending on the
7578                  * version of git being used. If git-rev-parse does not
7579                  * understand --is-inside-work-tree it will simply echo
7580                  * the option else either "true" or "false" is printed.
7581                  * Default to true for the unknown case. */
7582                 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
7584         } else if (*name == '.') {
7585                 string_ncopy(opt_cdup, name, namelen);
7587         } else {
7588                 string_ncopy(opt_prefix, name, namelen);
7589         }
7591         return OK;
7594 static int
7595 load_repo_info(void)
7597         const char *rev_parse_argv[] = {
7598                 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
7599                         "--show-cdup", "--show-prefix", NULL
7600         };
7602         return run_io_load(rev_parse_argv, "=", read_repo_info);
7606 /*
7607  * Main
7608  */
7610 static const char usage[] =
7611 "tig " TIG_VERSION " (" __DATE__ ")\n"
7612 "\n"
7613 "Usage: tig        [options] [revs] [--] [paths]\n"
7614 "   or: tig show   [options] [revs] [--] [paths]\n"
7615 "   or: tig blame  [rev] path\n"
7616 "   or: tig status\n"
7617 "   or: tig <      [git command output]\n"
7618 "\n"
7619 "Options:\n"
7620 "  -v, --version   Show version and exit\n"
7621 "  -h, --help      Show help message and exit";
7623 static void __NORETURN
7624 quit(int sig)
7626         /* XXX: Restore tty modes and let the OS cleanup the rest! */
7627         if (cursed)
7628                 endwin();
7629         exit(0);
7632 static void __NORETURN
7633 die(const char *err, ...)
7635         va_list args;
7637         endwin();
7639         va_start(args, err);
7640         fputs("tig: ", stderr);
7641         vfprintf(stderr, err, args);
7642         fputs("\n", stderr);
7643         va_end(args);
7645         exit(1);
7648 static void
7649 warn(const char *msg, ...)
7651         va_list args;
7653         va_start(args, msg);
7654         fputs("tig warning: ", stderr);
7655         vfprintf(stderr, msg, args);
7656         fputs("\n", stderr);
7657         va_end(args);
7660 static enum request
7661 parse_options(int argc, const char *argv[])
7663         enum request request = REQ_VIEW_MAIN;
7664         const char *subcommand;
7665         bool seen_dashdash = FALSE;
7666         /* XXX: This is vulnerable to the user overriding options
7667          * required for the main view parser. */
7668         const char *custom_argv[SIZEOF_ARG] = {
7669                 "git", "log", "--no-color", "--pretty=raw", "--parents",
7670                         "--topo-order", NULL
7671         };
7672         int i, j = 6;
7674         if (!isatty(STDIN_FILENO)) {
7675                 io_open(&VIEW(REQ_VIEW_PAGER)->io, "");
7676                 return REQ_VIEW_PAGER;
7677         }
7679         if (argc <= 1)
7680                 return REQ_NONE;
7682         subcommand = argv[1];
7683         if (!strcmp(subcommand, "status")) {
7684                 if (argc > 2)
7685                         warn("ignoring arguments after `%s'", subcommand);
7686                 return REQ_VIEW_STATUS;
7688         } else if (!strcmp(subcommand, "blame")) {
7689                 if (argc <= 2 || argc > 4)
7690                         die("invalid number of options to blame\n\n%s", usage);
7692                 i = 2;
7693                 if (argc == 4) {
7694                         string_ncopy(opt_ref, argv[i], strlen(argv[i]));
7695                         i++;
7696                 }
7698                 string_ncopy(opt_file, argv[i], strlen(argv[i]));
7699                 return REQ_VIEW_BLAME;
7701         } else if (!strcmp(subcommand, "show")) {
7702                 request = REQ_VIEW_DIFF;
7704         } else {
7705                 subcommand = NULL;
7706         }
7708         if (subcommand) {
7709                 custom_argv[1] = subcommand;
7710                 j = 2;
7711         }
7713         for (i = 1 + !!subcommand; i < argc; i++) {
7714                 const char *opt = argv[i];
7716                 if (seen_dashdash || !strcmp(opt, "--")) {
7717                         seen_dashdash = TRUE;
7719                 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
7720                         printf("tig version %s\n", TIG_VERSION);
7721                         quit(0);
7723                 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
7724                         printf("%s\n", usage);
7725                         quit(0);
7726                 }
7728                 custom_argv[j++] = opt;
7729                 if (j >= ARRAY_SIZE(custom_argv))
7730                         die("command too long");
7731         }
7733         if (!prepare_update(VIEW(request), custom_argv, NULL, FORMAT_NONE))
7734                 die("Failed to format arguments");
7736         return request;
7739 int
7740 main(int argc, const char *argv[])
7742         const char *codeset = "UTF-8";
7743         enum request request = parse_options(argc, argv);
7744         struct view *view;
7745         size_t i;
7747         signal(SIGINT, quit);
7748         signal(SIGPIPE, SIG_IGN);
7750         if (setlocale(LC_ALL, "")) {
7751                 codeset = nl_langinfo(CODESET);
7752         }
7754         if (load_repo_info() == ERR)
7755                 die("Failed to load repo info.");
7757         if (load_options() == ERR)
7758                 die("Failed to load user config.");
7760         if (load_git_config() == ERR)
7761                 die("Failed to load repo config.");
7763         /* Require a git repository unless when running in pager mode. */
7764         if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
7765                 die("Not a git repository");
7767         if (*opt_encoding && strcmp(codeset, "UTF-8")) {
7768                 opt_iconv_in = iconv_open("UTF-8", opt_encoding);
7769                 if (opt_iconv_in == ICONV_NONE)
7770                         die("Failed to initialize character set conversion");
7771         }
7773         if (codeset && strcmp(codeset, "UTF-8")) {
7774                 opt_iconv_out = iconv_open(codeset, "UTF-8");
7775                 if (opt_iconv_out == ICONV_NONE)
7776                         die("Failed to initialize character set conversion");
7777         }
7779         if (load_refs() == ERR)
7780                 die("Failed to load refs.");
7782         foreach_view (view, i)
7783                 argv_from_env(view->ops->argv, view->cmd_env);
7785         init_display();
7787         if (request != REQ_NONE)
7788                 open_view(NULL, request, OPEN_PREPARED);
7789         request = request == REQ_NONE ? REQ_VIEW_MAIN : REQ_NONE;
7791         while (view_driver(display[current_view], request)) {
7792                 int key = get_input(0);
7794                 view = display[current_view];
7795                 request = get_keybinding(view->keymap, key);
7797                 /* Some low-level request handling. This keeps access to
7798                  * status_win restricted. */
7799                 switch (request) {
7800                 case REQ_PROMPT:
7801                 {
7802                         char *cmd = read_prompt(":");
7804                         if (cmd && isdigit(*cmd)) {
7805                                 int lineno = view->lineno + 1;
7807                                 if (parse_int(&lineno, cmd, 1, view->lines + 1) == OK) {
7808                                         select_view_line(view, lineno - 1);
7809                                         report("");
7810                                 } else {
7811                                         report("Unable to parse '%s' as a line number", cmd);
7812                                 }
7814                         } else if (cmd) {
7815                                 struct view *next = VIEW(REQ_VIEW_PAGER);
7816                                 const char *argv[SIZEOF_ARG] = { "git" };
7817                                 int argc = 1;
7819                                 /* When running random commands, initially show the
7820                                  * command in the title. However, it maybe later be
7821                                  * overwritten if a commit line is selected. */
7822                                 string_ncopy(next->ref, cmd, strlen(cmd));
7824                                 if (!argv_from_string(argv, &argc, cmd)) {
7825                                         report("Too many arguments");
7826                                 } else if (!prepare_update(next, argv, NULL, FORMAT_DASH)) {
7827                                         report("Failed to format command");
7828                                 } else {
7829                                         open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
7830                                 }
7831                         }
7833                         request = REQ_NONE;
7834                         break;
7835                 }
7836                 case REQ_SEARCH:
7837                 case REQ_SEARCH_BACK:
7838                 {
7839                         const char *prompt = request == REQ_SEARCH ? "/" : "?";
7840                         char *search = read_prompt(prompt);
7842                         if (search)
7843                                 string_ncopy(opt_search, search, strlen(search));
7844                         else if (*opt_search)
7845                                 request = request == REQ_SEARCH ?
7846                                         REQ_FIND_NEXT :
7847                                         REQ_FIND_PREV;
7848                         else
7849                                 request = REQ_NONE;
7850                         break;
7851                 }
7852                 default:
7853                         break;
7854                 }
7855         }
7857         quit(0);
7859         return 0;