Code

Fix a potential problem with reading tokens larger then BUFSIZ
[tig.git] / tig.c
1 /* Copyright (c) 2006-2009 Jonas Fonseca <fonseca@diku.dk>
2  *
3  * This program is free software; you can redistribute it and/or
4  * modify it under the terms of the GNU General Public License as
5  * published by the Free Software Foundation; either version 2 of
6  * the License, or (at your option) any later version.
7  *
8  * This program is distributed in the hope that it will be useful,
9  * but WITHOUT ANY WARRANTY; without even the implied warranty of
10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11  * GNU General Public License for more details.
12  */
14 #ifdef HAVE_CONFIG_H
15 #include "config.h"
16 #endif
18 #ifndef TIG_VERSION
19 #define TIG_VERSION "unknown-version"
20 #endif
22 #ifndef DEBUG
23 #define NDEBUG
24 #endif
26 #include <assert.h>
27 #include <errno.h>
28 #include <ctype.h>
29 #include <signal.h>
30 #include <stdarg.h>
31 #include <stdio.h>
32 #include <stdlib.h>
33 #include <string.h>
34 #include <sys/types.h>
35 #include <sys/wait.h>
36 #include <sys/stat.h>
37 #include <sys/select.h>
38 #include <unistd.h>
39 #include <time.h>
40 #include <fcntl.h>
42 #include <regex.h>
44 #include <locale.h>
45 #include <langinfo.h>
46 #include <iconv.h>
48 /* ncurses(3): Must be defined to have extended wide-character functions. */
49 #define _XOPEN_SOURCE_EXTENDED
51 #ifdef HAVE_NCURSESW_NCURSES_H
52 #include <ncursesw/ncurses.h>
53 #else
54 #ifdef HAVE_NCURSES_NCURSES_H
55 #include <ncurses/ncurses.h>
56 #else
57 #include <ncurses.h>
58 #endif
59 #endif
61 #if __GNUC__ >= 3
62 #define __NORETURN __attribute__((__noreturn__))
63 #else
64 #define __NORETURN
65 #endif
67 static void __NORETURN die(const char *err, ...);
68 static void warn(const char *msg, ...);
69 static void report(const char *msg, ...);
70 static void set_nonblocking_input(bool loading);
71 static int load_refs(void);
72 static size_t utf8_length(const char **string, size_t col, int *width, size_t max_width, int *trimmed, bool reserve);
74 #define ABS(x)          ((x) >= 0  ? (x) : -(x))
75 #define MIN(x, y)       ((x) < (y) ? (x) :  (y))
77 #define ARRAY_SIZE(x)   (sizeof(x) / sizeof(x[0]))
78 #define STRING_SIZE(x)  (sizeof(x) - 1)
80 #define SIZEOF_STR      1024    /* Default string size. */
81 #define SIZEOF_REF      256     /* Size of symbolic or SHA1 ID. */
82 #define SIZEOF_REV      41      /* Holds a SHA-1 and an ending NUL. */
83 #define SIZEOF_ARG      32      /* Default argument array size. */
85 /* Revision graph */
87 #define REVGRAPH_INIT   'I'
88 #define REVGRAPH_MERGE  'M'
89 #define REVGRAPH_BRANCH '+'
90 #define REVGRAPH_COMMIT '*'
91 #define REVGRAPH_BOUND  '^'
93 #define SIZEOF_REVGRAPH 19      /* Size of revision ancestry graphics. */
95 /* This color name can be used to refer to the default term colors. */
96 #define COLOR_DEFAULT   (-1)
98 #define ICONV_NONE      ((iconv_t) -1)
99 #ifndef ICONV_CONST
100 #define ICONV_CONST     /* nothing */
101 #endif
103 /* The format and size of the date column in the main view. */
104 #define DATE_FORMAT     "%Y-%m-%d %H:%M"
105 #define DATE_COLS       STRING_SIZE("2006-04-29 14:21 ")
107 #define AUTHOR_COLS     20
108 #define ID_COLS         8
110 /* The default interval between line numbers. */
111 #define NUMBER_INTERVAL 5
113 #define TAB_SIZE        8
115 #define SCALE_SPLIT_VIEW(height)        ((height) * 2 / 3)
117 #define NULL_ID         "0000000000000000000000000000000000000000"
119 #define S_ISGITLINK(mode) (((mode) & S_IFMT) == 0160000)
121 #ifndef GIT_CONFIG
122 #define GIT_CONFIG "config"
123 #endif
125 /* Some ASCII-shorthands fitted into the ncurses namespace. */
126 #define KEY_TAB         '\t'
127 #define KEY_RETURN      '\r'
128 #define KEY_ESC         27
131 struct ref {
132         char *name;             /* Ref name; tag or head names are shortened. */
133         char id[SIZEOF_REV];    /* Commit SHA1 ID */
134         unsigned int head:1;    /* Is it the current HEAD? */
135         unsigned int tag:1;     /* Is it a tag? */
136         unsigned int ltag:1;    /* If so, is the tag local? */
137         unsigned int remote:1;  /* Is it a remote ref? */
138         unsigned int tracked:1; /* Is it the remote for the current HEAD? */
139         unsigned int next:1;    /* For ref lists: are there more refs? */
140 };
142 static struct ref **get_refs(const char *id);
144 enum format_flags {
145         FORMAT_ALL,             /* Perform replacement in all arguments. */
146         FORMAT_DASH,            /* Perform replacement up until "--". */
147         FORMAT_NONE             /* No replacement should be performed. */
148 };
150 static bool format_argv(const char *dst[], const char *src[], enum format_flags flags);
152 enum input_status {
153         INPUT_OK,
154         INPUT_SKIP,
155         INPUT_STOP,
156         INPUT_CANCEL
157 };
159 typedef enum input_status (*input_handler)(void *data, char *buf, int c);
161 static char *prompt_input(const char *prompt, input_handler handler, void *data);
162 static bool prompt_yesno(const char *prompt);
164 /*
165  * Allocation helpers ... Entering macro hell to never be seen again.
166  */
168 #define DEFINE_ALLOCATOR(name, type, chunk_size)                                \
169 static type *                                                                   \
170 name(type **mem, size_t size, size_t increase)                                  \
171 {                                                                               \
172         size_t num_chunks = (size + chunk_size - 1) / chunk_size;               \
173         size_t num_chunks_new = (size + increase + chunk_size - 1) / chunk_size;\
174         type *tmp = *mem;                                                       \
175                                                                                 \
176         if (mem == NULL || num_chunks != num_chunks_new) {                      \
177                 tmp = realloc(tmp, num_chunks_new * chunk_size * sizeof(type)); \
178                 if (tmp)                                                        \
179                         *mem = tmp;                                             \
180         }                                                                       \
181                                                                                 \
182         return tmp;                                                             \
185 /*
186  * String helpers
187  */
189 static inline void
190 string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t srclen)
192         if (srclen > dstlen - 1)
193                 srclen = dstlen - 1;
195         strncpy(dst, src, srclen);
196         dst[srclen] = 0;
199 /* Shorthands for safely copying into a fixed buffer. */
201 #define string_copy(dst, src) \
202         string_ncopy_do(dst, sizeof(dst), src, sizeof(src))
204 #define string_ncopy(dst, src, srclen) \
205         string_ncopy_do(dst, sizeof(dst), src, srclen)
207 #define string_copy_rev(dst, src) \
208         string_ncopy_do(dst, SIZEOF_REV, src, SIZEOF_REV - 1)
210 #define string_add(dst, from, src) \
211         string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src))
213 static void
214 string_expand(char *dst, size_t dstlen, const char *src, int tabsize)
216         size_t size, pos;
218         for (size = pos = 0; size < dstlen - 1 && src[pos]; pos++) {
219                 if (src[pos] == '\t') {
220                         size_t expanded = tabsize - (size % tabsize);
222                         if (expanded + size >= dstlen - 1)
223                                 expanded = dstlen - size - 1;
224                         memcpy(dst + size, "        ", expanded);
225                         size += expanded;
226                 } else {
227                         dst[size++] = src[pos];
228                 }
229         }
231         dst[size] = 0;
234 static char *
235 chomp_string(char *name)
237         int namelen;
239         while (isspace(*name))
240                 name++;
242         namelen = strlen(name) - 1;
243         while (namelen > 0 && isspace(name[namelen]))
244                 name[namelen--] = 0;
246         return name;
249 static bool
250 string_nformat(char *buf, size_t bufsize, size_t *bufpos, const char *fmt, ...)
252         va_list args;
253         size_t pos = bufpos ? *bufpos : 0;
255         va_start(args, fmt);
256         pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
257         va_end(args);
259         if (bufpos)
260                 *bufpos = pos;
262         return pos >= bufsize ? FALSE : TRUE;
265 #define string_format(buf, fmt, args...) \
266         string_nformat(buf, sizeof(buf), NULL, fmt, args)
268 #define string_format_from(buf, from, fmt, args...) \
269         string_nformat(buf, sizeof(buf), from, fmt, args)
271 static int
272 string_enum_compare(const char *str1, const char *str2, int len)
274         size_t i;
276 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
278         /* Diff-Header == DIFF_HEADER */
279         for (i = 0; i < len; i++) {
280                 if (toupper(str1[i]) == toupper(str2[i]))
281                         continue;
283                 if (string_enum_sep(str1[i]) &&
284                     string_enum_sep(str2[i]))
285                         continue;
287                 return str1[i] - str2[i];
288         }
290         return 0;
293 struct enum_map {
294         const char *name;
295         int namelen;
296         int value;
297 };
299 #define ENUM_MAP(name, value) { name, STRING_SIZE(name), value }
301 static bool
302 map_enum_do(const struct enum_map *map, size_t map_size, int *value, const char *name)
304         size_t namelen = strlen(name);
305         int i;
307         for (i = 0; i < map_size; i++)
308                 if (namelen == map[i].namelen &&
309                     !string_enum_compare(name, map[i].name, namelen)) {
310                         *value = map[i].value;
311                         return TRUE;
312                 }
314         return FALSE;
317 #define map_enum(attr, map, name) \
318         map_enum_do(map, ARRAY_SIZE(map), attr, name)
320 #define prefixcmp(str1, str2) \
321         strncmp(str1, str2, STRING_SIZE(str2))
323 static inline int
324 suffixcmp(const char *str, int slen, const char *suffix)
326         size_t len = slen >= 0 ? slen : strlen(str);
327         size_t suffixlen = strlen(suffix);
329         return suffixlen < len ? strcmp(str + len - suffixlen, suffix) : -1;
333 static const char *
334 mkdate(const time_t *time)
336         static char buf[DATE_COLS + 1];
337         struct tm tm;
339         gmtime_r(time, &tm);
340         return strftime(buf, sizeof(buf), DATE_FORMAT, &tm) ? buf : NULL;
344 static bool
345 argv_from_string(const char *argv[SIZEOF_ARG], int *argc, char *cmd)
347         int valuelen;
349         while (*cmd && *argc < SIZEOF_ARG && (valuelen = strcspn(cmd, " \t"))) {
350                 bool advance = cmd[valuelen] != 0;
352                 cmd[valuelen] = 0;
353                 argv[(*argc)++] = chomp_string(cmd);
354                 cmd = chomp_string(cmd + valuelen + advance);
355         }
357         if (*argc < SIZEOF_ARG)
358                 argv[*argc] = NULL;
359         return *argc < SIZEOF_ARG;
362 static void
363 argv_from_env(const char **argv, const char *name)
365         char *env = argv ? getenv(name) : NULL;
366         int argc = 0;
368         if (env && *env)
369                 env = strdup(env);
370         if (env && !argv_from_string(argv, &argc, env))
371                 die("Too many arguments in the `%s` environment variable", name);
375 /*
376  * Executing external commands.
377  */
379 enum io_type {
380         IO_FD,                  /* File descriptor based IO. */
381         IO_BG,                  /* Execute command in the background. */
382         IO_FG,                  /* Execute command with same std{in,out,err}. */
383         IO_RD,                  /* Read only fork+exec IO. */
384         IO_WR,                  /* Write only fork+exec IO. */
385         IO_AP,                  /* Append fork+exec output to file. */
386 };
388 struct io {
389         enum io_type type;      /* The requested type of pipe. */
390         const char *dir;        /* Directory from which to execute. */
391         pid_t pid;              /* Pipe for reading or writing. */
392         int pipe;               /* Pipe end for reading or writing. */
393         int error;              /* Error status. */
394         const char *argv[SIZEOF_ARG];   /* Shell command arguments. */
395         char *buf;              /* Read buffer. */
396         size_t bufalloc;        /* Allocated buffer size. */
397         size_t bufsize;         /* Buffer content size. */
398         char *bufpos;           /* Current buffer position. */
399         unsigned int eof:1;     /* Has end of file been reached. */
400 };
402 static void
403 reset_io(struct io *io)
405         io->pipe = -1;
406         io->pid = 0;
407         io->buf = io->bufpos = NULL;
408         io->bufalloc = io->bufsize = 0;
409         io->error = 0;
410         io->eof = 0;
413 static void
414 init_io(struct io *io, const char *dir, enum io_type type)
416         reset_io(io);
417         io->type = type;
418         io->dir = dir;
421 static bool
422 init_io_rd(struct io *io, const char *argv[], const char *dir,
423                 enum format_flags flags)
425         init_io(io, dir, IO_RD);
426         return format_argv(io->argv, argv, flags);
429 static bool
430 io_open(struct io *io, const char *name)
432         init_io(io, NULL, IO_FD);
433         io->pipe = *name ? open(name, O_RDONLY) : STDIN_FILENO;
434         if (io->pipe == -1)
435                 io->error = errno;
436         return io->pipe != -1;
439 static bool
440 kill_io(struct io *io)
442         return io->pid == 0 || kill(io->pid, SIGKILL) != -1;
445 static bool
446 done_io(struct io *io)
448         pid_t pid = io->pid;
450         if (io->pipe != -1)
451                 close(io->pipe);
452         free(io->buf);
453         reset_io(io);
455         while (pid > 0) {
456                 int status;
457                 pid_t waiting = waitpid(pid, &status, 0);
459                 if (waiting < 0) {
460                         if (errno == EINTR)
461                                 continue;
462                         report("waitpid failed (%s)", strerror(errno));
463                         return FALSE;
464                 }
466                 return waiting == pid &&
467                        !WIFSIGNALED(status) &&
468                        WIFEXITED(status) &&
469                        !WEXITSTATUS(status);
470         }
472         return TRUE;
475 static bool
476 start_io(struct io *io)
478         int pipefds[2] = { -1, -1 };
480         if (io->type == IO_FD)
481                 return TRUE;
483         if ((io->type == IO_RD || io->type == IO_WR) &&
484             pipe(pipefds) < 0)
485                 return FALSE;
486         else if (io->type == IO_AP)
487                 pipefds[1] = io->pipe;
489         if ((io->pid = fork())) {
490                 if (pipefds[!(io->type == IO_WR)] != -1)
491                         close(pipefds[!(io->type == IO_WR)]);
492                 if (io->pid != -1) {
493                         io->pipe = pipefds[!!(io->type == IO_WR)];
494                         return TRUE;
495                 }
497         } else {
498                 if (io->type != IO_FG) {
499                         int devnull = open("/dev/null", O_RDWR);
500                         int readfd  = io->type == IO_WR ? pipefds[0] : devnull;
501                         int writefd = (io->type == IO_RD || io->type == IO_AP)
502                                                         ? pipefds[1] : devnull;
504                         dup2(readfd,  STDIN_FILENO);
505                         dup2(writefd, STDOUT_FILENO);
506                         dup2(devnull, STDERR_FILENO);
508                         close(devnull);
509                         if (pipefds[0] != -1)
510                                 close(pipefds[0]);
511                         if (pipefds[1] != -1)
512                                 close(pipefds[1]);
513                 }
515                 if (io->dir && *io->dir && chdir(io->dir) == -1)
516                         die("Failed to change directory: %s", strerror(errno));
518                 execvp(io->argv[0], (char *const*) io->argv);
519                 die("Failed to execute program: %s", strerror(errno));
520         }
522         if (pipefds[!!(io->type == IO_WR)] != -1)
523                 close(pipefds[!!(io->type == IO_WR)]);
524         return FALSE;
527 static bool
528 run_io(struct io *io, const char **argv, const char *dir, enum io_type type)
530         init_io(io, dir, type);
531         if (!format_argv(io->argv, argv, FORMAT_NONE))
532                 return FALSE;
533         return start_io(io);
536 static int
537 run_io_do(struct io *io)
539         return start_io(io) && done_io(io);
542 static int
543 run_io_bg(const char **argv)
545         struct io io = {};
547         init_io(&io, NULL, IO_BG);
548         if (!format_argv(io.argv, argv, FORMAT_NONE))
549                 return FALSE;
550         return run_io_do(&io);
553 static bool
554 run_io_fg(const char **argv, const char *dir)
556         struct io io = {};
558         init_io(&io, dir, IO_FG);
559         if (!format_argv(io.argv, argv, FORMAT_NONE))
560                 return FALSE;
561         return run_io_do(&io);
564 static bool
565 run_io_append(const char **argv, enum format_flags flags, int fd)
567         struct io io = {};
569         init_io(&io, NULL, IO_AP);
570         io.pipe = fd;
571         if (format_argv(io.argv, argv, flags))
572                 return run_io_do(&io);
573         close(fd);
574         return FALSE;
577 static bool
578 run_io_rd(struct io *io, const char **argv, enum format_flags flags)
580         return init_io_rd(io, argv, NULL, flags) && start_io(io);
583 static bool
584 io_eof(struct io *io)
586         return io->eof;
589 static int
590 io_error(struct io *io)
592         return io->error;
595 static char *
596 io_strerror(struct io *io)
598         return strerror(io->error);
601 static bool
602 io_can_read(struct io *io)
604         struct timeval tv = { 0, 500 };
605         fd_set fds;
607         FD_ZERO(&fds);
608         FD_SET(io->pipe, &fds);
610         return select(io->pipe + 1, &fds, NULL, NULL, &tv) > 0;
613 static ssize_t
614 io_read(struct io *io, void *buf, size_t bufsize)
616         do {
617                 ssize_t readsize = read(io->pipe, buf, bufsize);
619                 if (readsize < 0 && (errno == EAGAIN || errno == EINTR))
620                         continue;
621                 else if (readsize == -1)
622                         io->error = errno;
623                 else if (readsize == 0)
624                         io->eof = 1;
625                 return readsize;
626         } while (1);
629 DEFINE_ALLOCATOR(realloc_io_buf, char, BUFSIZ)
631 static char *
632 io_get(struct io *io, int c, bool can_read)
634         char *eol;
635         ssize_t readsize;
637         while (TRUE) {
638                 if (io->bufsize > 0) {
639                         eol = memchr(io->bufpos, c, io->bufsize);
640                         if (eol) {
641                                 char *line = io->bufpos;
643                                 *eol = 0;
644                                 io->bufpos = eol + 1;
645                                 io->bufsize -= io->bufpos - line;
646                                 return line;
647                         }
648                 }
650                 if (io_eof(io)) {
651                         if (io->bufsize) {
652                                 io->bufpos[io->bufsize] = 0;
653                                 io->bufsize = 0;
654                                 return io->bufpos;
655                         }
656                         return NULL;
657                 }
659                 if (!can_read)
660                         return NULL;
662                 if (io->bufsize > 0 && io->bufpos > io->buf)
663                         memmove(io->buf, io->bufpos, io->bufsize);
665                 if (io->bufalloc == io->bufsize) {
666                         if (!realloc_io_buf(&io->buf, io->bufalloc, BUFSIZ))
667                                 return NULL;
668                         io->bufalloc += BUFSIZ;
669                 }
671                 io->bufpos = io->buf;
672                 readsize = io_read(io, io->buf + io->bufsize, io->bufalloc - io->bufsize);
673                 if (io_error(io))
674                         return NULL;
675                 io->bufsize += readsize;
676         }
679 static bool
680 io_write(struct io *io, const void *buf, size_t bufsize)
682         size_t written = 0;
684         while (!io_error(io) && written < bufsize) {
685                 ssize_t size;
687                 size = write(io->pipe, buf + written, bufsize - written);
688                 if (size < 0 && (errno == EAGAIN || errno == EINTR))
689                         continue;
690                 else if (size == -1)
691                         io->error = errno;
692                 else
693                         written += size;
694         }
696         return written == bufsize;
699 static bool
700 io_read_buf(struct io *io, char buf[], size_t bufsize)
702         char *result = io_get(io, '\n', TRUE);
704         if (result) {
705                 result = chomp_string(result);
706                 string_ncopy_do(buf, bufsize, result, strlen(result));
707         }
709         return done_io(io) && result;
712 static bool
713 run_io_buf(const char **argv, char buf[], size_t bufsize)
715         struct io io = {};
717         return run_io_rd(&io, argv, FORMAT_NONE) && io_read_buf(&io, buf, bufsize);
720 static int
721 io_load(struct io *io, const char *separators,
722         int (*read_property)(char *, size_t, char *, size_t))
724         char *name;
725         int state = OK;
727         if (!start_io(io))
728                 return ERR;
730         while (state == OK && (name = io_get(io, '\n', TRUE))) {
731                 char *value;
732                 size_t namelen;
733                 size_t valuelen;
735                 name = chomp_string(name);
736                 namelen = strcspn(name, separators);
738                 if (name[namelen]) {
739                         name[namelen] = 0;
740                         value = chomp_string(name + namelen + 1);
741                         valuelen = strlen(value);
743                 } else {
744                         value = "";
745                         valuelen = 0;
746                 }
748                 state = read_property(name, namelen, value, valuelen);
749         }
751         if (state != ERR && io_error(io))
752                 state = ERR;
753         done_io(io);
755         return state;
758 static int
759 run_io_load(const char **argv, const char *separators,
760             int (*read_property)(char *, size_t, char *, size_t))
762         struct io io = {};
764         return init_io_rd(&io, argv, NULL, FORMAT_NONE)
765                 ? io_load(&io, separators, read_property) : ERR;
769 /*
770  * User requests
771  */
773 #define REQ_INFO \
774         /* XXX: Keep the view request first and in sync with views[]. */ \
775         REQ_GROUP("View switching") \
776         REQ_(VIEW_MAIN,         "Show main view"), \
777         REQ_(VIEW_DIFF,         "Show diff view"), \
778         REQ_(VIEW_LOG,          "Show log view"), \
779         REQ_(VIEW_TREE,         "Show tree view"), \
780         REQ_(VIEW_BLOB,         "Show blob view"), \
781         REQ_(VIEW_BLAME,        "Show blame view"), \
782         REQ_(VIEW_HELP,         "Show help page"), \
783         REQ_(VIEW_PAGER,        "Show pager view"), \
784         REQ_(VIEW_STATUS,       "Show status view"), \
785         REQ_(VIEW_STAGE,        "Show stage view"), \
786         \
787         REQ_GROUP("View manipulation") \
788         REQ_(ENTER,             "Enter current line and scroll"), \
789         REQ_(NEXT,              "Move to next"), \
790         REQ_(PREVIOUS,          "Move to previous"), \
791         REQ_(PARENT,            "Move to parent"), \
792         REQ_(VIEW_NEXT,         "Move focus to next view"), \
793         REQ_(REFRESH,           "Reload and refresh"), \
794         REQ_(MAXIMIZE,          "Maximize the current view"), \
795         REQ_(VIEW_CLOSE,        "Close the current view"), \
796         REQ_(QUIT,              "Close all views and quit"), \
797         \
798         REQ_GROUP("View specific requests") \
799         REQ_(STATUS_UPDATE,     "Update file status"), \
800         REQ_(STATUS_REVERT,     "Revert file changes"), \
801         REQ_(STATUS_MERGE,      "Merge file using external tool"), \
802         REQ_(STAGE_NEXT,        "Find next chunk to stage"), \
803         \
804         REQ_GROUP("Cursor navigation") \
805         REQ_(MOVE_UP,           "Move cursor one line up"), \
806         REQ_(MOVE_DOWN,         "Move cursor one line down"), \
807         REQ_(MOVE_PAGE_DOWN,    "Move cursor one page down"), \
808         REQ_(MOVE_PAGE_UP,      "Move cursor one page up"), \
809         REQ_(MOVE_FIRST_LINE,   "Move cursor to first line"), \
810         REQ_(MOVE_LAST_LINE,    "Move cursor to last line"), \
811         \
812         REQ_GROUP("Scrolling") \
813         REQ_(SCROLL_LEFT,       "Scroll two columns left"), \
814         REQ_(SCROLL_RIGHT,      "Scroll two columns right"), \
815         REQ_(SCROLL_LINE_UP,    "Scroll one line up"), \
816         REQ_(SCROLL_LINE_DOWN,  "Scroll one line down"), \
817         REQ_(SCROLL_PAGE_UP,    "Scroll one page up"), \
818         REQ_(SCROLL_PAGE_DOWN,  "Scroll one page down"), \
819         \
820         REQ_GROUP("Searching") \
821         REQ_(SEARCH,            "Search the view"), \
822         REQ_(SEARCH_BACK,       "Search backwards in the view"), \
823         REQ_(FIND_NEXT,         "Find next search match"), \
824         REQ_(FIND_PREV,         "Find previous search match"), \
825         \
826         REQ_GROUP("Option manipulation") \
827         REQ_(TOGGLE_LINENO,     "Toggle line numbers"), \
828         REQ_(TOGGLE_DATE,       "Toggle date display"), \
829         REQ_(TOGGLE_AUTHOR,     "Toggle author display"), \
830         REQ_(TOGGLE_REV_GRAPH,  "Toggle revision graph visualization"), \
831         REQ_(TOGGLE_REFS,       "Toggle reference display (tags/branches)"), \
832         \
833         REQ_GROUP("Misc") \
834         REQ_(PROMPT,            "Bring up the prompt"), \
835         REQ_(SCREEN_REDRAW,     "Redraw the screen"), \
836         REQ_(SHOW_VERSION,      "Show version information"), \
837         REQ_(STOP_LOADING,      "Stop all loading views"), \
838         REQ_(EDIT,              "Open in editor"), \
839         REQ_(NONE,              "Do nothing")
842 /* User action requests. */
843 enum request {
844 #define REQ_GROUP(help)
845 #define REQ_(req, help) REQ_##req
847         /* Offset all requests to avoid conflicts with ncurses getch values. */
848         REQ_OFFSET = KEY_MAX + 1,
849         REQ_INFO
851 #undef  REQ_GROUP
852 #undef  REQ_
853 };
855 struct request_info {
856         enum request request;
857         const char *name;
858         int namelen;
859         const char *help;
860 };
862 static const struct request_info req_info[] = {
863 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
864 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
865         REQ_INFO
866 #undef  REQ_GROUP
867 #undef  REQ_
868 };
870 static enum request
871 get_request(const char *name)
873         int namelen = strlen(name);
874         int i;
876         for (i = 0; i < ARRAY_SIZE(req_info); i++)
877                 if (req_info[i].namelen == namelen &&
878                     !string_enum_compare(req_info[i].name, name, namelen))
879                         return req_info[i].request;
881         return REQ_NONE;
885 /*
886  * Options
887  */
889 /* Option and state variables. */
890 static bool opt_date                    = TRUE;
891 static bool opt_author                  = TRUE;
892 static bool opt_line_number             = FALSE;
893 static bool opt_line_graphics           = TRUE;
894 static bool opt_rev_graph               = FALSE;
895 static bool opt_show_refs               = TRUE;
896 static int opt_num_interval             = NUMBER_INTERVAL;
897 static double opt_hscroll               = 0.50;
898 static int opt_tab_size                 = TAB_SIZE;
899 static int opt_author_cols              = AUTHOR_COLS-1;
900 static char opt_path[SIZEOF_STR]        = "";
901 static char opt_file[SIZEOF_STR]        = "";
902 static char opt_ref[SIZEOF_REF]         = "";
903 static char opt_head[SIZEOF_REF]        = "";
904 static char opt_head_rev[SIZEOF_REV]    = "";
905 static char opt_remote[SIZEOF_REF]      = "";
906 static char opt_encoding[20]            = "UTF-8";
907 static bool opt_utf8                    = TRUE;
908 static char opt_codeset[20]             = "UTF-8";
909 static iconv_t opt_iconv                = ICONV_NONE;
910 static char opt_search[SIZEOF_STR]      = "";
911 static char opt_cdup[SIZEOF_STR]        = "";
912 static char opt_prefix[SIZEOF_STR]      = "";
913 static char opt_git_dir[SIZEOF_STR]     = "";
914 static signed char opt_is_inside_work_tree      = -1; /* set to TRUE or FALSE */
915 static char opt_editor[SIZEOF_STR]      = "";
916 static FILE *opt_tty                    = NULL;
918 #define is_initial_commit()     (!*opt_head_rev)
919 #define is_head_commit(rev)     (!strcmp((rev), "HEAD") || !strcmp(opt_head_rev, (rev)))
922 /*
923  * Line-oriented content detection.
924  */
926 #define LINE_INFO \
927 LINE(DIFF_HEADER,  "diff --git ",       COLOR_YELLOW,   COLOR_DEFAULT,  0), \
928 LINE(DIFF_CHUNK,   "@@",                COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
929 LINE(DIFF_ADD,     "+",                 COLOR_GREEN,    COLOR_DEFAULT,  0), \
930 LINE(DIFF_DEL,     "-",                 COLOR_RED,      COLOR_DEFAULT,  0), \
931 LINE(DIFF_INDEX,        "index ",         COLOR_BLUE,   COLOR_DEFAULT,  0), \
932 LINE(DIFF_OLDMODE,      "old file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
933 LINE(DIFF_NEWMODE,      "new file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
934 LINE(DIFF_COPY_FROM,    "copy from",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
935 LINE(DIFF_COPY_TO,      "copy to",        COLOR_YELLOW, COLOR_DEFAULT,  0), \
936 LINE(DIFF_RENAME_FROM,  "rename from",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
937 LINE(DIFF_RENAME_TO,    "rename to",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
938 LINE(DIFF_SIMILARITY,   "similarity ",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
939 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
940 LINE(DIFF_TREE,         "diff-tree ",     COLOR_BLUE,   COLOR_DEFAULT,  0), \
941 LINE(PP_AUTHOR,    "Author: ",          COLOR_CYAN,     COLOR_DEFAULT,  0), \
942 LINE(PP_COMMIT,    "Commit: ",          COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
943 LINE(PP_MERGE,     "Merge: ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
944 LINE(PP_DATE,      "Date:   ",          COLOR_YELLOW,   COLOR_DEFAULT,  0), \
945 LINE(PP_ADATE,     "AuthorDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
946 LINE(PP_CDATE,     "CommitDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
947 LINE(PP_REFS,      "Refs: ",            COLOR_RED,      COLOR_DEFAULT,  0), \
948 LINE(COMMIT,       "commit ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
949 LINE(PARENT,       "parent ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
950 LINE(TREE,         "tree ",             COLOR_BLUE,     COLOR_DEFAULT,  0), \
951 LINE(AUTHOR,       "author ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
952 LINE(COMMITTER,    "committer ",        COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
953 LINE(SIGNOFF,      "    Signed-off-by", COLOR_YELLOW,   COLOR_DEFAULT,  0), \
954 LINE(ACKED,        "    Acked-by",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
955 LINE(DEFAULT,      "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
956 LINE(CURSOR,       "",                  COLOR_WHITE,    COLOR_GREEN,    A_BOLD), \
957 LINE(STATUS,       "",                  COLOR_GREEN,    COLOR_DEFAULT,  0), \
958 LINE(DELIMITER,    "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
959 LINE(DATE,         "",                  COLOR_BLUE,     COLOR_DEFAULT,  0), \
960 LINE(MODE,         "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
961 LINE(LINE_NUMBER,  "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
962 LINE(TITLE_BLUR,   "",                  COLOR_WHITE,    COLOR_BLUE,     0), \
963 LINE(TITLE_FOCUS,  "",                  COLOR_WHITE,    COLOR_BLUE,     A_BOLD), \
964 LINE(MAIN_COMMIT,  "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
965 LINE(MAIN_TAG,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  A_BOLD), \
966 LINE(MAIN_LOCAL_TAG,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
967 LINE(MAIN_REMOTE,  "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
968 LINE(MAIN_TRACKED, "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_BOLD), \
969 LINE(MAIN_REF,     "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
970 LINE(MAIN_HEAD,    "",                  COLOR_CYAN,     COLOR_DEFAULT,  A_BOLD), \
971 LINE(MAIN_REVGRAPH,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
972 LINE(TREE_HEAD,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_BOLD), \
973 LINE(TREE_DIR,     "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_NORMAL), \
974 LINE(TREE_FILE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
975 LINE(STAT_HEAD,    "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
976 LINE(STAT_SECTION, "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
977 LINE(STAT_NONE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
978 LINE(STAT_STAGED,  "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
979 LINE(STAT_UNSTAGED,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
980 LINE(STAT_UNTRACKED,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
981 LINE(BLAME_ID,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0)
983 enum line_type {
984 #define LINE(type, line, fg, bg, attr) \
985         LINE_##type
986         LINE_INFO,
987         LINE_NONE
988 #undef  LINE
989 };
991 struct line_info {
992         const char *name;       /* Option name. */
993         int namelen;            /* Size of option name. */
994         const char *line;       /* The start of line to match. */
995         int linelen;            /* Size of string to match. */
996         int fg, bg, attr;       /* Color and text attributes for the lines. */
997 };
999 static struct line_info line_info[] = {
1000 #define LINE(type, line, fg, bg, attr) \
1001         { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
1002         LINE_INFO
1003 #undef  LINE
1004 };
1006 static enum line_type
1007 get_line_type(const char *line)
1009         int linelen = strlen(line);
1010         enum line_type type;
1012         for (type = 0; type < ARRAY_SIZE(line_info); type++)
1013                 /* Case insensitive search matches Signed-off-by lines better. */
1014                 if (linelen >= line_info[type].linelen &&
1015                     !strncasecmp(line_info[type].line, line, line_info[type].linelen))
1016                         return type;
1018         return LINE_DEFAULT;
1021 static inline int
1022 get_line_attr(enum line_type type)
1024         assert(type < ARRAY_SIZE(line_info));
1025         return COLOR_PAIR(type) | line_info[type].attr;
1028 static struct line_info *
1029 get_line_info(const char *name)
1031         size_t namelen = strlen(name);
1032         enum line_type type;
1034         for (type = 0; type < ARRAY_SIZE(line_info); type++)
1035                 if (namelen == line_info[type].namelen &&
1036                     !string_enum_compare(line_info[type].name, name, namelen))
1037                         return &line_info[type];
1039         return NULL;
1042 static void
1043 init_colors(void)
1045         int default_bg = line_info[LINE_DEFAULT].bg;
1046         int default_fg = line_info[LINE_DEFAULT].fg;
1047         enum line_type type;
1049         start_color();
1051         if (assume_default_colors(default_fg, default_bg) == ERR) {
1052                 default_bg = COLOR_BLACK;
1053                 default_fg = COLOR_WHITE;
1054         }
1056         for (type = 0; type < ARRAY_SIZE(line_info); type++) {
1057                 struct line_info *info = &line_info[type];
1058                 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
1059                 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
1061                 init_pair(type, fg, bg);
1062         }
1065 struct line {
1066         enum line_type type;
1068         /* State flags */
1069         unsigned int selected:1;
1070         unsigned int dirty:1;
1071         unsigned int cleareol:1;
1073         void *data;             /* User data */
1074 };
1077 /*
1078  * Keys
1079  */
1081 struct keybinding {
1082         int alias;
1083         enum request request;
1084 };
1086 static const struct keybinding default_keybindings[] = {
1087         /* View switching */
1088         { 'm',          REQ_VIEW_MAIN },
1089         { 'd',          REQ_VIEW_DIFF },
1090         { 'l',          REQ_VIEW_LOG },
1091         { 't',          REQ_VIEW_TREE },
1092         { 'f',          REQ_VIEW_BLOB },
1093         { 'B',          REQ_VIEW_BLAME },
1094         { 'p',          REQ_VIEW_PAGER },
1095         { 'h',          REQ_VIEW_HELP },
1096         { 'S',          REQ_VIEW_STATUS },
1097         { 'c',          REQ_VIEW_STAGE },
1099         /* View manipulation */
1100         { 'q',          REQ_VIEW_CLOSE },
1101         { KEY_TAB,      REQ_VIEW_NEXT },
1102         { KEY_RETURN,   REQ_ENTER },
1103         { KEY_UP,       REQ_PREVIOUS },
1104         { KEY_DOWN,     REQ_NEXT },
1105         { 'R',          REQ_REFRESH },
1106         { KEY_F(5),     REQ_REFRESH },
1107         { 'O',          REQ_MAXIMIZE },
1109         /* Cursor navigation */
1110         { 'k',          REQ_MOVE_UP },
1111         { 'j',          REQ_MOVE_DOWN },
1112         { KEY_HOME,     REQ_MOVE_FIRST_LINE },
1113         { KEY_END,      REQ_MOVE_LAST_LINE },
1114         { KEY_NPAGE,    REQ_MOVE_PAGE_DOWN },
1115         { ' ',          REQ_MOVE_PAGE_DOWN },
1116         { KEY_PPAGE,    REQ_MOVE_PAGE_UP },
1117         { 'b',          REQ_MOVE_PAGE_UP },
1118         { '-',          REQ_MOVE_PAGE_UP },
1120         /* Scrolling */
1121         { KEY_LEFT,     REQ_SCROLL_LEFT },
1122         { KEY_RIGHT,    REQ_SCROLL_RIGHT },
1123         { KEY_IC,       REQ_SCROLL_LINE_UP },
1124         { KEY_DC,       REQ_SCROLL_LINE_DOWN },
1125         { 'w',          REQ_SCROLL_PAGE_UP },
1126         { 's',          REQ_SCROLL_PAGE_DOWN },
1128         /* Searching */
1129         { '/',          REQ_SEARCH },
1130         { '?',          REQ_SEARCH_BACK },
1131         { 'n',          REQ_FIND_NEXT },
1132         { 'N',          REQ_FIND_PREV },
1134         /* Misc */
1135         { 'Q',          REQ_QUIT },
1136         { 'z',          REQ_STOP_LOADING },
1137         { 'v',          REQ_SHOW_VERSION },
1138         { 'r',          REQ_SCREEN_REDRAW },
1139         { '.',          REQ_TOGGLE_LINENO },
1140         { 'D',          REQ_TOGGLE_DATE },
1141         { 'A',          REQ_TOGGLE_AUTHOR },
1142         { 'g',          REQ_TOGGLE_REV_GRAPH },
1143         { 'F',          REQ_TOGGLE_REFS },
1144         { ':',          REQ_PROMPT },
1145         { 'u',          REQ_STATUS_UPDATE },
1146         { '!',          REQ_STATUS_REVERT },
1147         { 'M',          REQ_STATUS_MERGE },
1148         { '@',          REQ_STAGE_NEXT },
1149         { ',',          REQ_PARENT },
1150         { 'e',          REQ_EDIT },
1151 };
1153 #define KEYMAP_INFO \
1154         KEYMAP_(GENERIC), \
1155         KEYMAP_(MAIN), \
1156         KEYMAP_(DIFF), \
1157         KEYMAP_(LOG), \
1158         KEYMAP_(TREE), \
1159         KEYMAP_(BLOB), \
1160         KEYMAP_(BLAME), \
1161         KEYMAP_(PAGER), \
1162         KEYMAP_(HELP), \
1163         KEYMAP_(STATUS), \
1164         KEYMAP_(STAGE)
1166 enum keymap {
1167 #define KEYMAP_(name) KEYMAP_##name
1168         KEYMAP_INFO
1169 #undef  KEYMAP_
1170 };
1172 static const struct enum_map keymap_table[] = {
1173 #define KEYMAP_(name) ENUM_MAP(#name, KEYMAP_##name)
1174         KEYMAP_INFO
1175 #undef  KEYMAP_
1176 };
1178 #define set_keymap(map, name) map_enum(map, keymap_table, name)
1180 struct keybinding_table {
1181         struct keybinding *data;
1182         size_t size;
1183 };
1185 static struct keybinding_table keybindings[ARRAY_SIZE(keymap_table)];
1187 static void
1188 add_keybinding(enum keymap keymap, enum request request, int key)
1190         struct keybinding_table *table = &keybindings[keymap];
1192         table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data));
1193         if (!table->data)
1194                 die("Failed to allocate keybinding");
1195         table->data[table->size].alias = key;
1196         table->data[table->size++].request = request;
1199 /* Looks for a key binding first in the given map, then in the generic map, and
1200  * lastly in the default keybindings. */
1201 static enum request
1202 get_keybinding(enum keymap keymap, int key)
1204         size_t i;
1206         for (i = 0; i < keybindings[keymap].size; i++)
1207                 if (keybindings[keymap].data[i].alias == key)
1208                         return keybindings[keymap].data[i].request;
1210         for (i = 0; i < keybindings[KEYMAP_GENERIC].size; i++)
1211                 if (keybindings[KEYMAP_GENERIC].data[i].alias == key)
1212                         return keybindings[KEYMAP_GENERIC].data[i].request;
1214         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1215                 if (default_keybindings[i].alias == key)
1216                         return default_keybindings[i].request;
1218         return (enum request) key;
1222 struct key {
1223         const char *name;
1224         int value;
1225 };
1227 static const struct key key_table[] = {
1228         { "Enter",      KEY_RETURN },
1229         { "Space",      ' ' },
1230         { "Backspace",  KEY_BACKSPACE },
1231         { "Tab",        KEY_TAB },
1232         { "Escape",     KEY_ESC },
1233         { "Left",       KEY_LEFT },
1234         { "Right",      KEY_RIGHT },
1235         { "Up",         KEY_UP },
1236         { "Down",       KEY_DOWN },
1237         { "Insert",     KEY_IC },
1238         { "Delete",     KEY_DC },
1239         { "Hash",       '#' },
1240         { "Home",       KEY_HOME },
1241         { "End",        KEY_END },
1242         { "PageUp",     KEY_PPAGE },
1243         { "PageDown",   KEY_NPAGE },
1244         { "F1",         KEY_F(1) },
1245         { "F2",         KEY_F(2) },
1246         { "F3",         KEY_F(3) },
1247         { "F4",         KEY_F(4) },
1248         { "F5",         KEY_F(5) },
1249         { "F6",         KEY_F(6) },
1250         { "F7",         KEY_F(7) },
1251         { "F8",         KEY_F(8) },
1252         { "F9",         KEY_F(9) },
1253         { "F10",        KEY_F(10) },
1254         { "F11",        KEY_F(11) },
1255         { "F12",        KEY_F(12) },
1256 };
1258 static int
1259 get_key_value(const char *name)
1261         int i;
1263         for (i = 0; i < ARRAY_SIZE(key_table); i++)
1264                 if (!strcasecmp(key_table[i].name, name))
1265                         return key_table[i].value;
1267         if (strlen(name) == 1 && isprint(*name))
1268                 return (int) *name;
1270         return ERR;
1273 static const char *
1274 get_key_name(int key_value)
1276         static char key_char[] = "'X'";
1277         const char *seq = NULL;
1278         int key;
1280         for (key = 0; key < ARRAY_SIZE(key_table); key++)
1281                 if (key_table[key].value == key_value)
1282                         seq = key_table[key].name;
1284         if (seq == NULL &&
1285             key_value < 127 &&
1286             isprint(key_value)) {
1287                 key_char[1] = (char) key_value;
1288                 seq = key_char;
1289         }
1291         return seq ? seq : "(no key)";
1294 static const char *
1295 get_key(enum request request)
1297         static char buf[BUFSIZ];
1298         size_t pos = 0;
1299         char *sep = "";
1300         int i;
1302         buf[pos] = 0;
1304         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1305                 const struct keybinding *keybinding = &default_keybindings[i];
1307                 if (keybinding->request != request)
1308                         continue;
1310                 if (!string_format_from(buf, &pos, "%s%s", sep,
1311                                         get_key_name(keybinding->alias)))
1312                         return "Too many keybindings!";
1313                 sep = ", ";
1314         }
1316         return buf;
1319 struct run_request {
1320         enum keymap keymap;
1321         int key;
1322         const char *argv[SIZEOF_ARG];
1323 };
1325 static struct run_request *run_request;
1326 static size_t run_requests;
1328 DEFINE_ALLOCATOR(realloc_run_requests, struct run_request, 8)
1330 static enum request
1331 add_run_request(enum keymap keymap, int key, int argc, const char **argv)
1333         struct run_request *req;
1335         if (argc >= ARRAY_SIZE(req->argv) - 1)
1336                 return REQ_NONE;
1338         if (!realloc_run_requests(&run_request, run_requests, 1))
1339                 return REQ_NONE;
1341         req = &run_request[run_requests];
1342         req->keymap = keymap;
1343         req->key = key;
1344         req->argv[0] = NULL;
1346         if (!format_argv(req->argv, argv, FORMAT_NONE))
1347                 return REQ_NONE;
1349         return REQ_NONE + ++run_requests;
1352 static struct run_request *
1353 get_run_request(enum request request)
1355         if (request <= REQ_NONE)
1356                 return NULL;
1357         return &run_request[request - REQ_NONE - 1];
1360 static void
1361 add_builtin_run_requests(void)
1363         const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1364         const char *gc[] = { "git", "gc", NULL };
1365         struct {
1366                 enum keymap keymap;
1367                 int key;
1368                 int argc;
1369                 const char **argv;
1370         } reqs[] = {
1371                 { KEYMAP_MAIN,    'C', ARRAY_SIZE(cherry_pick) - 1, cherry_pick },
1372                 { KEYMAP_GENERIC, 'G', ARRAY_SIZE(gc) - 1, gc },
1373         };
1374         int i;
1376         for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1377                 enum request req;
1379                 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argc, reqs[i].argv);
1380                 if (req != REQ_NONE)
1381                         add_keybinding(reqs[i].keymap, req, reqs[i].key);
1382         }
1385 /*
1386  * User config file handling.
1387  */
1389 static int   config_lineno;
1390 static bool  config_errors;
1391 static const char *config_msg;
1393 static const struct enum_map color_map[] = {
1394 #define COLOR_MAP(name) ENUM_MAP(#name, COLOR_##name)
1395         COLOR_MAP(DEFAULT),
1396         COLOR_MAP(BLACK),
1397         COLOR_MAP(BLUE),
1398         COLOR_MAP(CYAN),
1399         COLOR_MAP(GREEN),
1400         COLOR_MAP(MAGENTA),
1401         COLOR_MAP(RED),
1402         COLOR_MAP(WHITE),
1403         COLOR_MAP(YELLOW),
1404 };
1406 static const struct enum_map attr_map[] = {
1407 #define ATTR_MAP(name) ENUM_MAP(#name, A_##name)
1408         ATTR_MAP(NORMAL),
1409         ATTR_MAP(BLINK),
1410         ATTR_MAP(BOLD),
1411         ATTR_MAP(DIM),
1412         ATTR_MAP(REVERSE),
1413         ATTR_MAP(STANDOUT),
1414         ATTR_MAP(UNDERLINE),
1415 };
1417 #define set_attribute(attr, name)       map_enum(attr, attr_map, name)
1419 static int parse_step(double *opt, const char *arg)
1421         *opt = atoi(arg);
1422         if (!strchr(arg, '%'))
1423                 return OK;
1425         /* "Shift down" so 100% and 1 does not conflict. */
1426         *opt = (*opt - 1) / 100;
1427         if (*opt >= 1.0) {
1428                 *opt = 0.99;
1429                 config_msg = "Step value larger than 100%";
1430                 return ERR;
1431         }
1432         if (*opt < 0.0) {
1433                 *opt = 1;
1434                 config_msg = "Invalid step value";
1435                 return ERR;
1436         }
1437         return OK;
1440 static int
1441 parse_int(int *opt, const char *arg, int min, int max)
1443         int value = atoi(arg);
1445         if (min <= value && value <= max) {
1446                 *opt = value;
1447                 return OK;
1448         }
1450         config_msg = "Integer value out of bound";
1451         return ERR;
1454 static bool
1455 set_color(int *color, const char *name)
1457         if (map_enum(color, color_map, name))
1458                 return TRUE;
1459         if (!prefixcmp(name, "color"))
1460                 return parse_int(color, name + 5, 0, 255) == OK;
1461         return FALSE;
1464 /* Wants: object fgcolor bgcolor [attribute] */
1465 static int
1466 option_color_command(int argc, const char *argv[])
1468         struct line_info *info;
1470         if (argc != 3 && argc != 4) {
1471                 config_msg = "Wrong number of arguments given to color command";
1472                 return ERR;
1473         }
1475         info = get_line_info(argv[0]);
1476         if (!info) {
1477                 static const struct enum_map obsolete[] = {
1478                         ENUM_MAP("main-delim",  LINE_DELIMITER),
1479                         ENUM_MAP("main-date",   LINE_DATE),
1480                         ENUM_MAP("main-author", LINE_AUTHOR),
1481                 };
1482                 int index;
1484                 if (!map_enum(&index, obsolete, argv[0])) {
1485                         config_msg = "Unknown color name";
1486                         return ERR;
1487                 }
1488                 info = &line_info[index];
1489         }
1491         if (!set_color(&info->fg, argv[1]) ||
1492             !set_color(&info->bg, argv[2])) {
1493                 config_msg = "Unknown color";
1494                 return ERR;
1495         }
1497         if (argc == 4 && !set_attribute(&info->attr, argv[3])) {
1498                 config_msg = "Unknown attribute";
1499                 return ERR;
1500         }
1502         return OK;
1505 static int parse_bool(bool *opt, const char *arg)
1507         *opt = (!strcmp(arg, "1") || !strcmp(arg, "true") || !strcmp(arg, "yes"))
1508                 ? TRUE : FALSE;
1509         return OK;
1512 static int
1513 parse_string(char *opt, const char *arg, size_t optsize)
1515         int arglen = strlen(arg);
1517         switch (arg[0]) {
1518         case '\"':
1519         case '\'':
1520                 if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1521                         config_msg = "Unmatched quotation";
1522                         return ERR;
1523                 }
1524                 arg += 1; arglen -= 2;
1525         default:
1526                 string_ncopy_do(opt, optsize, arg, arglen);
1527                 return OK;
1528         }
1531 /* Wants: name = value */
1532 static int
1533 option_set_command(int argc, const char *argv[])
1535         if (argc != 3) {
1536                 config_msg = "Wrong number of arguments given to set command";
1537                 return ERR;
1538         }
1540         if (strcmp(argv[1], "=")) {
1541                 config_msg = "No value assigned";
1542                 return ERR;
1543         }
1545         if (!strcmp(argv[0], "show-author"))
1546                 return parse_bool(&opt_author, argv[2]);
1548         if (!strcmp(argv[0], "show-date"))
1549                 return parse_bool(&opt_date, argv[2]);
1551         if (!strcmp(argv[0], "show-rev-graph"))
1552                 return parse_bool(&opt_rev_graph, argv[2]);
1554         if (!strcmp(argv[0], "show-refs"))
1555                 return parse_bool(&opt_show_refs, argv[2]);
1557         if (!strcmp(argv[0], "show-line-numbers"))
1558                 return parse_bool(&opt_line_number, argv[2]);
1560         if (!strcmp(argv[0], "line-graphics"))
1561                 return parse_bool(&opt_line_graphics, argv[2]);
1563         if (!strcmp(argv[0], "line-number-interval"))
1564                 return parse_int(&opt_num_interval, argv[2], 1, 1024);
1566         if (!strcmp(argv[0], "author-width"))
1567                 return parse_int(&opt_author_cols, argv[2], 0, 1024);
1569         if (!strcmp(argv[0], "horizontal-scroll"))
1570                 return parse_step(&opt_hscroll, argv[2]);
1572         if (!strcmp(argv[0], "tab-size"))
1573                 return parse_int(&opt_tab_size, argv[2], 1, 1024);
1575         if (!strcmp(argv[0], "commit-encoding"))
1576                 return parse_string(opt_encoding, argv[2], sizeof(opt_encoding));
1578         config_msg = "Unknown variable name";
1579         return ERR;
1582 /* Wants: mode request key */
1583 static int
1584 option_bind_command(int argc, const char *argv[])
1586         enum request request;
1587         int keymap;
1588         int key;
1590         if (argc < 3) {
1591                 config_msg = "Wrong number of arguments given to bind command";
1592                 return ERR;
1593         }
1595         if (set_keymap(&keymap, argv[0]) == ERR) {
1596                 config_msg = "Unknown key map";
1597                 return ERR;
1598         }
1600         key = get_key_value(argv[1]);
1601         if (key == ERR) {
1602                 config_msg = "Unknown key";
1603                 return ERR;
1604         }
1606         request = get_request(argv[2]);
1607         if (request == REQ_NONE) {
1608                 static const struct enum_map obsolete[] = {
1609                         ENUM_MAP("cherry-pick",         REQ_NONE),
1610                         ENUM_MAP("screen-resize",       REQ_NONE),
1611                         ENUM_MAP("tree-parent",         REQ_PARENT),
1612                 };
1613                 int alias;
1615                 if (map_enum(&alias, obsolete, argv[2])) {
1616                         if (alias != REQ_NONE)
1617                                 add_keybinding(keymap, alias, key);
1618                         config_msg = "Obsolete request name";
1619                         return ERR;
1620                 }
1621         }
1622         if (request == REQ_NONE && *argv[2]++ == '!')
1623                 request = add_run_request(keymap, key, argc - 2, argv + 2);
1624         if (request == REQ_NONE) {
1625                 config_msg = "Unknown request name";
1626                 return ERR;
1627         }
1629         add_keybinding(keymap, request, key);
1631         return OK;
1634 static int
1635 set_option(const char *opt, char *value)
1637         const char *argv[SIZEOF_ARG];
1638         int argc = 0;
1640         if (!argv_from_string(argv, &argc, value)) {
1641                 config_msg = "Too many option arguments";
1642                 return ERR;
1643         }
1645         if (!strcmp(opt, "color"))
1646                 return option_color_command(argc, argv);
1648         if (!strcmp(opt, "set"))
1649                 return option_set_command(argc, argv);
1651         if (!strcmp(opt, "bind"))
1652                 return option_bind_command(argc, argv);
1654         config_msg = "Unknown option command";
1655         return ERR;
1658 static int
1659 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
1661         int status = OK;
1663         config_lineno++;
1664         config_msg = "Internal error";
1666         /* Check for comment markers, since read_properties() will
1667          * only ensure opt and value are split at first " \t". */
1668         optlen = strcspn(opt, "#");
1669         if (optlen == 0)
1670                 return OK;
1672         if (opt[optlen] != 0) {
1673                 config_msg = "No option value";
1674                 status = ERR;
1676         }  else {
1677                 /* Look for comment endings in the value. */
1678                 size_t len = strcspn(value, "#");
1680                 if (len < valuelen) {
1681                         valuelen = len;
1682                         value[valuelen] = 0;
1683                 }
1685                 status = set_option(opt, value);
1686         }
1688         if (status == ERR) {
1689                 warn("Error on line %d, near '%.*s': %s",
1690                      config_lineno, (int) optlen, opt, config_msg);
1691                 config_errors = TRUE;
1692         }
1694         /* Always keep going if errors are encountered. */
1695         return OK;
1698 static void
1699 load_option_file(const char *path)
1701         struct io io = {};
1703         /* It's OK that the file doesn't exist. */
1704         if (!io_open(&io, path))
1705                 return;
1707         config_lineno = 0;
1708         config_errors = FALSE;
1710         if (io_load(&io, " \t", read_option) == ERR ||
1711             config_errors == TRUE)
1712                 warn("Errors while loading %s.", path);
1715 static int
1716 load_options(void)
1718         const char *home = getenv("HOME");
1719         const char *tigrc_user = getenv("TIGRC_USER");
1720         const char *tigrc_system = getenv("TIGRC_SYSTEM");
1721         char buf[SIZEOF_STR];
1723         add_builtin_run_requests();
1725         if (!tigrc_system)
1726                 tigrc_system = SYSCONFDIR "/tigrc";
1727         load_option_file(tigrc_system);
1729         if (!tigrc_user) {
1730                 if (!home || !string_format(buf, "%s/.tigrc", home))
1731                         return ERR;
1732                 tigrc_user = buf;
1733         }
1734         load_option_file(tigrc_user);
1736         return OK;
1740 /*
1741  * The viewer
1742  */
1744 struct view;
1745 struct view_ops;
1747 /* The display array of active views and the index of the current view. */
1748 static struct view *display[2];
1749 static unsigned int current_view;
1751 #define foreach_displayed_view(view, i) \
1752         for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1754 #define displayed_views()       (display[1] != NULL ? 2 : 1)
1756 /* Current head and commit ID */
1757 static char ref_blob[SIZEOF_REF]        = "";
1758 static char ref_commit[SIZEOF_REF]      = "HEAD";
1759 static char ref_head[SIZEOF_REF]        = "HEAD";
1761 struct view {
1762         const char *name;       /* View name */
1763         const char *cmd_env;    /* Command line set via environment */
1764         const char *id;         /* Points to either of ref_{head,commit,blob} */
1766         struct view_ops *ops;   /* View operations */
1768         enum keymap keymap;     /* What keymap does this view have */
1769         bool git_dir;           /* Whether the view requires a git directory. */
1771         char ref[SIZEOF_REF];   /* Hovered commit reference */
1772         char vid[SIZEOF_REF];   /* View ID. Set to id member when updating. */
1774         int height, width;      /* The width and height of the main window */
1775         WINDOW *win;            /* The main window */
1776         WINDOW *title;          /* The title window living below the main window */
1778         /* Navigation */
1779         unsigned long offset;   /* Offset of the window top */
1780         unsigned long yoffset;  /* Offset from the window side. */
1781         unsigned long lineno;   /* Current line number */
1782         unsigned long p_offset; /* Previous offset of the window top */
1783         unsigned long p_yoffset;/* Previous offset from the window side */
1784         unsigned long p_lineno; /* Previous current line number */
1785         bool p_restore;         /* Should the previous position be restored. */
1787         /* Searching */
1788         char grep[SIZEOF_STR];  /* Search string */
1789         regex_t *regex;         /* Pre-compiled regexp */
1791         /* If non-NULL, points to the view that opened this view. If this view
1792          * is closed tig will switch back to the parent view. */
1793         struct view *parent;
1795         /* Buffering */
1796         size_t lines;           /* Total number of lines */
1797         struct line *line;      /* Line index */
1798         unsigned int digits;    /* Number of digits in the lines member. */
1800         /* Drawing */
1801         struct line *curline;   /* Line currently being drawn. */
1802         enum line_type curtype; /* Attribute currently used for drawing. */
1803         unsigned long col;      /* Column when drawing. */
1804         bool has_scrolled;      /* View was scrolled. */
1806         /* Loading */
1807         struct io io;
1808         struct io *pipe;
1809         time_t start_time;
1810         time_t update_secs;
1811 };
1813 struct view_ops {
1814         /* What type of content being displayed. Used in the title bar. */
1815         const char *type;
1816         /* Default command arguments. */
1817         const char **argv;
1818         /* Open and reads in all view content. */
1819         bool (*open)(struct view *view);
1820         /* Read one line; updates view->line. */
1821         bool (*read)(struct view *view, char *data);
1822         /* Draw one line; @lineno must be < view->height. */
1823         bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
1824         /* Depending on view handle a special requests. */
1825         enum request (*request)(struct view *view, enum request request, struct line *line);
1826         /* Search for regexp in a line. */
1827         bool (*grep)(struct view *view, struct line *line);
1828         /* Select line */
1829         void (*select)(struct view *view, struct line *line);
1830 };
1832 static struct view_ops blame_ops;
1833 static struct view_ops blob_ops;
1834 static struct view_ops diff_ops;
1835 static struct view_ops help_ops;
1836 static struct view_ops log_ops;
1837 static struct view_ops main_ops;
1838 static struct view_ops pager_ops;
1839 static struct view_ops stage_ops;
1840 static struct view_ops status_ops;
1841 static struct view_ops tree_ops;
1843 #define VIEW_STR(name, env, ref, ops, map, git) \
1844         { name, #env, ref, ops, map, git }
1846 #define VIEW_(id, name, ops, git, ref) \
1847         VIEW_STR(name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
1850 static struct view views[] = {
1851         VIEW_(MAIN,   "main",   &main_ops,   TRUE,  ref_head),
1852         VIEW_(DIFF,   "diff",   &diff_ops,   TRUE,  ref_commit),
1853         VIEW_(LOG,    "log",    &log_ops,    TRUE,  ref_head),
1854         VIEW_(TREE,   "tree",   &tree_ops,   TRUE,  ref_commit),
1855         VIEW_(BLOB,   "blob",   &blob_ops,   TRUE,  ref_blob),
1856         VIEW_(BLAME,  "blame",  &blame_ops,  TRUE,  ref_commit),
1857         VIEW_(HELP,   "help",   &help_ops,   FALSE, ""),
1858         VIEW_(PAGER,  "pager",  &pager_ops,  FALSE, "stdin"),
1859         VIEW_(STATUS, "status", &status_ops, TRUE,  ""),
1860         VIEW_(STAGE,  "stage",  &stage_ops,  TRUE,  ""),
1861 };
1863 #define VIEW(req)       (&views[(req) - REQ_OFFSET - 1])
1864 #define VIEW_REQ(view)  ((view) - views + REQ_OFFSET + 1)
1866 #define foreach_view(view, i) \
1867         for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
1869 #define view_is_displayed(view) \
1870         (view == display[0] || view == display[1])
1873 enum line_graphic {
1874         LINE_GRAPHIC_VLINE
1875 };
1877 static chtype line_graphics[] = {
1878         /* LINE_GRAPHIC_VLINE: */ '|'
1879 };
1881 static inline void
1882 set_view_attr(struct view *view, enum line_type type)
1884         if (!view->curline->selected && view->curtype != type) {
1885                 wattrset(view->win, get_line_attr(type));
1886                 wchgat(view->win, -1, 0, type, NULL);
1887                 view->curtype = type;
1888         }
1891 static int
1892 draw_chars(struct view *view, enum line_type type, const char *string,
1893            int max_len, bool use_tilde)
1895         int len = 0;
1896         int col = 0;
1897         int trimmed = FALSE;
1898         size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
1900         if (max_len <= 0)
1901                 return 0;
1903         if (opt_utf8) {
1904                 len = utf8_length(&string, skip, &col, max_len, &trimmed, use_tilde);
1905         } else {
1906                 col = len = strlen(string);
1907                 if (len > max_len) {
1908                         if (use_tilde) {
1909                                 max_len -= 1;
1910                         }
1911                         col = len = max_len;
1912                         trimmed = TRUE;
1913                 }
1914         }
1916         set_view_attr(view, type);
1917         if (len > 0)
1918                 waddnstr(view->win, string, len);
1919         if (trimmed && use_tilde) {
1920                 set_view_attr(view, LINE_DELIMITER);
1921                 waddch(view->win, '~');
1922                 col++;
1923         }
1925         return col;
1928 static int
1929 draw_space(struct view *view, enum line_type type, int max, int spaces)
1931         static char space[] = "                    ";
1932         int col = 0;
1934         spaces = MIN(max, spaces);
1936         while (spaces > 0) {
1937                 int len = MIN(spaces, sizeof(space) - 1);
1939                 col += draw_chars(view, type, space, len, FALSE);
1940                 spaces -= len;
1941         }
1943         return col;
1946 static bool
1947 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
1949         view->col += draw_chars(view, type, string, view->width + view->yoffset - view->col, trim);
1950         return view->width + view->yoffset <= view->col;
1953 static bool
1954 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
1956         size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
1957         int max = view->width + view->yoffset - view->col;
1958         int i;
1960         if (max < size)
1961                 size = max;
1963         set_view_attr(view, type);
1964         /* Using waddch() instead of waddnstr() ensures that
1965          * they'll be rendered correctly for the cursor line. */
1966         for (i = skip; i < size; i++)
1967                 waddch(view->win, graphic[i]);
1969         view->col += size;
1970         if (size < max && skip <= size)
1971                 waddch(view->win, ' ');
1972         view->col++;
1974         return view->width + view->yoffset <= view->col;
1977 static bool
1978 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
1980         int max = MIN(view->width + view->yoffset - view->col, len);
1981         int col;
1983         if (text)
1984                 col = draw_chars(view, type, text, max - 1, trim);
1985         else
1986                 col = draw_space(view, type, max - 1, max - 1);
1988         view->col += col;
1989         view->col += draw_space(view, LINE_DEFAULT, max - col, max - col);
1990         return view->width + view->yoffset <= view->col;
1993 static bool
1994 draw_date(struct view *view, time_t *time)
1996         const char *date = mkdate(time);
1998         return draw_field(view, LINE_DATE, date, DATE_COLS, FALSE);
2001 static bool
2002 draw_author(struct view *view, const char *author)
2004         bool trim = opt_author_cols == 0 || opt_author_cols > 5 || !author;
2006         if (!trim) {
2007                 static char initials[10];
2008                 size_t pos;
2010 #define is_initial_sep(c) (isspace(c) || ispunct(c) || (c) == '@')
2012                 memset(initials, 0, sizeof(initials));
2013                 for (pos = 0; *author && pos < opt_author_cols - 1; author++, pos++) {
2014                         while (is_initial_sep(*author))
2015                                 author++;
2016                         strncpy(&initials[pos], author, sizeof(initials) - 1 - pos);
2017                         while (*author && !is_initial_sep(author[1]))
2018                                 author++;
2019                 }
2021                 author = initials;
2022         }
2024         return draw_field(view, LINE_AUTHOR, author, opt_author_cols, trim);
2027 static bool
2028 draw_mode(struct view *view, mode_t mode)
2030         const char *str;
2032         if (S_ISDIR(mode))
2033                 str = "drwxr-xr-x";
2034         else if (S_ISLNK(mode))
2035                 str = "lrwxrwxrwx";
2036         else if (S_ISGITLINK(mode))
2037                 str = "m---------";
2038         else if (S_ISREG(mode) && mode & S_IXUSR)
2039                 str = "-rwxr-xr-x";
2040         else if (S_ISREG(mode))
2041                 str = "-rw-r--r--";
2042         else
2043                 str = "----------";
2045         return draw_field(view, LINE_MODE, str, STRING_SIZE("-rw-r--r-- "), FALSE);
2048 static bool
2049 draw_lineno(struct view *view, unsigned int lineno)
2051         char number[10];
2052         int digits3 = view->digits < 3 ? 3 : view->digits;
2053         int max = MIN(view->width + view->yoffset - view->col, digits3);
2054         char *text = NULL;
2056         lineno += view->offset + 1;
2057         if (lineno == 1 || (lineno % opt_num_interval) == 0) {
2058                 static char fmt[] = "%1ld";
2060                 fmt[1] = '0' + (view->digits <= 9 ? digits3 : 1);
2061                 if (string_format(number, fmt, lineno))
2062                         text = number;
2063         }
2064         if (text)
2065                 view->col += draw_chars(view, LINE_LINE_NUMBER, text, max, TRUE);
2066         else
2067                 view->col += draw_space(view, LINE_LINE_NUMBER, max, digits3);
2068         return draw_graphic(view, LINE_DEFAULT, &line_graphics[LINE_GRAPHIC_VLINE], 1);
2071 static bool
2072 draw_view_line(struct view *view, unsigned int lineno)
2074         struct line *line;
2075         bool selected = (view->offset + lineno == view->lineno);
2077         assert(view_is_displayed(view));
2079         if (view->offset + lineno >= view->lines)
2080                 return FALSE;
2082         line = &view->line[view->offset + lineno];
2084         wmove(view->win, lineno, 0);
2085         if (line->cleareol)
2086                 wclrtoeol(view->win);
2087         view->col = 0;
2088         view->curline = line;
2089         view->curtype = LINE_NONE;
2090         line->selected = FALSE;
2091         line->dirty = line->cleareol = 0;
2093         if (selected) {
2094                 set_view_attr(view, LINE_CURSOR);
2095                 line->selected = TRUE;
2096                 view->ops->select(view, line);
2097         }
2099         return view->ops->draw(view, line, lineno);
2102 static void
2103 redraw_view_dirty(struct view *view)
2105         bool dirty = FALSE;
2106         int lineno;
2108         for (lineno = 0; lineno < view->height; lineno++) {
2109                 if (view->offset + lineno >= view->lines)
2110                         break;
2111                 if (!view->line[view->offset + lineno].dirty)
2112                         continue;
2113                 dirty = TRUE;
2114                 if (!draw_view_line(view, lineno))
2115                         break;
2116         }
2118         if (!dirty)
2119                 return;
2120         wnoutrefresh(view->win);
2123 static void
2124 redraw_view_from(struct view *view, int lineno)
2126         assert(0 <= lineno && lineno < view->height);
2128         for (; lineno < view->height; lineno++) {
2129                 if (!draw_view_line(view, lineno))
2130                         break;
2131         }
2133         wnoutrefresh(view->win);
2136 static void
2137 redraw_view(struct view *view)
2139         werase(view->win);
2140         redraw_view_from(view, 0);
2144 static void
2145 update_view_title(struct view *view)
2147         char buf[SIZEOF_STR];
2148         char state[SIZEOF_STR];
2149         size_t bufpos = 0, statelen = 0;
2151         assert(view_is_displayed(view));
2153         if (view != VIEW(REQ_VIEW_STATUS) && view->lines) {
2154                 unsigned int view_lines = view->offset + view->height;
2155                 unsigned int lines = view->lines
2156                                    ? MIN(view_lines, view->lines) * 100 / view->lines
2157                                    : 0;
2159                 string_format_from(state, &statelen, " - %s %d of %d (%d%%)",
2160                                    view->ops->type,
2161                                    view->lineno + 1,
2162                                    view->lines,
2163                                    lines);
2165         }
2167         if (view->pipe) {
2168                 time_t secs = time(NULL) - view->start_time;
2170                 /* Three git seconds are a long time ... */
2171                 if (secs > 2)
2172                         string_format_from(state, &statelen, " loading %lds", secs);
2173         }
2175         string_format_from(buf, &bufpos, "[%s]", view->name);
2176         if (*view->ref && bufpos < view->width) {
2177                 size_t refsize = strlen(view->ref);
2178                 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2180                 if (minsize < view->width)
2181                         refsize = view->width - minsize + 7;
2182                 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2183         }
2185         if (statelen && bufpos < view->width) {
2186                 string_format_from(buf, &bufpos, "%s", state);
2187         }
2189         if (view == display[current_view])
2190                 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2191         else
2192                 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2194         mvwaddnstr(view->title, 0, 0, buf, bufpos);
2195         wclrtoeol(view->title);
2196         wnoutrefresh(view->title);
2199 static void
2200 resize_display(void)
2202         int offset, i;
2203         struct view *base = display[0];
2204         struct view *view = display[1] ? display[1] : display[0];
2206         /* Setup window dimensions */
2208         getmaxyx(stdscr, base->height, base->width);
2210         /* Make room for the status window. */
2211         base->height -= 1;
2213         if (view != base) {
2214                 /* Horizontal split. */
2215                 view->width   = base->width;
2216                 view->height  = SCALE_SPLIT_VIEW(base->height);
2217                 base->height -= view->height;
2219                 /* Make room for the title bar. */
2220                 view->height -= 1;
2221         }
2223         /* Make room for the title bar. */
2224         base->height -= 1;
2226         offset = 0;
2228         foreach_displayed_view (view, i) {
2229                 if (!view->win) {
2230                         view->win = newwin(view->height, 0, offset, 0);
2231                         if (!view->win)
2232                                 die("Failed to create %s view", view->name);
2234                         scrollok(view->win, FALSE);
2236                         view->title = newwin(1, 0, offset + view->height, 0);
2237                         if (!view->title)
2238                                 die("Failed to create title window");
2240                 } else {
2241                         wresize(view->win, view->height, view->width);
2242                         mvwin(view->win,   offset, 0);
2243                         mvwin(view->title, offset + view->height, 0);
2244                 }
2246                 offset += view->height + 1;
2247         }
2250 static void
2251 redraw_display(bool clear)
2253         struct view *view;
2254         int i;
2256         foreach_displayed_view (view, i) {
2257                 if (clear)
2258                         wclear(view->win);
2259                 redraw_view(view);
2260                 update_view_title(view);
2261         }
2264 static void
2265 toggle_view_option(bool *option, const char *help)
2267         *option = !*option;
2268         redraw_display(FALSE);
2269         report("%sabling %s", *option ? "En" : "Dis", help);
2272 static void
2273 maximize_view(struct view *view)
2275         memset(display, 0, sizeof(display));
2276         current_view = 0;
2277         display[current_view] = view;
2278         resize_display();
2279         redraw_display(FALSE);
2280         report("");
2284 /*
2285  * Navigation
2286  */
2288 static bool
2289 goto_view_line(struct view *view, unsigned long offset, unsigned long lineno)
2291         if (lineno >= view->lines)
2292                 lineno = view->lines > 0 ? view->lines - 1 : 0;
2294         if (offset > lineno || offset + view->height <= lineno) {
2295                 unsigned long half = view->height / 2;
2297                 if (lineno > half)
2298                         offset = lineno - half;
2299                 else
2300                         offset = 0;
2301         }
2303         if (offset != view->offset || lineno != view->lineno) {
2304                 view->offset = offset;
2305                 view->lineno = lineno;
2306                 return TRUE;
2307         }
2309         return FALSE;
2312 static int
2313 apply_step(double step, int value)
2315         if (step >= 1)
2316                 return (int) step;
2317         value *= step + 0.01;
2318         return value ? value : 1;
2321 /* Scrolling backend */
2322 static void
2323 do_scroll_view(struct view *view, int lines)
2325         bool redraw_current_line = FALSE;
2327         /* The rendering expects the new offset. */
2328         view->offset += lines;
2330         assert(0 <= view->offset && view->offset < view->lines);
2331         assert(lines);
2333         /* Move current line into the view. */
2334         if (view->lineno < view->offset) {
2335                 view->lineno = view->offset;
2336                 redraw_current_line = TRUE;
2337         } else if (view->lineno >= view->offset + view->height) {
2338                 view->lineno = view->offset + view->height - 1;
2339                 redraw_current_line = TRUE;
2340         }
2342         assert(view->offset <= view->lineno && view->lineno < view->lines);
2344         /* Redraw the whole screen if scrolling is pointless. */
2345         if (view->height < ABS(lines)) {
2346                 redraw_view(view);
2348         } else {
2349                 int line = lines > 0 ? view->height - lines : 0;
2350                 int end = line + ABS(lines);
2352                 scrollok(view->win, TRUE);
2353                 wscrl(view->win, lines);
2354                 scrollok(view->win, FALSE);
2356                 while (line < end && draw_view_line(view, line))
2357                         line++;
2359                 if (redraw_current_line)
2360                         draw_view_line(view, view->lineno - view->offset);
2361                 wnoutrefresh(view->win);
2362         }
2364         view->has_scrolled = TRUE;
2365         report("");
2368 /* Scroll frontend */
2369 static void
2370 scroll_view(struct view *view, enum request request)
2372         int lines = 1;
2374         assert(view_is_displayed(view));
2376         switch (request) {
2377         case REQ_SCROLL_LEFT:
2378                 if (view->yoffset == 0) {
2379                         report("Cannot scroll beyond the first column");
2380                         return;
2381                 }
2382                 if (view->yoffset <= apply_step(opt_hscroll, view->width))
2383                         view->yoffset = 0;
2384                 else
2385                         view->yoffset -= apply_step(opt_hscroll, view->width);
2386                 redraw_view_from(view, 0);
2387                 report("");
2388                 return;
2389         case REQ_SCROLL_RIGHT:
2390                 view->yoffset += apply_step(opt_hscroll, view->width);
2391                 redraw_view(view);
2392                 report("");
2393                 return;
2394         case REQ_SCROLL_PAGE_DOWN:
2395                 lines = view->height;
2396         case REQ_SCROLL_LINE_DOWN:
2397                 if (view->offset + lines > view->lines)
2398                         lines = view->lines - view->offset;
2400                 if (lines == 0 || view->offset + view->height >= view->lines) {
2401                         report("Cannot scroll beyond the last line");
2402                         return;
2403                 }
2404                 break;
2406         case REQ_SCROLL_PAGE_UP:
2407                 lines = view->height;
2408         case REQ_SCROLL_LINE_UP:
2409                 if (lines > view->offset)
2410                         lines = view->offset;
2412                 if (lines == 0) {
2413                         report("Cannot scroll beyond the first line");
2414                         return;
2415                 }
2417                 lines = -lines;
2418                 break;
2420         default:
2421                 die("request %d not handled in switch", request);
2422         }
2424         do_scroll_view(view, lines);
2427 /* Cursor moving */
2428 static void
2429 move_view(struct view *view, enum request request)
2431         int scroll_steps = 0;
2432         int steps;
2434         switch (request) {
2435         case REQ_MOVE_FIRST_LINE:
2436                 steps = -view->lineno;
2437                 break;
2439         case REQ_MOVE_LAST_LINE:
2440                 steps = view->lines - view->lineno - 1;
2441                 break;
2443         case REQ_MOVE_PAGE_UP:
2444                 steps = view->height > view->lineno
2445                       ? -view->lineno : -view->height;
2446                 break;
2448         case REQ_MOVE_PAGE_DOWN:
2449                 steps = view->lineno + view->height >= view->lines
2450                       ? view->lines - view->lineno - 1 : view->height;
2451                 break;
2453         case REQ_MOVE_UP:
2454                 steps = -1;
2455                 break;
2457         case REQ_MOVE_DOWN:
2458                 steps = 1;
2459                 break;
2461         default:
2462                 die("request %d not handled in switch", request);
2463         }
2465         if (steps <= 0 && view->lineno == 0) {
2466                 report("Cannot move beyond the first line");
2467                 return;
2469         } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2470                 report("Cannot move beyond the last line");
2471                 return;
2472         }
2474         /* Move the current line */
2475         view->lineno += steps;
2476         assert(0 <= view->lineno && view->lineno < view->lines);
2478         /* Check whether the view needs to be scrolled */
2479         if (view->lineno < view->offset ||
2480             view->lineno >= view->offset + view->height) {
2481                 scroll_steps = steps;
2482                 if (steps < 0 && -steps > view->offset) {
2483                         scroll_steps = -view->offset;
2485                 } else if (steps > 0) {
2486                         if (view->lineno == view->lines - 1 &&
2487                             view->lines > view->height) {
2488                                 scroll_steps = view->lines - view->offset - 1;
2489                                 if (scroll_steps >= view->height)
2490                                         scroll_steps -= view->height - 1;
2491                         }
2492                 }
2493         }
2495         if (!view_is_displayed(view)) {
2496                 view->offset += scroll_steps;
2497                 assert(0 <= view->offset && view->offset < view->lines);
2498                 view->ops->select(view, &view->line[view->lineno]);
2499                 return;
2500         }
2502         /* Repaint the old "current" line if we be scrolling */
2503         if (ABS(steps) < view->height)
2504                 draw_view_line(view, view->lineno - steps - view->offset);
2506         if (scroll_steps) {
2507                 do_scroll_view(view, scroll_steps);
2508                 return;
2509         }
2511         /* Draw the current line */
2512         draw_view_line(view, view->lineno - view->offset);
2514         wnoutrefresh(view->win);
2515         report("");
2519 /*
2520  * Searching
2521  */
2523 static void search_view(struct view *view, enum request request);
2525 static bool
2526 grep_text(struct view *view, const char *text[])
2528         regmatch_t pmatch;
2529         size_t i;
2531         for (i = 0; text[i]; i++)
2532                 if (*text[i] &&
2533                     regexec(view->regex, text[i], 1, &pmatch, 0) != REG_NOMATCH)
2534                         return TRUE;
2535         return FALSE;
2538 static void
2539 select_view_line(struct view *view, unsigned long lineno)
2541         unsigned long old_lineno = view->lineno;
2542         unsigned long old_offset = view->offset;
2544         if (goto_view_line(view, view->offset, lineno)) {
2545                 if (view_is_displayed(view)) {
2546                         if (old_offset != view->offset) {
2547                                 redraw_view(view);
2548                         } else {
2549                                 draw_view_line(view, old_lineno - view->offset);
2550                                 draw_view_line(view, view->lineno - view->offset);
2551                                 wnoutrefresh(view->win);
2552                         }
2553                 } else {
2554                         view->ops->select(view, &view->line[view->lineno]);
2555                 }
2556         }
2559 static void
2560 find_next(struct view *view, enum request request)
2562         unsigned long lineno = view->lineno;
2563         int direction;
2565         if (!*view->grep) {
2566                 if (!*opt_search)
2567                         report("No previous search");
2568                 else
2569                         search_view(view, request);
2570                 return;
2571         }
2573         switch (request) {
2574         case REQ_SEARCH:
2575         case REQ_FIND_NEXT:
2576                 direction = 1;
2577                 break;
2579         case REQ_SEARCH_BACK:
2580         case REQ_FIND_PREV:
2581                 direction = -1;
2582                 break;
2584         default:
2585                 return;
2586         }
2588         if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
2589                 lineno += direction;
2591         /* Note, lineno is unsigned long so will wrap around in which case it
2592          * will become bigger than view->lines. */
2593         for (; lineno < view->lines; lineno += direction) {
2594                 if (view->ops->grep(view, &view->line[lineno])) {
2595                         select_view_line(view, lineno);
2596                         report("Line %ld matches '%s'", lineno + 1, view->grep);
2597                         return;
2598                 }
2599         }
2601         report("No match found for '%s'", view->grep);
2604 static void
2605 search_view(struct view *view, enum request request)
2607         int regex_err;
2609         if (view->regex) {
2610                 regfree(view->regex);
2611                 *view->grep = 0;
2612         } else {
2613                 view->regex = calloc(1, sizeof(*view->regex));
2614                 if (!view->regex)
2615                         return;
2616         }
2618         regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
2619         if (regex_err != 0) {
2620                 char buf[SIZEOF_STR] = "unknown error";
2622                 regerror(regex_err, view->regex, buf, sizeof(buf));
2623                 report("Search failed: %s", buf);
2624                 return;
2625         }
2627         string_copy(view->grep, opt_search);
2629         find_next(view, request);
2632 /*
2633  * Incremental updating
2634  */
2636 static void
2637 reset_view(struct view *view)
2639         int i;
2641         for (i = 0; i < view->lines; i++)
2642                 free(view->line[i].data);
2643         free(view->line);
2645         view->p_offset = view->offset;
2646         view->p_yoffset = view->yoffset;
2647         view->p_lineno = view->lineno;
2649         view->line = NULL;
2650         view->offset = 0;
2651         view->yoffset = 0;
2652         view->lines  = 0;
2653         view->lineno = 0;
2654         view->vid[0] = 0;
2655         view->update_secs = 0;
2658 static void
2659 free_argv(const char *argv[])
2661         int argc;
2663         for (argc = 0; argv[argc]; argc++)
2664                 free((void *) argv[argc]);
2667 static bool
2668 format_argv(const char *dst_argv[], const char *src_argv[], enum format_flags flags)
2670         char buf[SIZEOF_STR];
2671         int argc;
2672         bool noreplace = flags == FORMAT_NONE;
2674         free_argv(dst_argv);
2676         for (argc = 0; src_argv[argc]; argc++) {
2677                 const char *arg = src_argv[argc];
2678                 size_t bufpos = 0;
2680                 while (arg) {
2681                         char *next = strstr(arg, "%(");
2682                         int len = next - arg;
2683                         const char *value;
2685                         if (!next || noreplace) {
2686                                 if (flags == FORMAT_DASH && !strcmp(arg, "--"))
2687                                         noreplace = TRUE;
2688                                 len = strlen(arg);
2689                                 value = "";
2691                         } else if (!prefixcmp(next, "%(directory)")) {
2692                                 value = opt_path;
2694                         } else if (!prefixcmp(next, "%(file)")) {
2695                                 value = opt_file;
2697                         } else if (!prefixcmp(next, "%(ref)")) {
2698                                 value = *opt_ref ? opt_ref : "HEAD";
2700                         } else if (!prefixcmp(next, "%(head)")) {
2701                                 value = ref_head;
2703                         } else if (!prefixcmp(next, "%(commit)")) {
2704                                 value = ref_commit;
2706                         } else if (!prefixcmp(next, "%(blob)")) {
2707                                 value = ref_blob;
2709                         } else {
2710                                 report("Unknown replacement: `%s`", next);
2711                                 return FALSE;
2712                         }
2714                         if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
2715                                 return FALSE;
2717                         arg = next && !noreplace ? strchr(next, ')') + 1 : NULL;
2718                 }
2720                 dst_argv[argc] = strdup(buf);
2721                 if (!dst_argv[argc])
2722                         break;
2723         }
2725         dst_argv[argc] = NULL;
2727         return src_argv[argc] == NULL;
2730 static bool
2731 restore_view_position(struct view *view)
2733         if (!view->p_restore || (view->pipe && view->lines <= view->p_lineno))
2734                 return FALSE;
2736         /* Changing the view position cancels the restoring. */
2737         /* FIXME: Changing back to the first line is not detected. */
2738         if (view->offset != 0 || view->lineno != 0) {
2739                 view->p_restore = FALSE;
2740                 return FALSE;
2741         }
2743         if (goto_view_line(view, view->p_offset, view->p_lineno) &&
2744             view_is_displayed(view))
2745                 werase(view->win);
2747         view->yoffset = view->p_yoffset;
2748         view->p_restore = FALSE;
2750         return TRUE;
2753 static void
2754 end_update(struct view *view, bool force)
2756         if (!view->pipe)
2757                 return;
2758         while (!view->ops->read(view, NULL))
2759                 if (!force)
2760                         return;
2761         set_nonblocking_input(FALSE);
2762         if (force)
2763                 kill_io(view->pipe);
2764         done_io(view->pipe);
2765         view->pipe = NULL;
2768 static void
2769 setup_update(struct view *view, const char *vid)
2771         set_nonblocking_input(TRUE);
2772         reset_view(view);
2773         string_copy_rev(view->vid, vid);
2774         view->pipe = &view->io;
2775         view->start_time = time(NULL);
2778 static bool
2779 prepare_update(struct view *view, const char *argv[], const char *dir,
2780                enum format_flags flags)
2782         if (view->pipe)
2783                 end_update(view, TRUE);
2784         return init_io_rd(&view->io, argv, dir, flags);
2787 static bool
2788 prepare_update_file(struct view *view, const char *name)
2790         if (view->pipe)
2791                 end_update(view, TRUE);
2792         return io_open(&view->io, name);
2795 static bool
2796 begin_update(struct view *view, bool refresh)
2798         if (view->pipe)
2799                 end_update(view, TRUE);
2801         if (refresh) {
2802                 if (!start_io(&view->io))
2803                         return FALSE;
2805         } else {
2806                 if (view == VIEW(REQ_VIEW_TREE) && strcmp(view->vid, view->id))
2807                         opt_path[0] = 0;
2809                 if (!run_io_rd(&view->io, view->ops->argv, FORMAT_ALL))
2810                         return FALSE;
2812                 /* Put the current ref_* value to the view title ref
2813                  * member. This is needed by the blob view. Most other
2814                  * views sets it automatically after loading because the
2815                  * first line is a commit line. */
2816                 string_copy_rev(view->ref, view->id);
2817         }
2819         setup_update(view, view->id);
2821         return TRUE;
2824 static bool
2825 update_view(struct view *view)
2827         char out_buffer[BUFSIZ * 2];
2828         char *line;
2829         /* Clear the view and redraw everything since the tree sorting
2830          * might have rearranged things. */
2831         bool redraw = view->lines == 0;
2832         bool can_read = TRUE;
2834         if (!view->pipe)
2835                 return TRUE;
2837         if (!io_can_read(view->pipe)) {
2838                 if (view->lines == 0 && view_is_displayed(view)) {
2839                         time_t secs = time(NULL) - view->start_time;
2841                         if (secs > 1 && secs > view->update_secs) {
2842                                 if (view->update_secs == 0)
2843                                         redraw_view(view);
2844                                 update_view_title(view);
2845                                 view->update_secs = secs;
2846                         }
2847                 }
2848                 return TRUE;
2849         }
2851         for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
2852                 if (opt_iconv != ICONV_NONE) {
2853                         ICONV_CONST char *inbuf = line;
2854                         size_t inlen = strlen(line) + 1;
2856                         char *outbuf = out_buffer;
2857                         size_t outlen = sizeof(out_buffer);
2859                         size_t ret;
2861                         ret = iconv(opt_iconv, &inbuf, &inlen, &outbuf, &outlen);
2862                         if (ret != (size_t) -1)
2863                                 line = out_buffer;
2864                 }
2866                 if (!view->ops->read(view, line)) {
2867                         report("Allocation failure");
2868                         end_update(view, TRUE);
2869                         return FALSE;
2870                 }
2871         }
2873         {
2874                 unsigned long lines = view->lines;
2875                 int digits;
2877                 for (digits = 0; lines; digits++)
2878                         lines /= 10;
2880                 /* Keep the displayed view in sync with line number scaling. */
2881                 if (digits != view->digits) {
2882                         view->digits = digits;
2883                         if (opt_line_number || view == VIEW(REQ_VIEW_BLAME))
2884                                 redraw = TRUE;
2885                 }
2886         }
2888         if (io_error(view->pipe)) {
2889                 report("Failed to read: %s", io_strerror(view->pipe));
2890                 end_update(view, TRUE);
2892         } else if (io_eof(view->pipe)) {
2893                 report("");
2894                 end_update(view, FALSE);
2895         }
2897         if (restore_view_position(view))
2898                 redraw = TRUE;
2900         if (!view_is_displayed(view))
2901                 return TRUE;
2903         if (redraw)
2904                 redraw_view_from(view, 0);
2905         else
2906                 redraw_view_dirty(view);
2908         /* Update the title _after_ the redraw so that if the redraw picks up a
2909          * commit reference in view->ref it'll be available here. */
2910         update_view_title(view);
2911         return TRUE;
2914 DEFINE_ALLOCATOR(realloc_lines, struct line, 256)
2916 static struct line *
2917 add_line_data(struct view *view, void *data, enum line_type type)
2919         struct line *line;
2921         if (!realloc_lines(&view->line, view->lines, 1))
2922                 return NULL;
2924         line = &view->line[view->lines++];
2925         memset(line, 0, sizeof(*line));
2926         line->type = type;
2927         line->data = data;
2928         line->dirty = 1;
2930         return line;
2933 static struct line *
2934 add_line_text(struct view *view, const char *text, enum line_type type)
2936         char *data = text ? strdup(text) : NULL;
2938         return data ? add_line_data(view, data, type) : NULL;
2941 static struct line *
2942 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
2944         char buf[SIZEOF_STR];
2945         va_list args;
2947         va_start(args, fmt);
2948         if (vsnprintf(buf, sizeof(buf), fmt, args) >= sizeof(buf))
2949                 buf[0] = 0;
2950         va_end(args);
2952         return buf[0] ? add_line_text(view, buf, type) : NULL;
2955 /*
2956  * View opening
2957  */
2959 enum open_flags {
2960         OPEN_DEFAULT = 0,       /* Use default view switching. */
2961         OPEN_SPLIT = 1,         /* Split current view. */
2962         OPEN_RELOAD = 4,        /* Reload view even if it is the current. */
2963         OPEN_REFRESH = 16,      /* Refresh view using previous command. */
2964         OPEN_PREPARED = 32,     /* Open already prepared command. */
2965 };
2967 static void
2968 open_view(struct view *prev, enum request request, enum open_flags flags)
2970         bool split = !!(flags & OPEN_SPLIT);
2971         bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
2972         bool nomaximize = !!(flags & OPEN_REFRESH);
2973         struct view *view = VIEW(request);
2974         int nviews = displayed_views();
2975         struct view *base_view = display[0];
2977         if (view == prev && nviews == 1 && !reload) {
2978                 report("Already in %s view", view->name);
2979                 return;
2980         }
2982         if (view->git_dir && !opt_git_dir[0]) {
2983                 report("The %s view is disabled in pager view", view->name);
2984                 return;
2985         }
2987         if (split) {
2988                 display[1] = view;
2989                 current_view = 1;
2990         } else if (!nomaximize) {
2991                 /* Maximize the current view. */
2992                 memset(display, 0, sizeof(display));
2993                 current_view = 0;
2994                 display[current_view] = view;
2995         }
2997         /* Resize the view when switching between split- and full-screen,
2998          * or when switching between two different full-screen views. */
2999         if (nviews != displayed_views() ||
3000             (nviews == 1 && base_view != display[0]))
3001                 resize_display();
3003         if (view->ops->open) {
3004                 if (view->pipe)
3005                         end_update(view, TRUE);
3006                 if (!view->ops->open(view)) {
3007                         report("Failed to load %s view", view->name);
3008                         return;
3009                 }
3010                 restore_view_position(view);
3012         } else if ((reload || strcmp(view->vid, view->id)) &&
3013                    !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
3014                 report("Failed to load %s view", view->name);
3015                 return;
3016         }
3018         if (split && prev->lineno - prev->offset >= prev->height) {
3019                 /* Take the title line into account. */
3020                 int lines = prev->lineno - prev->offset - prev->height + 1;
3022                 /* Scroll the view that was split if the current line is
3023                  * outside the new limited view. */
3024                 do_scroll_view(prev, lines);
3025         }
3027         if (prev && view != prev) {
3028                 if (split) {
3029                         /* "Blur" the previous view. */
3030                         update_view_title(prev);
3031                 }
3033                 view->parent = prev;
3034         }
3036         if (view->pipe && view->lines == 0) {
3037                 /* Clear the old view and let the incremental updating refill
3038                  * the screen. */
3039                 werase(view->win);
3040                 view->p_restore = flags & (OPEN_RELOAD | OPEN_REFRESH);
3041                 report("");
3042         } else if (view_is_displayed(view)) {
3043                 redraw_view(view);
3044                 report("");
3045         }
3048 static void
3049 open_external_viewer(const char *argv[], const char *dir)
3051         def_prog_mode();           /* save current tty modes */
3052         endwin();                  /* restore original tty modes */
3053         run_io_fg(argv, dir);
3054         fprintf(stderr, "Press Enter to continue");
3055         getc(opt_tty);
3056         reset_prog_mode();
3057         redraw_display(TRUE);
3060 static void
3061 open_mergetool(const char *file)
3063         const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
3065         open_external_viewer(mergetool_argv, opt_cdup);
3068 static void
3069 open_editor(bool from_root, const char *file)
3071         const char *editor_argv[] = { "vi", file, NULL };
3072         const char *editor;
3074         editor = getenv("GIT_EDITOR");
3075         if (!editor && *opt_editor)
3076                 editor = opt_editor;
3077         if (!editor)
3078                 editor = getenv("VISUAL");
3079         if (!editor)
3080                 editor = getenv("EDITOR");
3081         if (!editor)
3082                 editor = "vi";
3084         editor_argv[0] = editor;
3085         open_external_viewer(editor_argv, from_root ? opt_cdup : NULL);
3088 static void
3089 open_run_request(enum request request)
3091         struct run_request *req = get_run_request(request);
3092         const char *argv[ARRAY_SIZE(req->argv)] = { NULL };
3094         if (!req) {
3095                 report("Unknown run request");
3096                 return;
3097         }
3099         if (format_argv(argv, req->argv, FORMAT_ALL))
3100                 open_external_viewer(argv, NULL);
3101         free_argv(argv);
3104 /*
3105  * User request switch noodle
3106  */
3108 static int
3109 view_driver(struct view *view, enum request request)
3111         int i;
3113         if (request == REQ_NONE)
3114                 return TRUE;
3116         if (request > REQ_NONE) {
3117                 open_run_request(request);
3118                 /* FIXME: When all views can refresh always do this. */
3119                 if (view == VIEW(REQ_VIEW_STATUS) ||
3120                     view == VIEW(REQ_VIEW_MAIN) ||
3121                     view == VIEW(REQ_VIEW_LOG) ||
3122                     view == VIEW(REQ_VIEW_STAGE))
3123                         request = REQ_REFRESH;
3124                 else
3125                         return TRUE;
3126         }
3128         if (view && view->lines) {
3129                 request = view->ops->request(view, request, &view->line[view->lineno]);
3130                 if (request == REQ_NONE)
3131                         return TRUE;
3132         }
3134         switch (request) {
3135         case REQ_MOVE_UP:
3136         case REQ_MOVE_DOWN:
3137         case REQ_MOVE_PAGE_UP:
3138         case REQ_MOVE_PAGE_DOWN:
3139         case REQ_MOVE_FIRST_LINE:
3140         case REQ_MOVE_LAST_LINE:
3141                 move_view(view, request);
3142                 break;
3144         case REQ_SCROLL_LEFT:
3145         case REQ_SCROLL_RIGHT:
3146         case REQ_SCROLL_LINE_DOWN:
3147         case REQ_SCROLL_LINE_UP:
3148         case REQ_SCROLL_PAGE_DOWN:
3149         case REQ_SCROLL_PAGE_UP:
3150                 scroll_view(view, request);
3151                 break;
3153         case REQ_VIEW_BLAME:
3154                 if (!opt_file[0]) {
3155                         report("No file chosen, press %s to open tree view",
3156                                get_key(REQ_VIEW_TREE));
3157                         break;
3158                 }
3159                 open_view(view, request, OPEN_DEFAULT);
3160                 break;
3162         case REQ_VIEW_BLOB:
3163                 if (!ref_blob[0]) {
3164                         report("No file chosen, press %s to open tree view",
3165                                get_key(REQ_VIEW_TREE));
3166                         break;
3167                 }
3168                 open_view(view, request, OPEN_DEFAULT);
3169                 break;
3171         case REQ_VIEW_PAGER:
3172                 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3173                         report("No pager content, press %s to run command from prompt",
3174                                get_key(REQ_PROMPT));
3175                         break;
3176                 }
3177                 open_view(view, request, OPEN_DEFAULT);
3178                 break;
3180         case REQ_VIEW_STAGE:
3181                 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3182                         report("No stage content, press %s to open the status view and choose file",
3183                                get_key(REQ_VIEW_STATUS));
3184                         break;
3185                 }
3186                 open_view(view, request, OPEN_DEFAULT);
3187                 break;
3189         case REQ_VIEW_STATUS:
3190                 if (opt_is_inside_work_tree == FALSE) {
3191                         report("The status view requires a working tree");
3192                         break;
3193                 }
3194                 open_view(view, request, OPEN_DEFAULT);
3195                 break;
3197         case REQ_VIEW_MAIN:
3198         case REQ_VIEW_DIFF:
3199         case REQ_VIEW_LOG:
3200         case REQ_VIEW_TREE:
3201         case REQ_VIEW_HELP:
3202                 open_view(view, request, OPEN_DEFAULT);
3203                 break;
3205         case REQ_NEXT:
3206         case REQ_PREVIOUS:
3207                 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3209                 if ((view == VIEW(REQ_VIEW_DIFF) &&
3210                      view->parent == VIEW(REQ_VIEW_MAIN)) ||
3211                    (view == VIEW(REQ_VIEW_DIFF) &&
3212                      view->parent == VIEW(REQ_VIEW_BLAME)) ||
3213                    (view == VIEW(REQ_VIEW_STAGE) &&
3214                      view->parent == VIEW(REQ_VIEW_STATUS)) ||
3215                    (view == VIEW(REQ_VIEW_BLOB) &&
3216                      view->parent == VIEW(REQ_VIEW_TREE))) {
3217                         int line;
3219                         view = view->parent;
3220                         line = view->lineno;
3221                         move_view(view, request);
3222                         if (view_is_displayed(view))
3223                                 update_view_title(view);
3224                         if (line != view->lineno)
3225                                 view->ops->request(view, REQ_ENTER,
3226                                                    &view->line[view->lineno]);
3228                 } else {
3229                         move_view(view, request);
3230                 }
3231                 break;
3233         case REQ_VIEW_NEXT:
3234         {
3235                 int nviews = displayed_views();
3236                 int next_view = (current_view + 1) % nviews;
3238                 if (next_view == current_view) {
3239                         report("Only one view is displayed");
3240                         break;
3241                 }
3243                 current_view = next_view;
3244                 /* Blur out the title of the previous view. */
3245                 update_view_title(view);
3246                 report("");
3247                 break;
3248         }
3249         case REQ_REFRESH:
3250                 report("Refreshing is not yet supported for the %s view", view->name);
3251                 break;
3253         case REQ_MAXIMIZE:
3254                 if (displayed_views() == 2)
3255                         maximize_view(view);
3256                 break;
3258         case REQ_TOGGLE_LINENO:
3259                 toggle_view_option(&opt_line_number, "line numbers");
3260                 break;
3262         case REQ_TOGGLE_DATE:
3263                 toggle_view_option(&opt_date, "date display");
3264                 break;
3266         case REQ_TOGGLE_AUTHOR:
3267                 toggle_view_option(&opt_author, "author display");
3268                 break;
3270         case REQ_TOGGLE_REV_GRAPH:
3271                 toggle_view_option(&opt_rev_graph, "revision graph display");
3272                 break;
3274         case REQ_TOGGLE_REFS:
3275                 toggle_view_option(&opt_show_refs, "reference display");
3276                 break;
3278         case REQ_SEARCH:
3279         case REQ_SEARCH_BACK:
3280                 search_view(view, request);
3281                 break;
3283         case REQ_FIND_NEXT:
3284         case REQ_FIND_PREV:
3285                 find_next(view, request);
3286                 break;
3288         case REQ_STOP_LOADING:
3289                 for (i = 0; i < ARRAY_SIZE(views); i++) {
3290                         view = &views[i];
3291                         if (view->pipe)
3292                                 report("Stopped loading the %s view", view->name),
3293                         end_update(view, TRUE);
3294                 }
3295                 break;
3297         case REQ_SHOW_VERSION:
3298                 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3299                 return TRUE;
3301         case REQ_SCREEN_REDRAW:
3302                 redraw_display(TRUE);
3303                 break;
3305         case REQ_EDIT:
3306                 report("Nothing to edit");
3307                 break;
3309         case REQ_ENTER:
3310                 report("Nothing to enter");
3311                 break;
3313         case REQ_VIEW_CLOSE:
3314                 /* XXX: Mark closed views by letting view->parent point to the
3315                  * view itself. Parents to closed view should never be
3316                  * followed. */
3317                 if (view->parent &&
3318                     view->parent->parent != view->parent) {
3319                         maximize_view(view->parent);
3320                         view->parent = view;
3321                         break;
3322                 }
3323                 /* Fall-through */
3324         case REQ_QUIT:
3325                 return FALSE;
3327         default:
3328                 report("Unknown key, press 'h' for help");
3329                 return TRUE;
3330         }
3332         return TRUE;
3336 /*
3337  * View backend utilities
3338  */
3340 DEFINE_ALLOCATOR(realloc_authors, const char *, 256)
3342 /* Small author cache to reduce memory consumption. It uses binary
3343  * search to lookup or find place to position new entries. No entries
3344  * are ever freed. */
3345 static const char *
3346 get_author(const char *name)
3348         static const char **authors;
3349         static size_t authors_size;
3350         int from = 0, to = authors_size - 1;
3352         while (from <= to) {
3353                 size_t pos = (to + from) / 2;
3354                 int cmp = strcmp(name, authors[pos]);
3356                 if (!cmp)
3357                         return authors[pos];
3359                 if (cmp < 0)
3360                         to = pos - 1;
3361                 else
3362                         from = pos + 1;
3363         }
3365         if (!realloc_authors(&authors, authors_size, 1))
3366                 return NULL;
3367         name = strdup(name);
3368         if (!name)
3369                 return NULL;
3371         memmove(authors + from + 1, authors + from, (authors_size - from) * sizeof(*authors));
3372         authors[from] = name;
3373         authors_size++;
3375         return name;
3378 static void
3379 parse_timezone(time_t *time, const char *zone)
3381         long tz;
3383         tz  = ('0' - zone[1]) * 60 * 60 * 10;
3384         tz += ('0' - zone[2]) * 60 * 60;
3385         tz += ('0' - zone[3]) * 60;
3386         tz += ('0' - zone[4]);
3388         if (zone[0] == '-')
3389                 tz = -tz;
3391         *time -= tz;
3394 /* Parse author lines where the name may be empty:
3395  *      author  <email@address.tld> 1138474660 +0100
3396  */
3397 static void
3398 parse_author_line(char *ident, const char **author, time_t *time)
3400         char *nameend = strchr(ident, '<');
3401         char *emailend = strchr(ident, '>');
3403         if (nameend && emailend)
3404                 *nameend = *emailend = 0;
3405         ident = chomp_string(ident);
3406         if (!*ident) {
3407                 if (nameend)
3408                         ident = chomp_string(nameend + 1);
3409                 if (!*ident)
3410                         ident = "Unknown";
3411         }
3413         *author = get_author(ident);
3415         /* Parse epoch and timezone */
3416         if (emailend && emailend[1] == ' ') {
3417                 char *secs = emailend + 2;
3418                 char *zone = strchr(secs, ' ');
3420                 *time = (time_t) atol(secs);
3422                 if (zone && strlen(zone) == STRING_SIZE(" +0700"))
3423                         parse_timezone(time, zone + 1);
3424         }
3427 static enum input_status
3428 select_commit_parent_handler(void *data, char *buf, int c)
3430         size_t parents = *(size_t *) data;
3431         int parent = 0;
3433         if (!isdigit(c))
3434                 return INPUT_SKIP;
3436         if (*buf)
3437                 parent = atoi(buf) * 10;
3438         parent += c - '0';
3440         if (parent > parents)
3441                 return INPUT_SKIP;
3442         return INPUT_OK;
3445 static bool
3446 select_commit_parent(const char *id, char rev[SIZEOF_REV], const char *path)
3448         char buf[SIZEOF_STR * 4];
3449         const char *revlist_argv[] = {
3450                 "git", "rev-list", "-1", "--parents", id, "--", path, NULL
3451         };
3452         int parents;
3454         if (!run_io_buf(revlist_argv, buf, sizeof(buf)) ||
3455             (parents = (strlen(buf) / 40) - 1) < 0) {
3456                 report("Failed to get parent information");
3457                 return FALSE;
3459         } else if (parents == 0) {
3460                 if (path)
3461                         report("Path '%s' does not exist in the parent", path);
3462                 else
3463                         report("The selected commit has no parents");
3464                 return FALSE;
3465         }
3467         if (parents > 1) {
3468                 char prompt[SIZEOF_STR];
3469                 char *result;
3471                 if (!string_format(prompt, "Which parent? [1..%d] ", parents))
3472                         return FALSE;
3473                 result = prompt_input(prompt, select_commit_parent_handler, &parents);
3474                 if (!result)
3475                         return FALSE;
3476                 parents = atoi(result);
3477         }
3479         string_copy_rev(rev, &buf[41 * parents]);
3480         return TRUE;
3483 /*
3484  * Pager backend
3485  */
3487 static bool
3488 pager_draw(struct view *view, struct line *line, unsigned int lineno)
3490         char text[SIZEOF_STR];
3492         if (opt_line_number && draw_lineno(view, lineno))
3493                 return TRUE;
3495         string_expand(text, sizeof(text), line->data, opt_tab_size);
3496         draw_text(view, line->type, text, TRUE);
3497         return TRUE;
3500 static bool
3501 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
3503         const char *describe_argv[] = { "git", "describe", commit_id, NULL };
3504         char ref[SIZEOF_STR];
3506         if (!run_io_buf(describe_argv, ref, sizeof(ref)) || !*ref)
3507                 return TRUE;
3509         /* This is the only fatal call, since it can "corrupt" the buffer. */
3510         if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
3511                 return FALSE;
3513         return TRUE;
3516 static void
3517 add_pager_refs(struct view *view, struct line *line)
3519         char buf[SIZEOF_STR];
3520         char *commit_id = (char *)line->data + STRING_SIZE("commit ");
3521         struct ref **refs;
3522         size_t bufpos = 0, refpos = 0;
3523         const char *sep = "Refs: ";
3524         bool is_tag = FALSE;
3526         assert(line->type == LINE_COMMIT);
3528         refs = get_refs(commit_id);
3529         if (!refs) {
3530                 if (view == VIEW(REQ_VIEW_DIFF))
3531                         goto try_add_describe_ref;
3532                 return;
3533         }
3535         do {
3536                 struct ref *ref = refs[refpos];
3537                 const char *fmt = ref->tag    ? "%s[%s]" :
3538                                   ref->remote ? "%s<%s>" : "%s%s";
3540                 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
3541                         return;
3542                 sep = ", ";
3543                 if (ref->tag)
3544                         is_tag = TRUE;
3545         } while (refs[refpos++]->next);
3547         if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
3548 try_add_describe_ref:
3549                 /* Add <tag>-g<commit_id> "fake" reference. */
3550                 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
3551                         return;
3552         }
3554         if (bufpos == 0)
3555                 return;
3557         add_line_text(view, buf, LINE_PP_REFS);
3560 static bool
3561 pager_read(struct view *view, char *data)
3563         struct line *line;
3565         if (!data)
3566                 return TRUE;
3568         line = add_line_text(view, data, get_line_type(data));
3569         if (!line)
3570                 return FALSE;
3572         if (line->type == LINE_COMMIT &&
3573             (view == VIEW(REQ_VIEW_DIFF) ||
3574              view == VIEW(REQ_VIEW_LOG)))
3575                 add_pager_refs(view, line);
3577         return TRUE;
3580 static enum request
3581 pager_request(struct view *view, enum request request, struct line *line)
3583         int split = 0;
3585         if (request != REQ_ENTER)
3586                 return request;
3588         if (line->type == LINE_COMMIT &&
3589            (view == VIEW(REQ_VIEW_LOG) ||
3590             view == VIEW(REQ_VIEW_PAGER))) {
3591                 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
3592                 split = 1;
3593         }
3595         /* Always scroll the view even if it was split. That way
3596          * you can use Enter to scroll through the log view and
3597          * split open each commit diff. */
3598         scroll_view(view, REQ_SCROLL_LINE_DOWN);
3600         /* FIXME: A minor workaround. Scrolling the view will call report("")
3601          * but if we are scrolling a non-current view this won't properly
3602          * update the view title. */
3603         if (split)
3604                 update_view_title(view);
3606         return REQ_NONE;
3609 static bool
3610 pager_grep(struct view *view, struct line *line)
3612         const char *text[] = { line->data, NULL };
3614         return grep_text(view, text);
3617 static void
3618 pager_select(struct view *view, struct line *line)
3620         if (line->type == LINE_COMMIT) {
3621                 char *text = (char *)line->data + STRING_SIZE("commit ");
3623                 if (view != VIEW(REQ_VIEW_PAGER))
3624                         string_copy_rev(view->ref, text);
3625                 string_copy_rev(ref_commit, text);
3626         }
3629 static struct view_ops pager_ops = {
3630         "line",
3631         NULL,
3632         NULL,
3633         pager_read,
3634         pager_draw,
3635         pager_request,
3636         pager_grep,
3637         pager_select,
3638 };
3640 static const char *log_argv[SIZEOF_ARG] = {
3641         "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
3642 };
3644 static enum request
3645 log_request(struct view *view, enum request request, struct line *line)
3647         switch (request) {
3648         case REQ_REFRESH:
3649                 load_refs();
3650                 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
3651                 return REQ_NONE;
3652         default:
3653                 return pager_request(view, request, line);
3654         }
3657 static struct view_ops log_ops = {
3658         "line",
3659         log_argv,
3660         NULL,
3661         pager_read,
3662         pager_draw,
3663         log_request,
3664         pager_grep,
3665         pager_select,
3666 };
3668 static const char *diff_argv[SIZEOF_ARG] = {
3669         "git", "show", "--pretty=fuller", "--no-color", "--root",
3670                 "--patch-with-stat", "--find-copies-harder", "-C", "%(commit)", NULL
3671 };
3673 static struct view_ops diff_ops = {
3674         "line",
3675         diff_argv,
3676         NULL,
3677         pager_read,
3678         pager_draw,
3679         pager_request,
3680         pager_grep,
3681         pager_select,
3682 };
3684 /*
3685  * Help backend
3686  */
3688 static bool
3689 help_open(struct view *view)
3691         char buf[SIZEOF_STR];
3692         size_t bufpos;
3693         int i;
3695         if (view->lines > 0)
3696                 return TRUE;
3698         add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
3700         for (i = 0; i < ARRAY_SIZE(req_info); i++) {
3701                 const char *key;
3703                 if (req_info[i].request == REQ_NONE)
3704                         continue;
3706                 if (!req_info[i].request) {
3707                         add_line_text(view, "", LINE_DEFAULT);
3708                         add_line_text(view, req_info[i].help, LINE_DEFAULT);
3709                         continue;
3710                 }
3712                 key = get_key(req_info[i].request);
3713                 if (!*key)
3714                         key = "(no key defined)";
3716                 for (bufpos = 0; bufpos <= req_info[i].namelen; bufpos++) {
3717                         buf[bufpos] = tolower(req_info[i].name[bufpos]);
3718                         if (buf[bufpos] == '_')
3719                                 buf[bufpos] = '-';
3720                 }
3722                 add_line_format(view, LINE_DEFAULT, "    %-25s %-20s %s",
3723                                 key, buf, req_info[i].help);
3724         }
3726         if (run_requests) {
3727                 add_line_text(view, "", LINE_DEFAULT);
3728                 add_line_text(view, "External commands:", LINE_DEFAULT);
3729         }
3731         for (i = 0; i < run_requests; i++) {
3732                 struct run_request *req = get_run_request(REQ_NONE + i + 1);
3733                 const char *key;
3734                 int argc;
3736                 if (!req)
3737                         continue;
3739                 key = get_key_name(req->key);
3740                 if (!*key)
3741                         key = "(no key defined)";
3743                 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
3744                         if (!string_format_from(buf, &bufpos, "%s%s",
3745                                                 argc ? " " : "", req->argv[argc]))
3746                                 return REQ_NONE;
3748                 add_line_format(view, LINE_DEFAULT, "    %-10s %-14s `%s`",
3749                                 keymap_table[req->keymap].name, key, buf);
3750         }
3752         return TRUE;
3755 static struct view_ops help_ops = {
3756         "line",
3757         NULL,
3758         help_open,
3759         NULL,
3760         pager_draw,
3761         pager_request,
3762         pager_grep,
3763         pager_select,
3764 };
3767 /*
3768  * Tree backend
3769  */
3771 struct tree_stack_entry {
3772         struct tree_stack_entry *prev;  /* Entry below this in the stack */
3773         unsigned long lineno;           /* Line number to restore */
3774         char *name;                     /* Position of name in opt_path */
3775 };
3777 /* The top of the path stack. */
3778 static struct tree_stack_entry *tree_stack = NULL;
3779 unsigned long tree_lineno = 0;
3781 static void
3782 pop_tree_stack_entry(void)
3784         struct tree_stack_entry *entry = tree_stack;
3786         tree_lineno = entry->lineno;
3787         entry->name[0] = 0;
3788         tree_stack = entry->prev;
3789         free(entry);
3792 static void
3793 push_tree_stack_entry(const char *name, unsigned long lineno)
3795         struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
3796         size_t pathlen = strlen(opt_path);
3798         if (!entry)
3799                 return;
3801         entry->prev = tree_stack;
3802         entry->name = opt_path + pathlen;
3803         tree_stack = entry;
3805         if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
3806                 pop_tree_stack_entry();
3807                 return;
3808         }
3810         /* Move the current line to the first tree entry. */
3811         tree_lineno = 1;
3812         entry->lineno = lineno;
3815 /* Parse output from git-ls-tree(1):
3816  *
3817  * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
3818  */
3820 #define SIZEOF_TREE_ATTR \
3821         STRING_SIZE("100644 blob f931e1d229c3e185caad4449bf5b66ed72462657\t")
3823 #define SIZEOF_TREE_MODE \
3824         STRING_SIZE("100644 ")
3826 #define TREE_ID_OFFSET \
3827         STRING_SIZE("100644 blob ")
3829 struct tree_entry {
3830         char id[SIZEOF_REV];
3831         mode_t mode;
3832         time_t time;                    /* Date from the author ident. */
3833         const char *author;             /* Author of the commit. */
3834         char name[1];
3835 };
3837 static const char *
3838 tree_path(struct line *line)
3840         return ((struct tree_entry *) line->data)->name;
3844 static int
3845 tree_compare_entry(struct line *line1, struct line *line2)
3847         if (line1->type != line2->type)
3848                 return line1->type == LINE_TREE_DIR ? -1 : 1;
3849         return strcmp(tree_path(line1), tree_path(line2));
3852 static struct line *
3853 tree_entry(struct view *view, enum line_type type, const char *path,
3854            const char *mode, const char *id)
3856         struct tree_entry *entry = calloc(1, sizeof(*entry) + strlen(path));
3857         struct line *line = entry ? add_line_data(view, entry, type) : NULL;
3859         if (!entry || !line) {
3860                 free(entry);
3861                 return NULL;
3862         }
3864         strncpy(entry->name, path, strlen(path));
3865         if (mode)
3866                 entry->mode = strtoul(mode, NULL, 8);
3867         if (id)
3868                 string_copy_rev(entry->id, id);
3870         return line;
3873 static bool
3874 tree_read_date(struct view *view, char *text, bool *read_date)
3876         static const char *author_name;
3877         static time_t author_time;
3879         if (!text && *read_date) {
3880                 *read_date = FALSE;
3881                 return TRUE;
3883         } else if (!text) {
3884                 char *path = *opt_path ? opt_path : ".";
3885                 /* Find next entry to process */
3886                 const char *log_file[] = {
3887                         "git", "log", "--no-color", "--pretty=raw",
3888                                 "--cc", "--raw", view->id, "--", path, NULL
3889                 };
3890                 struct io io = {};
3892                 if (!view->lines) {
3893                         tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL);
3894                         report("Tree is empty");
3895                         return TRUE;
3896                 }
3898                 if (!run_io_rd(&io, log_file, FORMAT_NONE)) {
3899                         report("Failed to load tree data");
3900                         return TRUE;
3901                 }
3903                 done_io(view->pipe);
3904                 view->io = io;
3905                 *read_date = TRUE;
3906                 return FALSE;
3908         } else if (*text == 'a' && get_line_type(text) == LINE_AUTHOR) {
3909                 parse_author_line(text + STRING_SIZE("author "),
3910                                   &author_name, &author_time);
3912         } else if (*text == ':') {
3913                 char *pos;
3914                 size_t annotated = 1;
3915                 size_t i;
3917                 pos = strchr(text, '\t');
3918                 if (!pos)
3919                         return TRUE;
3920                 text = pos + 1;
3921                 if (*opt_prefix && !strncmp(text, opt_prefix, strlen(opt_prefix)))
3922                         text += strlen(opt_prefix);
3923                 if (*opt_path && !strncmp(text, opt_path, strlen(opt_path)))
3924                         text += strlen(opt_path);
3925                 pos = strchr(text, '/');
3926                 if (pos)
3927                         *pos = 0;
3929                 for (i = 1; i < view->lines; i++) {
3930                         struct line *line = &view->line[i];
3931                         struct tree_entry *entry = line->data;
3933                         annotated += !!entry->author;
3934                         if (entry->author || strcmp(entry->name, text))
3935                                 continue;
3937                         entry->author = author_name;
3938                         entry->time = author_time;
3939                         line->dirty = 1;
3940                         break;
3941                 }
3943                 if (annotated == view->lines)
3944                         kill_io(view->pipe);
3945         }
3946         return TRUE;
3949 static bool
3950 tree_read(struct view *view, char *text)
3952         static bool read_date = FALSE;
3953         struct tree_entry *data;
3954         struct line *entry, *line;
3955         enum line_type type;
3956         size_t textlen = text ? strlen(text) : 0;
3957         char *path = text + SIZEOF_TREE_ATTR;
3959         if (read_date || !text)
3960                 return tree_read_date(view, text, &read_date);
3962         if (textlen <= SIZEOF_TREE_ATTR)
3963                 return FALSE;
3964         if (view->lines == 0 &&
3965             !tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL))
3966                 return FALSE;
3968         /* Strip the path part ... */
3969         if (*opt_path) {
3970                 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
3971                 size_t striplen = strlen(opt_path);
3973                 if (pathlen > striplen)
3974                         memmove(path, path + striplen,
3975                                 pathlen - striplen + 1);
3977                 /* Insert "link" to parent directory. */
3978                 if (view->lines == 1 &&
3979                     !tree_entry(view, LINE_TREE_DIR, "..", "040000", view->ref))
3980                         return FALSE;
3981         }
3983         type = text[SIZEOF_TREE_MODE] == 't' ? LINE_TREE_DIR : LINE_TREE_FILE;
3984         entry = tree_entry(view, type, path, text, text + TREE_ID_OFFSET);
3985         if (!entry)
3986                 return FALSE;
3987         data = entry->data;
3989         /* Skip "Directory ..." and ".." line. */
3990         for (line = &view->line[1 + !!*opt_path]; line < entry; line++) {
3991                 if (tree_compare_entry(line, entry) <= 0)
3992                         continue;
3994                 memmove(line + 1, line, (entry - line) * sizeof(*entry));
3996                 line->data = data;
3997                 line->type = type;
3998                 for (; line <= entry; line++)
3999                         line->dirty = line->cleareol = 1;
4000                 return TRUE;
4001         }
4003         if (tree_lineno > view->lineno) {
4004                 view->lineno = tree_lineno;
4005                 tree_lineno = 0;
4006         }
4008         return TRUE;
4011 static bool
4012 tree_draw(struct view *view, struct line *line, unsigned int lineno)
4014         struct tree_entry *entry = line->data;
4016         if (line->type == LINE_TREE_HEAD) {
4017                 if (draw_text(view, line->type, "Directory path /", TRUE))
4018                         return TRUE;
4019         } else {
4020                 if (draw_mode(view, entry->mode))
4021                         return TRUE;
4023                 if (opt_author && draw_author(view, entry->author))
4024                         return TRUE;
4026                 if (opt_date && draw_date(view, entry->author ? &entry->time : NULL))
4027                         return TRUE;
4028         }
4029         if (draw_text(view, line->type, entry->name, TRUE))
4030                 return TRUE;
4031         return TRUE;
4034 static void
4035 open_blob_editor()
4037         char file[SIZEOF_STR] = "/tmp/tigblob.XXXXXX";
4038         int fd = mkstemp(file);
4040         if (fd == -1)
4041                 report("Failed to create temporary file");
4042         else if (!run_io_append(blob_ops.argv, FORMAT_ALL, fd))
4043                 report("Failed to save blob data to file");
4044         else
4045                 open_editor(FALSE, file);
4046         if (fd != -1)
4047                 unlink(file);
4050 static enum request
4051 tree_request(struct view *view, enum request request, struct line *line)
4053         enum open_flags flags;
4055         switch (request) {
4056         case REQ_VIEW_BLAME:
4057                 if (line->type != LINE_TREE_FILE) {
4058                         report("Blame only supported for files");
4059                         return REQ_NONE;
4060                 }
4062                 string_copy(opt_ref, view->vid);
4063                 return request;
4065         case REQ_EDIT:
4066                 if (line->type != LINE_TREE_FILE) {
4067                         report("Edit only supported for files");
4068                 } else if (!is_head_commit(view->vid)) {
4069                         open_blob_editor();
4070                 } else {
4071                         open_editor(TRUE, opt_file);
4072                 }
4073                 return REQ_NONE;
4075         case REQ_PARENT:
4076                 if (!*opt_path) {
4077                         /* quit view if at top of tree */
4078                         return REQ_VIEW_CLOSE;
4079                 }
4080                 /* fake 'cd  ..' */
4081                 line = &view->line[1];
4082                 break;
4084         case REQ_ENTER:
4085                 break;
4087         default:
4088                 return request;
4089         }
4091         /* Cleanup the stack if the tree view is at a different tree. */
4092         while (!*opt_path && tree_stack)
4093                 pop_tree_stack_entry();
4095         switch (line->type) {
4096         case LINE_TREE_DIR:
4097                 /* Depending on whether it is a subdirectory or parent link
4098                  * mangle the path buffer. */
4099                 if (line == &view->line[1] && *opt_path) {
4100                         pop_tree_stack_entry();
4102                 } else {
4103                         const char *basename = tree_path(line);
4105                         push_tree_stack_entry(basename, view->lineno);
4106                 }
4108                 /* Trees and subtrees share the same ID, so they are not not
4109                  * unique like blobs. */
4110                 flags = OPEN_RELOAD;
4111                 request = REQ_VIEW_TREE;
4112                 break;
4114         case LINE_TREE_FILE:
4115                 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4116                 request = REQ_VIEW_BLOB;
4117                 break;
4119         default:
4120                 return REQ_NONE;
4121         }
4123         open_view(view, request, flags);
4124         if (request == REQ_VIEW_TREE)
4125                 view->lineno = tree_lineno;
4127         return REQ_NONE;
4130 static bool
4131 tree_grep(struct view *view, struct line *line)
4133         struct tree_entry *entry = line->data;
4134         const char *text[] = {
4135                 entry->name,
4136                 opt_author ? entry->author : "",
4137                 opt_date ? mkdate(&entry->time) : "",
4138                 NULL
4139         };
4141         return grep_text(view, text);
4144 static void
4145 tree_select(struct view *view, struct line *line)
4147         struct tree_entry *entry = line->data;
4149         if (line->type == LINE_TREE_FILE) {
4150                 string_copy_rev(ref_blob, entry->id);
4151                 string_format(opt_file, "%s%s", opt_path, tree_path(line));
4153         } else if (line->type != LINE_TREE_DIR) {
4154                 return;
4155         }
4157         string_copy_rev(view->ref, entry->id);
4160 static const char *tree_argv[SIZEOF_ARG] = {
4161         "git", "ls-tree", "%(commit)", "%(directory)", NULL
4162 };
4164 static struct view_ops tree_ops = {
4165         "file",
4166         tree_argv,
4167         NULL,
4168         tree_read,
4169         tree_draw,
4170         tree_request,
4171         tree_grep,
4172         tree_select,
4173 };
4175 static bool
4176 blob_read(struct view *view, char *line)
4178         if (!line)
4179                 return TRUE;
4180         return add_line_text(view, line, LINE_DEFAULT) != NULL;
4183 static enum request
4184 blob_request(struct view *view, enum request request, struct line *line)
4186         switch (request) {
4187         case REQ_EDIT:
4188                 open_blob_editor();
4189                 return REQ_NONE;
4190         default:
4191                 return pager_request(view, request, line);
4192         }
4195 static const char *blob_argv[SIZEOF_ARG] = {
4196         "git", "cat-file", "blob", "%(blob)", NULL
4197 };
4199 static struct view_ops blob_ops = {
4200         "line",
4201         blob_argv,
4202         NULL,
4203         blob_read,
4204         pager_draw,
4205         blob_request,
4206         pager_grep,
4207         pager_select,
4208 };
4210 /*
4211  * Blame backend
4212  *
4213  * Loading the blame view is a two phase job:
4214  *
4215  *  1. File content is read either using opt_file from the
4216  *     filesystem or using git-cat-file.
4217  *  2. Then blame information is incrementally added by
4218  *     reading output from git-blame.
4219  */
4221 static const char *blame_head_argv[] = {
4222         "git", "blame", "--incremental", "--", "%(file)", NULL
4223 };
4225 static const char *blame_ref_argv[] = {
4226         "git", "blame", "--incremental", "%(ref)", "--", "%(file)", NULL
4227 };
4229 static const char *blame_cat_file_argv[] = {
4230         "git", "cat-file", "blob", "%(ref):%(file)", NULL
4231 };
4233 struct blame_commit {
4234         char id[SIZEOF_REV];            /* SHA1 ID. */
4235         char title[128];                /* First line of the commit message. */
4236         const char *author;             /* Author of the commit. */
4237         time_t time;                    /* Date from the author ident. */
4238         char filename[128];             /* Name of file. */
4239         bool has_previous;              /* Was a "previous" line detected. */
4240 };
4242 struct blame {
4243         struct blame_commit *commit;
4244         unsigned long lineno;
4245         char text[1];
4246 };
4248 static bool
4249 blame_open(struct view *view)
4251         if (*opt_ref || !io_open(&view->io, opt_file)) {
4252                 if (!run_io_rd(&view->io, blame_cat_file_argv, FORMAT_ALL))
4253                         return FALSE;
4254         }
4256         setup_update(view, opt_file);
4257         string_format(view->ref, "%s ...", opt_file);
4259         return TRUE;
4262 static struct blame_commit *
4263 get_blame_commit(struct view *view, const char *id)
4265         size_t i;
4267         for (i = 0; i < view->lines; i++) {
4268                 struct blame *blame = view->line[i].data;
4270                 if (!blame->commit)
4271                         continue;
4273                 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
4274                         return blame->commit;
4275         }
4277         {
4278                 struct blame_commit *commit = calloc(1, sizeof(*commit));
4280                 if (commit)
4281                         string_ncopy(commit->id, id, SIZEOF_REV);
4282                 return commit;
4283         }
4286 static bool
4287 parse_number(const char **posref, size_t *number, size_t min, size_t max)
4289         const char *pos = *posref;
4291         *posref = NULL;
4292         pos = strchr(pos + 1, ' ');
4293         if (!pos || !isdigit(pos[1]))
4294                 return FALSE;
4295         *number = atoi(pos + 1);
4296         if (*number < min || *number > max)
4297                 return FALSE;
4299         *posref = pos;
4300         return TRUE;
4303 static struct blame_commit *
4304 parse_blame_commit(struct view *view, const char *text, int *blamed)
4306         struct blame_commit *commit;
4307         struct blame *blame;
4308         const char *pos = text + SIZEOF_REV - 2;
4309         size_t orig_lineno = 0;
4310         size_t lineno;
4311         size_t group;
4313         if (strlen(text) <= SIZEOF_REV || pos[1] != ' ')
4314                 return NULL;
4316         if (!parse_number(&pos, &orig_lineno, 1, 9999999) ||
4317             !parse_number(&pos, &lineno, 1, view->lines) ||
4318             !parse_number(&pos, &group, 1, view->lines - lineno + 1))
4319                 return NULL;
4321         commit = get_blame_commit(view, text);
4322         if (!commit)
4323                 return NULL;
4325         *blamed += group;
4326         while (group--) {
4327                 struct line *line = &view->line[lineno + group - 1];
4329                 blame = line->data;
4330                 blame->commit = commit;
4331                 blame->lineno = orig_lineno + group - 1;
4332                 line->dirty = 1;
4333         }
4335         return commit;
4338 static bool
4339 blame_read_file(struct view *view, const char *line, bool *read_file)
4341         if (!line) {
4342                 const char **argv = *opt_ref ? blame_ref_argv : blame_head_argv;
4343                 struct io io = {};
4345                 if (view->lines == 0 && !view->parent)
4346                         die("No blame exist for %s", view->vid);
4348                 if (view->lines == 0 || !run_io_rd(&io, argv, FORMAT_ALL)) {
4349                         report("Failed to load blame data");
4350                         return TRUE;
4351                 }
4353                 done_io(view->pipe);
4354                 view->io = io;
4355                 *read_file = FALSE;
4356                 return FALSE;
4358         } else {
4359                 size_t linelen = strlen(line);
4360                 struct blame *blame = malloc(sizeof(*blame) + linelen);
4362                 if (!blame)
4363                         return FALSE;
4365                 blame->commit = NULL;
4366                 strncpy(blame->text, line, linelen);
4367                 blame->text[linelen] = 0;
4368                 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
4369         }
4372 static bool
4373 match_blame_header(const char *name, char **line)
4375         size_t namelen = strlen(name);
4376         bool matched = !strncmp(name, *line, namelen);
4378         if (matched)
4379                 *line += namelen;
4381         return matched;
4384 static bool
4385 blame_read(struct view *view, char *line)
4387         static struct blame_commit *commit = NULL;
4388         static int blamed = 0;
4389         static bool read_file = TRUE;
4391         if (read_file)
4392                 return blame_read_file(view, line, &read_file);
4394         if (!line) {
4395                 /* Reset all! */
4396                 commit = NULL;
4397                 blamed = 0;
4398                 read_file = TRUE;
4399                 string_format(view->ref, "%s", view->vid);
4400                 if (view_is_displayed(view)) {
4401                         update_view_title(view);
4402                         redraw_view_from(view, 0);
4403                 }
4404                 return TRUE;
4405         }
4407         if (!commit) {
4408                 commit = parse_blame_commit(view, line, &blamed);
4409                 string_format(view->ref, "%s %2d%%", view->vid,
4410                               view->lines ? blamed * 100 / view->lines : 0);
4412         } else if (match_blame_header("author ", &line)) {
4413                 commit->author = get_author(line);
4415         } else if (match_blame_header("author-time ", &line)) {
4416                 commit->time = (time_t) atol(line);
4418         } else if (match_blame_header("author-tz ", &line)) {
4419                 parse_timezone(&commit->time, line);
4421         } else if (match_blame_header("summary ", &line)) {
4422                 string_ncopy(commit->title, line, strlen(line));
4424         } else if (match_blame_header("previous ", &line)) {
4425                 commit->has_previous = TRUE;
4427         } else if (match_blame_header("filename ", &line)) {
4428                 string_ncopy(commit->filename, line, strlen(line));
4429                 commit = NULL;
4430         }
4432         return TRUE;
4435 static bool
4436 blame_draw(struct view *view, struct line *line, unsigned int lineno)
4438         struct blame *blame = line->data;
4439         time_t *time = NULL;
4440         const char *id = NULL, *author = NULL;
4441         char text[SIZEOF_STR];
4443         if (blame->commit && *blame->commit->filename) {
4444                 id = blame->commit->id;
4445                 author = blame->commit->author;
4446                 time = &blame->commit->time;
4447         }
4449         if (opt_date && draw_date(view, time))
4450                 return TRUE;
4452         if (opt_author && draw_author(view, author))
4453                 return TRUE;
4455         if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
4456                 return TRUE;
4458         if (draw_lineno(view, lineno))
4459                 return TRUE;
4461         string_expand(text, sizeof(text), blame->text, opt_tab_size);
4462         draw_text(view, LINE_DEFAULT, text, TRUE);
4463         return TRUE;
4466 static bool
4467 check_blame_commit(struct blame *blame, bool check_null_id)
4469         if (!blame->commit)
4470                 report("Commit data not loaded yet");
4471         else if (check_null_id && !strcmp(blame->commit->id, NULL_ID))
4472                 report("No commit exist for the selected line");
4473         else
4474                 return TRUE;
4475         return FALSE;
4478 static void
4479 setup_blame_parent_line(struct view *view, struct blame *blame)
4481         const char *diff_tree_argv[] = {
4482                 "git", "diff-tree", "-U0", blame->commit->id,
4483                         "--", blame->commit->filename, NULL
4484         };
4485         struct io io = {};
4486         int parent_lineno = -1;
4487         int blamed_lineno = -1;
4488         char *line;
4490         if (!run_io(&io, diff_tree_argv, NULL, IO_RD))
4491                 return;
4493         while ((line = io_get(&io, '\n', TRUE))) {
4494                 if (*line == '@') {
4495                         char *pos = strchr(line, '+');
4497                         parent_lineno = atoi(line + 4);
4498                         if (pos)
4499                                 blamed_lineno = atoi(pos + 1);
4501                 } else if (*line == '+' && parent_lineno != -1) {
4502                         if (blame->lineno == blamed_lineno - 1 &&
4503                             !strcmp(blame->text, line + 1)) {
4504                                 view->lineno = parent_lineno ? parent_lineno - 1 : 0;
4505                                 break;
4506                         }
4507                         blamed_lineno++;
4508                 }
4509         }
4511         done_io(&io);
4514 static enum request
4515 blame_request(struct view *view, enum request request, struct line *line)
4517         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4518         struct blame *blame = line->data;
4520         switch (request) {
4521         case REQ_VIEW_BLAME:
4522                 if (check_blame_commit(blame, TRUE)) {
4523                         string_copy(opt_ref, blame->commit->id);
4524                         string_copy(opt_file, blame->commit->filename);
4525                         if (blame->lineno)
4526                                 view->lineno = blame->lineno;
4527                         open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4528                 }
4529                 break;
4531         case REQ_PARENT:
4532                 if (check_blame_commit(blame, TRUE) &&
4533                     select_commit_parent(blame->commit->id, opt_ref,
4534                                          blame->commit->filename)) {
4535                         string_copy(opt_file, blame->commit->filename);
4536                         setup_blame_parent_line(view, blame);
4537                         open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4538                 }
4539                 break;
4541         case REQ_ENTER:
4542                 if (!check_blame_commit(blame, FALSE))
4543                         break;
4545                 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
4546                     !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
4547                         break;
4549                 if (!strcmp(blame->commit->id, NULL_ID)) {
4550                         struct view *diff = VIEW(REQ_VIEW_DIFF);
4551                         const char *diff_index_argv[] = {
4552                                 "git", "diff-index", "--root", "--patch-with-stat",
4553                                         "-C", "-M", "HEAD", "--", view->vid, NULL
4554                         };
4556                         if (!blame->commit->has_previous) {
4557                                 diff_index_argv[1] = "diff";
4558                                 diff_index_argv[2] = "--no-color";
4559                                 diff_index_argv[6] = "--";
4560                                 diff_index_argv[7] = "/dev/null";
4561                         }
4563                         if (!prepare_update(diff, diff_index_argv, NULL, FORMAT_DASH)) {
4564                                 report("Failed to allocate diff command");
4565                                 break;
4566                         }
4567                         flags |= OPEN_PREPARED;
4568                 }
4570                 open_view(view, REQ_VIEW_DIFF, flags);
4571                 if (VIEW(REQ_VIEW_DIFF)->pipe && !strcmp(blame->commit->id, NULL_ID))
4572                         string_copy_rev(VIEW(REQ_VIEW_DIFF)->ref, NULL_ID);
4573                 break;
4575         default:
4576                 return request;
4577         }
4579         return REQ_NONE;
4582 static bool
4583 blame_grep(struct view *view, struct line *line)
4585         struct blame *blame = line->data;
4586         struct blame_commit *commit = blame->commit;
4587         const char *text[] = {
4588                 blame->text,
4589                 commit ? commit->title : "",
4590                 commit ? commit->id : "",
4591                 commit && opt_author ? commit->author : "",
4592                 commit && opt_date ? mkdate(&commit->time) : "",
4593         };
4595         return grep_text(view, text);
4598 static void
4599 blame_select(struct view *view, struct line *line)
4601         struct blame *blame = line->data;
4602         struct blame_commit *commit = blame->commit;
4604         if (!commit)
4605                 return;
4607         if (!strcmp(commit->id, NULL_ID))
4608                 string_ncopy(ref_commit, "HEAD", 4);
4609         else
4610                 string_copy_rev(ref_commit, commit->id);
4613 static struct view_ops blame_ops = {
4614         "line",
4615         NULL,
4616         blame_open,
4617         blame_read,
4618         blame_draw,
4619         blame_request,
4620         blame_grep,
4621         blame_select,
4622 };
4624 /*
4625  * Status backend
4626  */
4628 struct status {
4629         char status;
4630         struct {
4631                 mode_t mode;
4632                 char rev[SIZEOF_REV];
4633                 char name[SIZEOF_STR];
4634         } old;
4635         struct {
4636                 mode_t mode;
4637                 char rev[SIZEOF_REV];
4638                 char name[SIZEOF_STR];
4639         } new;
4640 };
4642 static char status_onbranch[SIZEOF_STR];
4643 static struct status stage_status;
4644 static enum line_type stage_line_type;
4645 static size_t stage_chunks;
4646 static int *stage_chunk;
4648 DEFINE_ALLOCATOR(realloc_ints, int, 32)
4650 /* This should work even for the "On branch" line. */
4651 static inline bool
4652 status_has_none(struct view *view, struct line *line)
4654         return line < view->line + view->lines && !line[1].data;
4657 /* Get fields from the diff line:
4658  * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
4659  */
4660 static inline bool
4661 status_get_diff(struct status *file, const char *buf, size_t bufsize)
4663         const char *old_mode = buf +  1;
4664         const char *new_mode = buf +  8;
4665         const char *old_rev  = buf + 15;
4666         const char *new_rev  = buf + 56;
4667         const char *status   = buf + 97;
4669         if (bufsize < 98 ||
4670             old_mode[-1] != ':' ||
4671             new_mode[-1] != ' ' ||
4672             old_rev[-1]  != ' ' ||
4673             new_rev[-1]  != ' ' ||
4674             status[-1]   != ' ')
4675                 return FALSE;
4677         file->status = *status;
4679         string_copy_rev(file->old.rev, old_rev);
4680         string_copy_rev(file->new.rev, new_rev);
4682         file->old.mode = strtoul(old_mode, NULL, 8);
4683         file->new.mode = strtoul(new_mode, NULL, 8);
4685         file->old.name[0] = file->new.name[0] = 0;
4687         return TRUE;
4690 static bool
4691 status_run(struct view *view, const char *argv[], char status, enum line_type type)
4693         struct status *unmerged = NULL;
4694         char *buf;
4695         struct io io = {};
4697         if (!run_io(&io, argv, NULL, IO_RD))
4698                 return FALSE;
4700         add_line_data(view, NULL, type);
4702         while ((buf = io_get(&io, 0, TRUE))) {
4703                 struct status *file = unmerged;
4705                 if (!file) {
4706                         file = calloc(1, sizeof(*file));
4707                         if (!file || !add_line_data(view, file, type))
4708                                 goto error_out;
4709                 }
4711                 /* Parse diff info part. */
4712                 if (status) {
4713                         file->status = status;
4714                         if (status == 'A')
4715                                 string_copy(file->old.rev, NULL_ID);
4717                 } else if (!file->status || file == unmerged) {
4718                         if (!status_get_diff(file, buf, strlen(buf)))
4719                                 goto error_out;
4721                         buf = io_get(&io, 0, TRUE);
4722                         if (!buf)
4723                                 break;
4725                         /* Collapse all modified entries that follow an
4726                          * associated unmerged entry. */
4727                         if (unmerged == file) {
4728                                 unmerged->status = 'U';
4729                                 unmerged = NULL;
4730                         } else if (file->status == 'U') {
4731                                 unmerged = file;
4732                         }
4733                 }
4735                 /* Grab the old name for rename/copy. */
4736                 if (!*file->old.name &&
4737                     (file->status == 'R' || file->status == 'C')) {
4738                         string_ncopy(file->old.name, buf, strlen(buf));
4740                         buf = io_get(&io, 0, TRUE);
4741                         if (!buf)
4742                                 break;
4743                 }
4745                 /* git-ls-files just delivers a NUL separated list of
4746                  * file names similar to the second half of the
4747                  * git-diff-* output. */
4748                 string_ncopy(file->new.name, buf, strlen(buf));
4749                 if (!*file->old.name)
4750                         string_copy(file->old.name, file->new.name);
4751                 file = NULL;
4752         }
4754         if (io_error(&io)) {
4755 error_out:
4756                 done_io(&io);
4757                 return FALSE;
4758         }
4760         if (!view->line[view->lines - 1].data)
4761                 add_line_data(view, NULL, LINE_STAT_NONE);
4763         done_io(&io);
4764         return TRUE;
4767 /* Don't show unmerged entries in the staged section. */
4768 static const char *status_diff_index_argv[] = {
4769         "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
4770                              "--cached", "-M", "HEAD", NULL
4771 };
4773 static const char *status_diff_files_argv[] = {
4774         "git", "diff-files", "-z", NULL
4775 };
4777 static const char *status_list_other_argv[] = {
4778         "git", "ls-files", "-z", "--others", "--exclude-standard", NULL
4779 };
4781 static const char *status_list_no_head_argv[] = {
4782         "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
4783 };
4785 static const char *update_index_argv[] = {
4786         "git", "update-index", "-q", "--unmerged", "--refresh", NULL
4787 };
4789 /* Restore the previous line number to stay in the context or select a
4790  * line with something that can be updated. */
4791 static void
4792 status_restore(struct view *view)
4794         if (view->p_lineno >= view->lines)
4795                 view->p_lineno = view->lines - 1;
4796         while (view->p_lineno < view->lines && !view->line[view->p_lineno].data)
4797                 view->p_lineno++;
4798         while (view->p_lineno > 0 && !view->line[view->p_lineno].data)
4799                 view->p_lineno--;
4801         /* If the above fails, always skip the "On branch" line. */
4802         if (view->p_lineno < view->lines)
4803                 view->lineno = view->p_lineno;
4804         else
4805                 view->lineno = 1;
4807         if (view->lineno < view->offset)
4808                 view->offset = view->lineno;
4809         else if (view->offset + view->height <= view->lineno)
4810                 view->offset = view->lineno - view->height + 1;
4812         view->p_restore = FALSE;
4815 static void
4816 status_update_onbranch(void)
4818         static const char *paths[][2] = {
4819                 { "rebase-apply/rebasing",      "Rebasing" },
4820                 { "rebase-apply/applying",      "Applying mailbox" },
4821                 { "rebase-apply/",              "Rebasing mailbox" },
4822                 { "rebase-merge/interactive",   "Interactive rebase" },
4823                 { "rebase-merge/",              "Rebase merge" },
4824                 { "MERGE_HEAD",                 "Merging" },
4825                 { "BISECT_LOG",                 "Bisecting" },
4826                 { "HEAD",                       "On branch" },
4827         };
4828         char buf[SIZEOF_STR];
4829         struct stat stat;
4830         int i;
4832         if (is_initial_commit()) {
4833                 string_copy(status_onbranch, "Initial commit");
4834                 return;
4835         }
4837         for (i = 0; i < ARRAY_SIZE(paths); i++) {
4838                 char *head = opt_head;
4840                 if (!string_format(buf, "%s/%s", opt_git_dir, paths[i][0]) ||
4841                     lstat(buf, &stat) < 0)
4842                         continue;
4844                 if (!*opt_head) {
4845                         struct io io = {};
4847                         if (string_format(buf, "%s/rebase-merge/head-name", opt_git_dir) &&
4848                             io_open(&io, buf) &&
4849                             io_read_buf(&io, buf, sizeof(buf))) {
4850                                 head = buf;
4851                                 if (!prefixcmp(head, "refs/heads/"))
4852                                         head += STRING_SIZE("refs/heads/");
4853                         }
4854                 }
4856                 if (!string_format(status_onbranch, "%s %s", paths[i][1], head))
4857                         string_copy(status_onbranch, opt_head);
4858                 return;
4859         }
4861         string_copy(status_onbranch, "Not currently on any branch");
4864 /* First parse staged info using git-diff-index(1), then parse unstaged
4865  * info using git-diff-files(1), and finally untracked files using
4866  * git-ls-files(1). */
4867 static bool
4868 status_open(struct view *view)
4870         reset_view(view);
4872         add_line_data(view, NULL, LINE_STAT_HEAD);
4873         status_update_onbranch();
4875         run_io_bg(update_index_argv);
4877         if (is_initial_commit()) {
4878                 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
4879                         return FALSE;
4880         } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
4881                 return FALSE;
4882         }
4884         if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
4885             !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
4886                 return FALSE;
4888         /* Restore the exact position or use the specialized restore
4889          * mode? */
4890         if (!view->p_restore)
4891                 status_restore(view);
4892         return TRUE;
4895 static bool
4896 status_draw(struct view *view, struct line *line, unsigned int lineno)
4898         struct status *status = line->data;
4899         enum line_type type;
4900         const char *text;
4902         if (!status) {
4903                 switch (line->type) {
4904                 case LINE_STAT_STAGED:
4905                         type = LINE_STAT_SECTION;
4906                         text = "Changes to be committed:";
4907                         break;
4909                 case LINE_STAT_UNSTAGED:
4910                         type = LINE_STAT_SECTION;
4911                         text = "Changed but not updated:";
4912                         break;
4914                 case LINE_STAT_UNTRACKED:
4915                         type = LINE_STAT_SECTION;
4916                         text = "Untracked files:";
4917                         break;
4919                 case LINE_STAT_NONE:
4920                         type = LINE_DEFAULT;
4921                         text = "  (no files)";
4922                         break;
4924                 case LINE_STAT_HEAD:
4925                         type = LINE_STAT_HEAD;
4926                         text = status_onbranch;
4927                         break;
4929                 default:
4930                         return FALSE;
4931                 }
4932         } else {
4933                 static char buf[] = { '?', ' ', ' ', ' ', 0 };
4935                 buf[0] = status->status;
4936                 if (draw_text(view, line->type, buf, TRUE))
4937                         return TRUE;
4938                 type = LINE_DEFAULT;
4939                 text = status->new.name;
4940         }
4942         draw_text(view, type, text, TRUE);
4943         return TRUE;
4946 static enum request
4947 status_load_error(struct view *view, struct view *stage, const char *path)
4949         if (displayed_views() == 2 || display[current_view] != view)
4950                 maximize_view(view);
4951         report("Failed to load '%s': %s", path, io_strerror(&stage->io));
4952         return REQ_NONE;
4955 static enum request
4956 status_enter(struct view *view, struct line *line)
4958         struct status *status = line->data;
4959         const char *oldpath = status ? status->old.name : NULL;
4960         /* Diffs for unmerged entries are empty when passing the new
4961          * path, so leave it empty. */
4962         const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
4963         const char *info;
4964         enum open_flags split;
4965         struct view *stage = VIEW(REQ_VIEW_STAGE);
4967         if (line->type == LINE_STAT_NONE ||
4968             (!status && line[1].type == LINE_STAT_NONE)) {
4969                 report("No file to diff");
4970                 return REQ_NONE;
4971         }
4973         switch (line->type) {
4974         case LINE_STAT_STAGED:
4975                 if (is_initial_commit()) {
4976                         const char *no_head_diff_argv[] = {
4977                                 "git", "diff", "--no-color", "--patch-with-stat",
4978                                         "--", "/dev/null", newpath, NULL
4979                         };
4981                         if (!prepare_update(stage, no_head_diff_argv, opt_cdup, FORMAT_DASH))
4982                                 return status_load_error(view, stage, newpath);
4983                 } else {
4984                         const char *index_show_argv[] = {
4985                                 "git", "diff-index", "--root", "--patch-with-stat",
4986                                         "-C", "-M", "--cached", "HEAD", "--",
4987                                         oldpath, newpath, NULL
4988                         };
4990                         if (!prepare_update(stage, index_show_argv, opt_cdup, FORMAT_DASH))
4991                                 return status_load_error(view, stage, newpath);
4992                 }
4994                 if (status)
4995                         info = "Staged changes to %s";
4996                 else
4997                         info = "Staged changes";
4998                 break;
5000         case LINE_STAT_UNSTAGED:
5001         {
5002                 const char *files_show_argv[] = {
5003                         "git", "diff-files", "--root", "--patch-with-stat",
5004                                 "-C", "-M", "--", oldpath, newpath, NULL
5005                 };
5007                 if (!prepare_update(stage, files_show_argv, opt_cdup, FORMAT_DASH))
5008                         return status_load_error(view, stage, newpath);
5009                 if (status)
5010                         info = "Unstaged changes to %s";
5011                 else
5012                         info = "Unstaged changes";
5013                 break;
5014         }
5015         case LINE_STAT_UNTRACKED:
5016                 if (!newpath) {
5017                         report("No file to show");
5018                         return REQ_NONE;
5019                 }
5021                 if (!suffixcmp(status->new.name, -1, "/")) {
5022                         report("Cannot display a directory");
5023                         return REQ_NONE;
5024                 }
5026                 if (!prepare_update_file(stage, newpath))
5027                         return status_load_error(view, stage, newpath);
5028                 info = "Untracked file %s";
5029                 break;
5031         case LINE_STAT_HEAD:
5032                 return REQ_NONE;
5034         default:
5035                 die("line type %d not handled in switch", line->type);
5036         }
5038         split = view_is_displayed(view) ? OPEN_SPLIT : 0;
5039         open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split);
5040         if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
5041                 if (status) {
5042                         stage_status = *status;
5043                 } else {
5044                         memset(&stage_status, 0, sizeof(stage_status));
5045                 }
5047                 stage_line_type = line->type;
5048                 stage_chunks = 0;
5049                 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
5050         }
5052         return REQ_NONE;
5055 static bool
5056 status_exists(struct status *status, enum line_type type)
5058         struct view *view = VIEW(REQ_VIEW_STATUS);
5059         unsigned long lineno;
5061         for (lineno = 0; lineno < view->lines; lineno++) {
5062                 struct line *line = &view->line[lineno];
5063                 struct status *pos = line->data;
5065                 if (line->type != type)
5066                         continue;
5067                 if (!pos && (!status || !status->status) && line[1].data) {
5068                         select_view_line(view, lineno);
5069                         return TRUE;
5070                 }
5071                 if (pos && !strcmp(status->new.name, pos->new.name)) {
5072                         select_view_line(view, lineno);
5073                         return TRUE;
5074                 }
5075         }
5077         return FALSE;
5081 static bool
5082 status_update_prepare(struct io *io, enum line_type type)
5084         const char *staged_argv[] = {
5085                 "git", "update-index", "-z", "--index-info", NULL
5086         };
5087         const char *others_argv[] = {
5088                 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
5089         };
5091         switch (type) {
5092         case LINE_STAT_STAGED:
5093                 return run_io(io, staged_argv, opt_cdup, IO_WR);
5095         case LINE_STAT_UNSTAGED:
5096                 return run_io(io, others_argv, opt_cdup, IO_WR);
5098         case LINE_STAT_UNTRACKED:
5099                 return run_io(io, others_argv, NULL, IO_WR);
5101         default:
5102                 die("line type %d not handled in switch", type);
5103                 return FALSE;
5104         }
5107 static bool
5108 status_update_write(struct io *io, struct status *status, enum line_type type)
5110         char buf[SIZEOF_STR];
5111         size_t bufsize = 0;
5113         switch (type) {
5114         case LINE_STAT_STAGED:
5115                 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
5116                                         status->old.mode,
5117                                         status->old.rev,
5118                                         status->old.name, 0))
5119                         return FALSE;
5120                 break;
5122         case LINE_STAT_UNSTAGED:
5123         case LINE_STAT_UNTRACKED:
5124                 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
5125                         return FALSE;
5126                 break;
5128         default:
5129                 die("line type %d not handled in switch", type);
5130         }
5132         return io_write(io, buf, bufsize);
5135 static bool
5136 status_update_file(struct status *status, enum line_type type)
5138         struct io io = {};
5139         bool result;
5141         if (!status_update_prepare(&io, type))
5142                 return FALSE;
5144         result = status_update_write(&io, status, type);
5145         return done_io(&io) && result;
5148 static bool
5149 status_update_files(struct view *view, struct line *line)
5151         char buf[sizeof(view->ref)];
5152         struct io io = {};
5153         bool result = TRUE;
5154         struct line *pos = view->line + view->lines;
5155         int files = 0;
5156         int file, done;
5157         int cursor_y, cursor_x;
5159         if (!status_update_prepare(&io, line->type))
5160                 return FALSE;
5162         for (pos = line; pos < view->line + view->lines && pos->data; pos++)
5163                 files++;
5165         string_copy(buf, view->ref);
5166         getsyx(cursor_y, cursor_x);
5167         for (file = 0, done = 5; result && file < files; line++, file++) {
5168                 int almost_done = file * 100 / files;
5170                 if (almost_done > done) {
5171                         done = almost_done;
5172                         string_format(view->ref, "updating file %u of %u (%d%% done)",
5173                                       file, files, done);
5174                         update_view_title(view);
5175                         setsyx(cursor_y, cursor_x);
5176                         doupdate();
5177                 }
5178                 result = status_update_write(&io, line->data, line->type);
5179         }
5180         string_copy(view->ref, buf);
5182         return done_io(&io) && result;
5185 static bool
5186 status_update(struct view *view)
5188         struct line *line = &view->line[view->lineno];
5190         assert(view->lines);
5192         if (!line->data) {
5193                 /* This should work even for the "On branch" line. */
5194                 if (line < view->line + view->lines && !line[1].data) {
5195                         report("Nothing to update");
5196                         return FALSE;
5197                 }
5199                 if (!status_update_files(view, line + 1)) {
5200                         report("Failed to update file status");
5201                         return FALSE;
5202                 }
5204         } else if (!status_update_file(line->data, line->type)) {
5205                 report("Failed to update file status");
5206                 return FALSE;
5207         }
5209         return TRUE;
5212 static bool
5213 status_revert(struct status *status, enum line_type type, bool has_none)
5215         if (!status || type != LINE_STAT_UNSTAGED) {
5216                 if (type == LINE_STAT_STAGED) {
5217                         report("Cannot revert changes to staged files");
5218                 } else if (type == LINE_STAT_UNTRACKED) {
5219                         report("Cannot revert changes to untracked files");
5220                 } else if (has_none) {
5221                         report("Nothing to revert");
5222                 } else {
5223                         report("Cannot revert changes to multiple files");
5224                 }
5225                 return FALSE;
5227         } else {
5228                 char mode[10] = "100644";
5229                 const char *reset_argv[] = {
5230                         "git", "update-index", "--cacheinfo", mode,
5231                                 status->old.rev, status->old.name, NULL
5232                 };
5233                 const char *checkout_argv[] = {
5234                         "git", "checkout", "--", status->old.name, NULL
5235                 };
5237                 if (!prompt_yesno("Are you sure you want to overwrite any changes?"))
5238                         return FALSE;
5239                 string_format(mode, "%o", status->old.mode);
5240                 return (status->status != 'U' || run_io_fg(reset_argv, opt_cdup)) &&
5241                         run_io_fg(checkout_argv, opt_cdup);
5242         }
5245 static enum request
5246 status_request(struct view *view, enum request request, struct line *line)
5248         struct status *status = line->data;
5250         switch (request) {
5251         case REQ_STATUS_UPDATE:
5252                 if (!status_update(view))
5253                         return REQ_NONE;
5254                 break;
5256         case REQ_STATUS_REVERT:
5257                 if (!status_revert(status, line->type, status_has_none(view, line)))
5258                         return REQ_NONE;
5259                 break;
5261         case REQ_STATUS_MERGE:
5262                 if (!status || status->status != 'U') {
5263                         report("Merging only possible for files with unmerged status ('U').");
5264                         return REQ_NONE;
5265                 }
5266                 open_mergetool(status->new.name);
5267                 break;
5269         case REQ_EDIT:
5270                 if (!status)
5271                         return request;
5272                 if (status->status == 'D') {
5273                         report("File has been deleted.");
5274                         return REQ_NONE;
5275                 }
5277                 open_editor(status->status != '?', status->new.name);
5278                 break;
5280         case REQ_VIEW_BLAME:
5281                 if (status) {
5282                         string_copy(opt_file, status->new.name);
5283                         opt_ref[0] = 0;
5284                 }
5285                 return request;
5287         case REQ_ENTER:
5288                 /* After returning the status view has been split to
5289                  * show the stage view. No further reloading is
5290                  * necessary. */
5291                 return status_enter(view, line);
5293         case REQ_REFRESH:
5294                 /* Simply reload the view. */
5295                 break;
5297         default:
5298                 return request;
5299         }
5301         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
5303         return REQ_NONE;
5306 static void
5307 status_select(struct view *view, struct line *line)
5309         struct status *status = line->data;
5310         char file[SIZEOF_STR] = "all files";
5311         const char *text;
5312         const char *key;
5314         if (status && !string_format(file, "'%s'", status->new.name))
5315                 return;
5317         if (!status && line[1].type == LINE_STAT_NONE)
5318                 line++;
5320         switch (line->type) {
5321         case LINE_STAT_STAGED:
5322                 text = "Press %s to unstage %s for commit";
5323                 break;
5325         case LINE_STAT_UNSTAGED:
5326                 text = "Press %s to stage %s for commit";
5327                 break;
5329         case LINE_STAT_UNTRACKED:
5330                 text = "Press %s to stage %s for addition";
5331                 break;
5333         case LINE_STAT_HEAD:
5334         case LINE_STAT_NONE:
5335                 text = "Nothing to update";
5336                 break;
5338         default:
5339                 die("line type %d not handled in switch", line->type);
5340         }
5342         if (status && status->status == 'U') {
5343                 text = "Press %s to resolve conflict in %s";
5344                 key = get_key(REQ_STATUS_MERGE);
5346         } else {
5347                 key = get_key(REQ_STATUS_UPDATE);
5348         }
5350         string_format(view->ref, text, key, file);
5353 static bool
5354 status_grep(struct view *view, struct line *line)
5356         struct status *status = line->data;
5358         if (status) {
5359                 const char buf[2] = { status->status, 0 };
5360                 const char *text[] = { status->new.name, buf, NULL };
5362                 return grep_text(view, text);
5363         }
5365         return FALSE;
5368 static struct view_ops status_ops = {
5369         "file",
5370         NULL,
5371         status_open,
5372         NULL,
5373         status_draw,
5374         status_request,
5375         status_grep,
5376         status_select,
5377 };
5380 static bool
5381 stage_diff_write(struct io *io, struct line *line, struct line *end)
5383         while (line < end) {
5384                 if (!io_write(io, line->data, strlen(line->data)) ||
5385                     !io_write(io, "\n", 1))
5386                         return FALSE;
5387                 line++;
5388                 if (line->type == LINE_DIFF_CHUNK ||
5389                     line->type == LINE_DIFF_HEADER)
5390                         break;
5391         }
5393         return TRUE;
5396 static struct line *
5397 stage_diff_find(struct view *view, struct line *line, enum line_type type)
5399         for (; view->line < line; line--)
5400                 if (line->type == type)
5401                         return line;
5403         return NULL;
5406 static bool
5407 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
5409         const char *apply_argv[SIZEOF_ARG] = {
5410                 "git", "apply", "--whitespace=nowarn", NULL
5411         };
5412         struct line *diff_hdr;
5413         struct io io = {};
5414         int argc = 3;
5416         diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
5417         if (!diff_hdr)
5418                 return FALSE;
5420         if (!revert)
5421                 apply_argv[argc++] = "--cached";
5422         if (revert || stage_line_type == LINE_STAT_STAGED)
5423                 apply_argv[argc++] = "-R";
5424         apply_argv[argc++] = "-";
5425         apply_argv[argc++] = NULL;
5426         if (!run_io(&io, apply_argv, opt_cdup, IO_WR))
5427                 return FALSE;
5429         if (!stage_diff_write(&io, diff_hdr, chunk) ||
5430             !stage_diff_write(&io, chunk, view->line + view->lines))
5431                 chunk = NULL;
5433         done_io(&io);
5434         run_io_bg(update_index_argv);
5436         return chunk ? TRUE : FALSE;
5439 static bool
5440 stage_update(struct view *view, struct line *line)
5442         struct line *chunk = NULL;
5444         if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
5445                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
5447         if (chunk) {
5448                 if (!stage_apply_chunk(view, chunk, FALSE)) {
5449                         report("Failed to apply chunk");
5450                         return FALSE;
5451                 }
5453         } else if (!stage_status.status) {
5454                 view = VIEW(REQ_VIEW_STATUS);
5456                 for (line = view->line; line < view->line + view->lines; line++)
5457                         if (line->type == stage_line_type)
5458                                 break;
5460                 if (!status_update_files(view, line + 1)) {
5461                         report("Failed to update files");
5462                         return FALSE;
5463                 }
5465         } else if (!status_update_file(&stage_status, stage_line_type)) {
5466                 report("Failed to update file");
5467                 return FALSE;
5468         }
5470         return TRUE;
5473 static bool
5474 stage_revert(struct view *view, struct line *line)
5476         struct line *chunk = NULL;
5478         if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
5479                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
5481         if (chunk) {
5482                 if (!prompt_yesno("Are you sure you want to revert changes?"))
5483                         return FALSE;
5485                 if (!stage_apply_chunk(view, chunk, TRUE)) {
5486                         report("Failed to revert chunk");
5487                         return FALSE;
5488                 }
5489                 return TRUE;
5491         } else {
5492                 return status_revert(stage_status.status ? &stage_status : NULL,
5493                                      stage_line_type, FALSE);
5494         }
5498 static void
5499 stage_next(struct view *view, struct line *line)
5501         int i;
5503         if (!stage_chunks) {
5504                 for (line = view->line; line < view->line + view->lines; line++) {
5505                         if (line->type != LINE_DIFF_CHUNK)
5506                                 continue;
5508                         if (!realloc_ints(&stage_chunk, stage_chunks, 1)) {
5509                                 report("Allocation failure");
5510                                 return;
5511                         }
5513                         stage_chunk[stage_chunks++] = line - view->line;
5514                 }
5515         }
5517         for (i = 0; i < stage_chunks; i++) {
5518                 if (stage_chunk[i] > view->lineno) {
5519                         do_scroll_view(view, stage_chunk[i] - view->lineno);
5520                         report("Chunk %d of %d", i + 1, stage_chunks);
5521                         return;
5522                 }
5523         }
5525         report("No next chunk found");
5528 static enum request
5529 stage_request(struct view *view, enum request request, struct line *line)
5531         switch (request) {
5532         case REQ_STATUS_UPDATE:
5533                 if (!stage_update(view, line))
5534                         return REQ_NONE;
5535                 break;
5537         case REQ_STATUS_REVERT:
5538                 if (!stage_revert(view, line))
5539                         return REQ_NONE;
5540                 break;
5542         case REQ_STAGE_NEXT:
5543                 if (stage_line_type == LINE_STAT_UNTRACKED) {
5544                         report("File is untracked; press %s to add",
5545                                get_key(REQ_STATUS_UPDATE));
5546                         return REQ_NONE;
5547                 }
5548                 stage_next(view, line);
5549                 return REQ_NONE;
5551         case REQ_EDIT:
5552                 if (!stage_status.new.name[0])
5553                         return request;
5554                 if (stage_status.status == 'D') {
5555                         report("File has been deleted.");
5556                         return REQ_NONE;
5557                 }
5559                 open_editor(stage_status.status != '?', stage_status.new.name);
5560                 break;
5562         case REQ_REFRESH:
5563                 /* Reload everything ... */
5564                 break;
5566         case REQ_VIEW_BLAME:
5567                 if (stage_status.new.name[0]) {
5568                         string_copy(opt_file, stage_status.new.name);
5569                         opt_ref[0] = 0;
5570                 }
5571                 return request;
5573         case REQ_ENTER:
5574                 return pager_request(view, request, line);
5576         default:
5577                 return request;
5578         }
5580         VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
5581         open_view(view, REQ_VIEW_STATUS, OPEN_REFRESH);
5583         /* Check whether the staged entry still exists, and close the
5584          * stage view if it doesn't. */
5585         if (!status_exists(&stage_status, stage_line_type)) {
5586                 status_restore(VIEW(REQ_VIEW_STATUS));
5587                 return REQ_VIEW_CLOSE;
5588         }
5590         if (stage_line_type == LINE_STAT_UNTRACKED) {
5591                 if (!suffixcmp(stage_status.new.name, -1, "/")) {
5592                         report("Cannot display a directory");
5593                         return REQ_NONE;
5594                 }
5596                 if (!prepare_update_file(view, stage_status.new.name)) {
5597                         report("Failed to open file: %s", strerror(errno));
5598                         return REQ_NONE;
5599                 }
5600         }
5601         open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
5603         return REQ_NONE;
5606 static struct view_ops stage_ops = {
5607         "line",
5608         NULL,
5609         NULL,
5610         pager_read,
5611         pager_draw,
5612         stage_request,
5613         pager_grep,
5614         pager_select,
5615 };
5618 /*
5619  * Revision graph
5620  */
5622 struct commit {
5623         char id[SIZEOF_REV];            /* SHA1 ID. */
5624         char title[128];                /* First line of the commit message. */
5625         const char *author;             /* Author of the commit. */
5626         time_t time;                    /* Date from the author ident. */
5627         struct ref **refs;              /* Repository references. */
5628         chtype graph[SIZEOF_REVGRAPH];  /* Ancestry chain graphics. */
5629         size_t graph_size;              /* The width of the graph array. */
5630         bool has_parents;               /* Rewritten --parents seen. */
5631 };
5633 /* Size of rev graph with no  "padding" columns */
5634 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
5636 struct rev_graph {
5637         struct rev_graph *prev, *next, *parents;
5638         char rev[SIZEOF_REVITEMS][SIZEOF_REV];
5639         size_t size;
5640         struct commit *commit;
5641         size_t pos;
5642         unsigned int boundary:1;
5643 };
5645 /* Parents of the commit being visualized. */
5646 static struct rev_graph graph_parents[4];
5648 /* The current stack of revisions on the graph. */
5649 static struct rev_graph graph_stacks[4] = {
5650         { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
5651         { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
5652         { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
5653         { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
5654 };
5656 static inline bool
5657 graph_parent_is_merge(struct rev_graph *graph)
5659         return graph->parents->size > 1;
5662 static inline void
5663 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
5665         struct commit *commit = graph->commit;
5667         if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
5668                 commit->graph[commit->graph_size++] = symbol;
5671 static void
5672 clear_rev_graph(struct rev_graph *graph)
5674         graph->boundary = 0;
5675         graph->size = graph->pos = 0;
5676         graph->commit = NULL;
5677         memset(graph->parents, 0, sizeof(*graph->parents));
5680 static void
5681 done_rev_graph(struct rev_graph *graph)
5683         if (graph_parent_is_merge(graph) &&
5684             graph->pos < graph->size - 1 &&
5685             graph->next->size == graph->size + graph->parents->size - 1) {
5686                 size_t i = graph->pos + graph->parents->size - 1;
5688                 graph->commit->graph_size = i * 2;
5689                 while (i < graph->next->size - 1) {
5690                         append_to_rev_graph(graph, ' ');
5691                         append_to_rev_graph(graph, '\\');
5692                         i++;
5693                 }
5694         }
5696         clear_rev_graph(graph);
5699 static void
5700 push_rev_graph(struct rev_graph *graph, const char *parent)
5702         int i;
5704         /* "Collapse" duplicate parents lines.
5705          *
5706          * FIXME: This needs to also update update the drawn graph but
5707          * for now it just serves as a method for pruning graph lines. */
5708         for (i = 0; i < graph->size; i++)
5709                 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
5710                         return;
5712         if (graph->size < SIZEOF_REVITEMS) {
5713                 string_copy_rev(graph->rev[graph->size++], parent);
5714         }
5717 static chtype
5718 get_rev_graph_symbol(struct rev_graph *graph)
5720         chtype symbol;
5722         if (graph->boundary)
5723                 symbol = REVGRAPH_BOUND;
5724         else if (graph->parents->size == 0)
5725                 symbol = REVGRAPH_INIT;
5726         else if (graph_parent_is_merge(graph))
5727                 symbol = REVGRAPH_MERGE;
5728         else if (graph->pos >= graph->size)
5729                 symbol = REVGRAPH_BRANCH;
5730         else
5731                 symbol = REVGRAPH_COMMIT;
5733         return symbol;
5736 static void
5737 draw_rev_graph(struct rev_graph *graph)
5739         struct rev_filler {
5740                 chtype separator, line;
5741         };
5742         enum { DEFAULT, RSHARP, RDIAG, LDIAG };
5743         static struct rev_filler fillers[] = {
5744                 { ' ',  '|' },
5745                 { '`',  '.' },
5746                 { '\'', ' ' },
5747                 { '/',  ' ' },
5748         };
5749         chtype symbol = get_rev_graph_symbol(graph);
5750         struct rev_filler *filler;
5751         size_t i;
5753         if (opt_line_graphics)
5754                 fillers[DEFAULT].line = line_graphics[LINE_GRAPHIC_VLINE];
5756         filler = &fillers[DEFAULT];
5758         for (i = 0; i < graph->pos; i++) {
5759                 append_to_rev_graph(graph, filler->line);
5760                 if (graph_parent_is_merge(graph->prev) &&
5761                     graph->prev->pos == i)
5762                         filler = &fillers[RSHARP];
5764                 append_to_rev_graph(graph, filler->separator);
5765         }
5767         /* Place the symbol for this revision. */
5768         append_to_rev_graph(graph, symbol);
5770         if (graph->prev->size > graph->size)
5771                 filler = &fillers[RDIAG];
5772         else
5773                 filler = &fillers[DEFAULT];
5775         i++;
5777         for (; i < graph->size; i++) {
5778                 append_to_rev_graph(graph, filler->separator);
5779                 append_to_rev_graph(graph, filler->line);
5780                 if (graph_parent_is_merge(graph->prev) &&
5781                     i < graph->prev->pos + graph->parents->size)
5782                         filler = &fillers[RSHARP];
5783                 if (graph->prev->size > graph->size)
5784                         filler = &fillers[LDIAG];
5785         }
5787         if (graph->prev->size > graph->size) {
5788                 append_to_rev_graph(graph, filler->separator);
5789                 if (filler->line != ' ')
5790                         append_to_rev_graph(graph, filler->line);
5791         }
5794 /* Prepare the next rev graph */
5795 static void
5796 prepare_rev_graph(struct rev_graph *graph)
5798         size_t i;
5800         /* First, traverse all lines of revisions up to the active one. */
5801         for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
5802                 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
5803                         break;
5805                 push_rev_graph(graph->next, graph->rev[graph->pos]);
5806         }
5808         /* Interleave the new revision parent(s). */
5809         for (i = 0; !graph->boundary && i < graph->parents->size; i++)
5810                 push_rev_graph(graph->next, graph->parents->rev[i]);
5812         /* Lastly, put any remaining revisions. */
5813         for (i = graph->pos + 1; i < graph->size; i++)
5814                 push_rev_graph(graph->next, graph->rev[i]);
5817 static void
5818 update_rev_graph(struct view *view, struct rev_graph *graph)
5820         /* If this is the finalizing update ... */
5821         if (graph->commit)
5822                 prepare_rev_graph(graph);
5824         /* Graph visualization needs a one rev look-ahead,
5825          * so the first update doesn't visualize anything. */
5826         if (!graph->prev->commit)
5827                 return;
5829         if (view->lines > 2)
5830                 view->line[view->lines - 3].dirty = 1;
5831         if (view->lines > 1)
5832                 view->line[view->lines - 2].dirty = 1;
5833         draw_rev_graph(graph->prev);
5834         done_rev_graph(graph->prev->prev);
5838 /*
5839  * Main view backend
5840  */
5842 static const char *main_argv[SIZEOF_ARG] = {
5843         "git", "log", "--no-color", "--pretty=raw", "--parents",
5844                       "--topo-order", "%(head)", NULL
5845 };
5847 static bool
5848 main_draw(struct view *view, struct line *line, unsigned int lineno)
5850         struct commit *commit = line->data;
5852         if (!commit->author)
5853                 return FALSE;
5855         if (opt_date && draw_date(view, &commit->time))
5856                 return TRUE;
5858         if (opt_author && draw_author(view, commit->author))
5859                 return TRUE;
5861         if (opt_rev_graph && commit->graph_size &&
5862             draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
5863                 return TRUE;
5865         if (opt_show_refs && commit->refs) {
5866                 size_t i = 0;
5868                 do {
5869                         enum line_type type;
5871                         if (commit->refs[i]->head)
5872                                 type = LINE_MAIN_HEAD;
5873                         else if (commit->refs[i]->ltag)
5874                                 type = LINE_MAIN_LOCAL_TAG;
5875                         else if (commit->refs[i]->tag)
5876                                 type = LINE_MAIN_TAG;
5877                         else if (commit->refs[i]->tracked)
5878                                 type = LINE_MAIN_TRACKED;
5879                         else if (commit->refs[i]->remote)
5880                                 type = LINE_MAIN_REMOTE;
5881                         else
5882                                 type = LINE_MAIN_REF;
5884                         if (draw_text(view, type, "[", TRUE) ||
5885                             draw_text(view, type, commit->refs[i]->name, TRUE) ||
5886                             draw_text(view, type, "]", TRUE))
5887                                 return TRUE;
5889                         if (draw_text(view, LINE_DEFAULT, " ", TRUE))
5890                                 return TRUE;
5891                 } while (commit->refs[i++]->next);
5892         }
5894         draw_text(view, LINE_DEFAULT, commit->title, TRUE);
5895         return TRUE;
5898 /* Reads git log --pretty=raw output and parses it into the commit struct. */
5899 static bool
5900 main_read(struct view *view, char *line)
5902         static struct rev_graph *graph = graph_stacks;
5903         enum line_type type;
5904         struct commit *commit;
5906         if (!line) {
5907                 int i;
5909                 if (!view->lines && !view->parent)
5910                         die("No revisions match the given arguments.");
5911                 if (view->lines > 0) {
5912                         commit = view->line[view->lines - 1].data;
5913                         view->line[view->lines - 1].dirty = 1;
5914                         if (!commit->author) {
5915                                 view->lines--;
5916                                 free(commit);
5917                                 graph->commit = NULL;
5918                         }
5919                 }
5920                 update_rev_graph(view, graph);
5922                 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
5923                         clear_rev_graph(&graph_stacks[i]);
5924                 return TRUE;
5925         }
5927         type = get_line_type(line);
5928         if (type == LINE_COMMIT) {
5929                 commit = calloc(1, sizeof(struct commit));
5930                 if (!commit)
5931                         return FALSE;
5933                 line += STRING_SIZE("commit ");
5934                 if (*line == '-') {
5935                         graph->boundary = 1;
5936                         line++;
5937                 }
5939                 string_copy_rev(commit->id, line);
5940                 commit->refs = get_refs(commit->id);
5941                 graph->commit = commit;
5942                 add_line_data(view, commit, LINE_MAIN_COMMIT);
5944                 while ((line = strchr(line, ' '))) {
5945                         line++;
5946                         push_rev_graph(graph->parents, line);
5947                         commit->has_parents = TRUE;
5948                 }
5949                 return TRUE;
5950         }
5952         if (!view->lines)
5953                 return TRUE;
5954         commit = view->line[view->lines - 1].data;
5956         switch (type) {
5957         case LINE_PARENT:
5958                 if (commit->has_parents)
5959                         break;
5960                 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
5961                 break;
5963         case LINE_AUTHOR:
5964                 parse_author_line(line + STRING_SIZE("author "),
5965                                   &commit->author, &commit->time);
5966                 update_rev_graph(view, graph);
5967                 graph = graph->next;
5968                 break;
5970         default:
5971                 /* Fill in the commit title if it has not already been set. */
5972                 if (commit->title[0])
5973                         break;
5975                 /* Require titles to start with a non-space character at the
5976                  * offset used by git log. */
5977                 if (strncmp(line, "    ", 4))
5978                         break;
5979                 line += 4;
5980                 /* Well, if the title starts with a whitespace character,
5981                  * try to be forgiving.  Otherwise we end up with no title. */
5982                 while (isspace(*line))
5983                         line++;
5984                 if (*line == '\0')
5985                         break;
5986                 /* FIXME: More graceful handling of titles; append "..." to
5987                  * shortened titles, etc. */
5989                 string_expand(commit->title, sizeof(commit->title), line, 1);
5990                 view->line[view->lines - 1].dirty = 1;
5991         }
5993         return TRUE;
5996 static enum request
5997 main_request(struct view *view, enum request request, struct line *line)
5999         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
6001         switch (request) {
6002         case REQ_ENTER:
6003                 open_view(view, REQ_VIEW_DIFF, flags);
6004                 break;
6005         case REQ_REFRESH:
6006                 load_refs();
6007                 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
6008                 break;
6009         default:
6010                 return request;
6011         }
6013         return REQ_NONE;
6016 static bool
6017 grep_refs(struct ref **refs, regex_t *regex)
6019         regmatch_t pmatch;
6020         size_t i = 0;
6022         if (!opt_show_refs || !refs)
6023                 return FALSE;
6024         do {
6025                 if (regexec(regex, refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
6026                         return TRUE;
6027         } while (refs[i++]->next);
6029         return FALSE;
6032 static bool
6033 main_grep(struct view *view, struct line *line)
6035         struct commit *commit = line->data;
6036         const char *text[] = {
6037                 commit->title,
6038                 opt_author ? commit->author : "",
6039                 opt_date ? mkdate(&commit->time) : "",
6040                 NULL
6041         };
6043         return grep_text(view, text) || grep_refs(commit->refs, view->regex);
6046 static void
6047 main_select(struct view *view, struct line *line)
6049         struct commit *commit = line->data;
6051         string_copy_rev(view->ref, commit->id);
6052         string_copy_rev(ref_commit, view->ref);
6055 static struct view_ops main_ops = {
6056         "commit",
6057         main_argv,
6058         NULL,
6059         main_read,
6060         main_draw,
6061         main_request,
6062         main_grep,
6063         main_select,
6064 };
6067 /*
6068  * Unicode / UTF-8 handling
6069  *
6070  * NOTE: Much of the following code for dealing with Unicode is derived from
6071  * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
6072  * src/intl/charset.c from the UTF-8 branch commit elinks-0.11.0-g31f2c28.
6073  */
6075 static inline int
6076 unicode_width(unsigned long c)
6078         if (c >= 0x1100 &&
6079            (c <= 0x115f                         /* Hangul Jamo */
6080             || c == 0x2329
6081             || c == 0x232a
6082             || (c >= 0x2e80  && c <= 0xa4cf && c != 0x303f)
6083                                                 /* CJK ... Yi */
6084             || (c >= 0xac00  && c <= 0xd7a3)    /* Hangul Syllables */
6085             || (c >= 0xf900  && c <= 0xfaff)    /* CJK Compatibility Ideographs */
6086             || (c >= 0xfe30  && c <= 0xfe6f)    /* CJK Compatibility Forms */
6087             || (c >= 0xff00  && c <= 0xff60)    /* Fullwidth Forms */
6088             || (c >= 0xffe0  && c <= 0xffe6)
6089             || (c >= 0x20000 && c <= 0x2fffd)
6090             || (c >= 0x30000 && c <= 0x3fffd)))
6091                 return 2;
6093         if (c == '\t')
6094                 return opt_tab_size;
6096         return 1;
6099 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
6100  * Illegal bytes are set one. */
6101 static const unsigned char utf8_bytes[256] = {
6102         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,
6103         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,
6104         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,
6105         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,
6106         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,
6107         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,
6108         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,
6109         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,
6110 };
6112 /* Decode UTF-8 multi-byte representation into a Unicode character. */
6113 static inline unsigned long
6114 utf8_to_unicode(const char *string, size_t length)
6116         unsigned long unicode;
6118         switch (length) {
6119         case 1:
6120                 unicode  =   string[0];
6121                 break;
6122         case 2:
6123                 unicode  =  (string[0] & 0x1f) << 6;
6124                 unicode +=  (string[1] & 0x3f);
6125                 break;
6126         case 3:
6127                 unicode  =  (string[0] & 0x0f) << 12;
6128                 unicode += ((string[1] & 0x3f) << 6);
6129                 unicode +=  (string[2] & 0x3f);
6130                 break;
6131         case 4:
6132                 unicode  =  (string[0] & 0x0f) << 18;
6133                 unicode += ((string[1] & 0x3f) << 12);
6134                 unicode += ((string[2] & 0x3f) << 6);
6135                 unicode +=  (string[3] & 0x3f);
6136                 break;
6137         case 5:
6138                 unicode  =  (string[0] & 0x0f) << 24;
6139                 unicode += ((string[1] & 0x3f) << 18);
6140                 unicode += ((string[2] & 0x3f) << 12);
6141                 unicode += ((string[3] & 0x3f) << 6);
6142                 unicode +=  (string[4] & 0x3f);
6143                 break;
6144         case 6:
6145                 unicode  =  (string[0] & 0x01) << 30;
6146                 unicode += ((string[1] & 0x3f) << 24);
6147                 unicode += ((string[2] & 0x3f) << 18);
6148                 unicode += ((string[3] & 0x3f) << 12);
6149                 unicode += ((string[4] & 0x3f) << 6);
6150                 unicode +=  (string[5] & 0x3f);
6151                 break;
6152         default:
6153                 die("Invalid Unicode length");
6154         }
6156         /* Invalid characters could return the special 0xfffd value but NUL
6157          * should be just as good. */
6158         return unicode > 0xffff ? 0 : unicode;
6161 /* Calculates how much of string can be shown within the given maximum width
6162  * and sets trimmed parameter to non-zero value if all of string could not be
6163  * shown. If the reserve flag is TRUE, it will reserve at least one
6164  * trailing character, which can be useful when drawing a delimiter.
6165  *
6166  * Returns the number of bytes to output from string to satisfy max_width. */
6167 static size_t
6168 utf8_length(const char **start, size_t skip, int *width, size_t max_width, int *trimmed, bool reserve)
6170         const char *string = *start;
6171         const char *end = strchr(string, '\0');
6172         unsigned char last_bytes = 0;
6173         size_t last_ucwidth = 0;
6175         *width = 0;
6176         *trimmed = 0;
6178         while (string < end) {
6179                 int c = *(unsigned char *) string;
6180                 unsigned char bytes = utf8_bytes[c];
6181                 size_t ucwidth;
6182                 unsigned long unicode;
6184                 if (string + bytes > end)
6185                         break;
6187                 /* Change representation to figure out whether
6188                  * it is a single- or double-width character. */
6190                 unicode = utf8_to_unicode(string, bytes);
6191                 /* FIXME: Graceful handling of invalid Unicode character. */
6192                 if (!unicode)
6193                         break;
6195                 ucwidth = unicode_width(unicode);
6196                 if (skip > 0) {
6197                         skip -= ucwidth <= skip ? ucwidth : skip;
6198                         *start += bytes;
6199                 }
6200                 *width  += ucwidth;
6201                 if (*width > max_width) {
6202                         *trimmed = 1;
6203                         *width -= ucwidth;
6204                         if (reserve && *width == max_width) {
6205                                 string -= last_bytes;
6206                                 *width -= last_ucwidth;
6207                         }
6208                         break;
6209                 }
6211                 string  += bytes;
6212                 last_bytes = ucwidth ? bytes : 0;
6213                 last_ucwidth = ucwidth;
6214         }
6216         return string - *start;
6220 /*
6221  * Status management
6222  */
6224 /* Whether or not the curses interface has been initialized. */
6225 static bool cursed = FALSE;
6227 /* Terminal hacks and workarounds. */
6228 static bool use_scroll_redrawwin;
6229 static bool use_scroll_status_wclear;
6231 /* The status window is used for polling keystrokes. */
6232 static WINDOW *status_win;
6234 /* Reading from the prompt? */
6235 static bool input_mode = FALSE;
6237 static bool status_empty = FALSE;
6239 /* Update status and title window. */
6240 static void
6241 report(const char *msg, ...)
6243         struct view *view = display[current_view];
6245         if (input_mode)
6246                 return;
6248         if (!view) {
6249                 char buf[SIZEOF_STR];
6250                 va_list args;
6252                 va_start(args, msg);
6253                 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
6254                         buf[sizeof(buf) - 1] = 0;
6255                         buf[sizeof(buf) - 2] = '.';
6256                         buf[sizeof(buf) - 3] = '.';
6257                         buf[sizeof(buf) - 4] = '.';
6258                 }
6259                 va_end(args);
6260                 die("%s", buf);
6261         }
6263         if (!status_empty || *msg) {
6264                 va_list args;
6266                 va_start(args, msg);
6268                 wmove(status_win, 0, 0);
6269                 if (view->has_scrolled && use_scroll_status_wclear)
6270                         wclear(status_win);
6271                 if (*msg) {
6272                         vwprintw(status_win, msg, args);
6273                         status_empty = FALSE;
6274                 } else {
6275                         status_empty = TRUE;
6276                 }
6277                 wclrtoeol(status_win);
6278                 wnoutrefresh(status_win);
6280                 va_end(args);
6281         }
6283         update_view_title(view);
6286 /* Controls when nodelay should be in effect when polling user input. */
6287 static void
6288 set_nonblocking_input(bool loading)
6290         static unsigned int loading_views;
6292         if ((loading == FALSE && loading_views-- == 1) ||
6293             (loading == TRUE  && loading_views++ == 0))
6294                 nodelay(status_win, loading);
6297 static void
6298 init_display(void)
6300         const char *term;
6301         int x, y;
6303         /* Initialize the curses library */
6304         if (isatty(STDIN_FILENO)) {
6305                 cursed = !!initscr();
6306                 opt_tty = stdin;
6307         } else {
6308                 /* Leave stdin and stdout alone when acting as a pager. */
6309                 opt_tty = fopen("/dev/tty", "r+");
6310                 if (!opt_tty)
6311                         die("Failed to open /dev/tty");
6312                 cursed = !!newterm(NULL, opt_tty, opt_tty);
6313         }
6315         if (!cursed)
6316                 die("Failed to initialize curses");
6318         nonl();         /* Disable conversion and detect newlines from input. */
6319         cbreak();       /* Take input chars one at a time, no wait for \n */
6320         noecho();       /* Don't echo input */
6321         leaveok(stdscr, FALSE);
6323         if (has_colors())
6324                 init_colors();
6326         getmaxyx(stdscr, y, x);
6327         status_win = newwin(1, 0, y - 1, 0);
6328         if (!status_win)
6329                 die("Failed to create status window");
6331         /* Enable keyboard mapping */
6332         keypad(status_win, TRUE);
6333         wbkgdset(status_win, get_line_attr(LINE_STATUS));
6335         TABSIZE = opt_tab_size;
6336         if (opt_line_graphics) {
6337                 line_graphics[LINE_GRAPHIC_VLINE] = ACS_VLINE;
6338         }
6340         term = getenv("XTERM_VERSION") ? NULL : getenv("COLORTERM");
6341         if (term && !strcmp(term, "gnome-terminal")) {
6342                 /* In the gnome-terminal-emulator, the message from
6343                  * scrolling up one line when impossible followed by
6344                  * scrolling down one line causes corruption of the
6345                  * status line. This is fixed by calling wclear. */
6346                 use_scroll_status_wclear = TRUE;
6347                 use_scroll_redrawwin = FALSE;
6349         } else if (term && !strcmp(term, "xrvt-xpm")) {
6350                 /* No problems with full optimizations in xrvt-(unicode)
6351                  * and aterm. */
6352                 use_scroll_status_wclear = use_scroll_redrawwin = FALSE;
6354         } else {
6355                 /* When scrolling in (u)xterm the last line in the
6356                  * scrolling direction will update slowly. */
6357                 use_scroll_redrawwin = TRUE;
6358                 use_scroll_status_wclear = FALSE;
6359         }
6362 static int
6363 get_input(int prompt_position)
6365         struct view *view;
6366         int i, key, cursor_y, cursor_x;
6368         if (prompt_position)
6369                 input_mode = TRUE;
6371         while (TRUE) {
6372                 foreach_view (view, i) {
6373                         update_view(view);
6374                         if (view_is_displayed(view) && view->has_scrolled &&
6375                             use_scroll_redrawwin)
6376                                 redrawwin(view->win);
6377                         view->has_scrolled = FALSE;
6378                 }
6380                 /* Update the cursor position. */
6381                 if (prompt_position) {
6382                         getbegyx(status_win, cursor_y, cursor_x);
6383                         cursor_x = prompt_position;
6384                 } else {
6385                         view = display[current_view];
6386                         getbegyx(view->win, cursor_y, cursor_x);
6387                         cursor_x = view->width - 1;
6388                         cursor_y += view->lineno - view->offset;
6389                 }
6390                 setsyx(cursor_y, cursor_x);
6392                 /* Refresh, accept single keystroke of input */
6393                 doupdate();
6394                 key = wgetch(status_win);
6396                 /* wgetch() with nodelay() enabled returns ERR when
6397                  * there's no input. */
6398                 if (key == ERR) {
6400                 } else if (key == KEY_RESIZE) {
6401                         int height, width;
6403                         getmaxyx(stdscr, height, width);
6405                         wresize(status_win, 1, width);
6406                         mvwin(status_win, height - 1, 0);
6407                         wnoutrefresh(status_win);
6408                         resize_display();
6409                         redraw_display(TRUE);
6411                 } else {
6412                         input_mode = FALSE;
6413                         return key;
6414                 }
6415         }
6418 static char *
6419 prompt_input(const char *prompt, input_handler handler, void *data)
6421         enum input_status status = INPUT_OK;
6422         static char buf[SIZEOF_STR];
6423         size_t pos = 0;
6425         buf[pos] = 0;
6427         while (status == INPUT_OK || status == INPUT_SKIP) {
6428                 int key;
6430                 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
6431                 wclrtoeol(status_win);
6433                 key = get_input(pos + 1);
6434                 switch (key) {
6435                 case KEY_RETURN:
6436                 case KEY_ENTER:
6437                 case '\n':
6438                         status = pos ? INPUT_STOP : INPUT_CANCEL;
6439                         break;
6441                 case KEY_BACKSPACE:
6442                         if (pos > 0)
6443                                 buf[--pos] = 0;
6444                         else
6445                                 status = INPUT_CANCEL;
6446                         break;
6448                 case KEY_ESC:
6449                         status = INPUT_CANCEL;
6450                         break;
6452                 default:
6453                         if (pos >= sizeof(buf)) {
6454                                 report("Input string too long");
6455                                 return NULL;
6456                         }
6458                         status = handler(data, buf, key);
6459                         if (status == INPUT_OK)
6460                                 buf[pos++] = (char) key;
6461                 }
6462         }
6464         /* Clear the status window */
6465         status_empty = FALSE;
6466         report("");
6468         if (status == INPUT_CANCEL)
6469                 return NULL;
6471         buf[pos++] = 0;
6473         return buf;
6476 static enum input_status
6477 prompt_yesno_handler(void *data, char *buf, int c)
6479         if (c == 'y' || c == 'Y')
6480                 return INPUT_STOP;
6481         if (c == 'n' || c == 'N')
6482                 return INPUT_CANCEL;
6483         return INPUT_SKIP;
6486 static bool
6487 prompt_yesno(const char *prompt)
6489         char prompt2[SIZEOF_STR];
6491         if (!string_format(prompt2, "%s [Yy/Nn]", prompt))
6492                 return FALSE;
6494         return !!prompt_input(prompt2, prompt_yesno_handler, NULL);
6497 static enum input_status
6498 read_prompt_handler(void *data, char *buf, int c)
6500         return isprint(c) ? INPUT_OK : INPUT_SKIP;
6503 static char *
6504 read_prompt(const char *prompt)
6506         return prompt_input(prompt, read_prompt_handler, NULL);
6509 /*
6510  * Repository properties
6511  */
6513 static struct ref *refs = NULL;
6514 static size_t refs_size = 0;
6516 /* Id <-> ref store */
6517 static struct ref ***id_refs = NULL;
6518 static size_t id_refs_size = 0;
6520 DEFINE_ALLOCATOR(realloc_refs, struct ref, 256)
6521 DEFINE_ALLOCATOR(realloc_refs_list, struct ref *, 8)
6522 DEFINE_ALLOCATOR(realloc_refs_lists, struct ref **, 8)
6524 static int
6525 compare_refs(const void *ref1_, const void *ref2_)
6527         const struct ref *ref1 = *(const struct ref **)ref1_;
6528         const struct ref *ref2 = *(const struct ref **)ref2_;
6530         if (ref1->tag != ref2->tag)
6531                 return ref2->tag - ref1->tag;
6532         if (ref1->ltag != ref2->ltag)
6533                 return ref2->ltag - ref2->ltag;
6534         if (ref1->head != ref2->head)
6535                 return ref2->head - ref1->head;
6536         if (ref1->tracked != ref2->tracked)
6537                 return ref2->tracked - ref1->tracked;
6538         if (ref1->remote != ref2->remote)
6539                 return ref2->remote - ref1->remote;
6540         return strcmp(ref1->name, ref2->name);
6543 static struct ref **
6544 get_refs(const char *id)
6546         struct ref **ref_list = NULL;
6547         size_t ref_list_size = 0;
6548         size_t i;
6550         for (i = 0; i < id_refs_size; i++)
6551                 if (!strcmp(id, id_refs[i][0]->id))
6552                         return id_refs[i];
6554         if (!realloc_refs_lists(&id_refs, id_refs_size, 1))
6555                 return NULL;
6557         for (i = 0; i < refs_size; i++) {
6558                 if (strcmp(id, refs[i].id))
6559                         continue;
6561                 if (!realloc_refs_list(&ref_list, ref_list_size, 1))
6562                         return ref_list;
6564                 ref_list[ref_list_size] = &refs[i];
6565                 /* XXX: The properties of the commit chains ensures that we can
6566                  * safely modify the shared ref. The repo references will
6567                  * always be similar for the same id. */
6568                 ref_list[ref_list_size]->next = 1;
6569                 ref_list_size++;
6570         }
6572         if (ref_list) {
6573                 qsort(ref_list, ref_list_size, sizeof(*ref_list), compare_refs);
6574                 ref_list[ref_list_size - 1]->next = 0;
6575                 id_refs[id_refs_size++] = ref_list;
6576         }
6578         return ref_list;
6581 static int
6582 read_ref(char *id, size_t idlen, char *name, size_t namelen)
6584         struct ref *ref;
6585         bool tag = FALSE;
6586         bool ltag = FALSE;
6587         bool remote = FALSE;
6588         bool tracked = FALSE;
6589         bool check_replace = FALSE;
6590         bool head = FALSE;
6592         if (!prefixcmp(name, "refs/tags/")) {
6593                 if (!suffixcmp(name, namelen, "^{}")) {
6594                         namelen -= 3;
6595                         name[namelen] = 0;
6596                         if (refs_size > 0 && refs[refs_size - 1].ltag == TRUE)
6597                                 check_replace = TRUE;
6598                 } else {
6599                         ltag = TRUE;
6600                 }
6602                 tag = TRUE;
6603                 namelen -= STRING_SIZE("refs/tags/");
6604                 name    += STRING_SIZE("refs/tags/");
6606         } else if (!prefixcmp(name, "refs/remotes/")) {
6607                 remote = TRUE;
6608                 namelen -= STRING_SIZE("refs/remotes/");
6609                 name    += STRING_SIZE("refs/remotes/");
6610                 tracked  = !strcmp(opt_remote, name);
6612         } else if (!prefixcmp(name, "refs/heads/")) {
6613                 namelen -= STRING_SIZE("refs/heads/");
6614                 name    += STRING_SIZE("refs/heads/");
6615                 head     = !strncmp(opt_head, name, namelen);
6617         } else if (!strcmp(name, "HEAD")) {
6618                 string_ncopy(opt_head_rev, id, idlen);
6619                 return OK;
6620         }
6622         if (check_replace && !strcmp(name, refs[refs_size - 1].name)) {
6623                 /* it's an annotated tag, replace the previous SHA1 with the
6624                  * resolved commit id; relies on the fact git-ls-remote lists
6625                  * the commit id of an annotated tag right before the commit id
6626                  * it points to. */
6627                 refs[refs_size - 1].ltag = ltag;
6628                 string_copy_rev(refs[refs_size - 1].id, id);
6630                 return OK;
6631         }
6633         if (!realloc_refs(&refs, refs_size, 1))
6634                 return ERR;
6636         ref = &refs[refs_size++];
6637         ref->name = malloc(namelen + 1);
6638         if (!ref->name)
6639                 return ERR;
6641         strncpy(ref->name, name, namelen);
6642         ref->name[namelen] = 0;
6643         ref->head = head;
6644         ref->tag = tag;
6645         ref->ltag = ltag;
6646         ref->remote = remote;
6647         ref->tracked = tracked;
6648         string_copy_rev(ref->id, id);
6650         return OK;
6653 static int
6654 load_refs(void)
6656         const char *head_argv[] = {
6657                 "git", "symbolic-ref", "HEAD", NULL
6658         };
6659         static const char *ls_remote_argv[SIZEOF_ARG] = {
6660                 "git", "ls-remote", opt_git_dir, NULL
6661         };
6662         static bool init = FALSE;
6664         if (!init) {
6665                 argv_from_env(ls_remote_argv, "TIG_LS_REMOTE");
6666                 init = TRUE;
6667         }
6669         if (!*opt_git_dir)
6670                 return OK;
6672         if (run_io_buf(head_argv, opt_head, sizeof(opt_head)) &&
6673             !prefixcmp(opt_head, "refs/heads/")) {
6674                 char *offset = opt_head + STRING_SIZE("refs/heads/");
6676                 memmove(opt_head, offset, strlen(offset) + 1);
6677         }
6679         while (refs_size > 0)
6680                 free(refs[--refs_size].name);
6681         while (id_refs_size > 0)
6682                 free(id_refs[--id_refs_size]);
6684         return run_io_load(ls_remote_argv, "\t", read_ref);
6687 static void
6688 set_remote_branch(const char *name, const char *value, size_t valuelen)
6690         if (!strcmp(name, ".remote")) {
6691                 string_ncopy(opt_remote, value, valuelen);
6693         } else if (*opt_remote && !strcmp(name, ".merge")) {
6694                 size_t from = strlen(opt_remote);
6696                 if (!prefixcmp(value, "refs/heads/"))
6697                         value += STRING_SIZE("refs/heads/");
6699                 if (!string_format_from(opt_remote, &from, "/%s", value))
6700                         opt_remote[0] = 0;
6701         }
6704 static void
6705 set_repo_config_option(char *name, char *value, int (*cmd)(int, const char **))
6707         const char *argv[SIZEOF_ARG] = { name, "=" };
6708         int argc = 1 + (cmd == option_set_command);
6709         int error = ERR;
6711         if (!argv_from_string(argv, &argc, value))
6712                 config_msg = "Too many option arguments";
6713         else
6714                 error = cmd(argc, argv);
6716         if (error == ERR)
6717                 warn("Option 'tig.%s': %s", name, config_msg);
6720 static bool
6721 set_environment_variable(const char *name, const char *value)
6723         size_t len = strlen(name) + 1 + strlen(value) + 1;
6724         char *env = malloc(len);
6726         if (env &&
6727             string_nformat(env, len, NULL, "%s=%s", name, value) &&
6728             putenv(env) == 0)
6729                 return TRUE;
6730         free(env);
6731         return FALSE;
6734 static void
6735 set_work_tree(const char *value)
6737         char cwd[SIZEOF_STR];
6739         if (!getcwd(cwd, sizeof(cwd)))
6740                 die("Failed to get cwd path: %s", strerror(errno));
6741         if (chdir(opt_git_dir) < 0)
6742                 die("Failed to chdir(%s): %s", strerror(errno));
6743         if (!getcwd(opt_git_dir, sizeof(opt_git_dir)))
6744                 die("Failed to get git path: %s", strerror(errno));
6745         if (chdir(cwd) < 0)
6746                 die("Failed to chdir(%s): %s", cwd, strerror(errno));
6747         if (chdir(value) < 0)
6748                 die("Failed to chdir(%s): %s", value, strerror(errno));
6749         if (!getcwd(cwd, sizeof(cwd)))
6750                 die("Failed to get cwd path: %s", strerror(errno));
6751         if (!set_environment_variable("GIT_WORK_TREE", cwd))
6752                 die("Failed to set GIT_WORK_TREE to '%s'", cwd);
6753         if (!set_environment_variable("GIT_DIR", opt_git_dir))
6754                 die("Failed to set GIT_DIR to '%s'", opt_git_dir);
6755         opt_is_inside_work_tree = TRUE;
6758 static int
6759 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
6761         if (!strcmp(name, "i18n.commitencoding"))
6762                 string_ncopy(opt_encoding, value, valuelen);
6764         else if (!strcmp(name, "core.editor"))
6765                 string_ncopy(opt_editor, value, valuelen);
6767         else if (!strcmp(name, "core.worktree"))
6768                 set_work_tree(value);
6770         else if (!prefixcmp(name, "tig.color."))
6771                 set_repo_config_option(name + 10, value, option_color_command);
6773         else if (!prefixcmp(name, "tig.bind."))
6774                 set_repo_config_option(name + 9, value, option_bind_command);
6776         else if (!prefixcmp(name, "tig."))
6777                 set_repo_config_option(name + 4, value, option_set_command);
6779         else if (*opt_head && !prefixcmp(name, "branch.") &&
6780                  !strncmp(name + 7, opt_head, strlen(opt_head)))
6781                 set_remote_branch(name + 7 + strlen(opt_head), value, valuelen);
6783         return OK;
6786 static int
6787 load_git_config(void)
6789         const char *config_list_argv[] = { "git", GIT_CONFIG, "--list", NULL };
6791         return run_io_load(config_list_argv, "=", read_repo_config_option);
6794 static int
6795 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
6797         if (!opt_git_dir[0]) {
6798                 string_ncopy(opt_git_dir, name, namelen);
6800         } else if (opt_is_inside_work_tree == -1) {
6801                 /* This can be 3 different values depending on the
6802                  * version of git being used. If git-rev-parse does not
6803                  * understand --is-inside-work-tree it will simply echo
6804                  * the option else either "true" or "false" is printed.
6805                  * Default to true for the unknown case. */
6806                 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
6808         } else if (*name == '.') {
6809                 string_ncopy(opt_cdup, name, namelen);
6811         } else {
6812                 string_ncopy(opt_prefix, name, namelen);
6813         }
6815         return OK;
6818 static int
6819 load_repo_info(void)
6821         const char *rev_parse_argv[] = {
6822                 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
6823                         "--show-cdup", "--show-prefix", NULL
6824         };
6826         return run_io_load(rev_parse_argv, "=", read_repo_info);
6830 /*
6831  * Main
6832  */
6834 static const char usage[] =
6835 "tig " TIG_VERSION " (" __DATE__ ")\n"
6836 "\n"
6837 "Usage: tig        [options] [revs] [--] [paths]\n"
6838 "   or: tig show   [options] [revs] [--] [paths]\n"
6839 "   or: tig blame  [rev] path\n"
6840 "   or: tig status\n"
6841 "   or: tig <      [git command output]\n"
6842 "\n"
6843 "Options:\n"
6844 "  -v, --version   Show version and exit\n"
6845 "  -h, --help      Show help message and exit";
6847 static void __NORETURN
6848 quit(int sig)
6850         /* XXX: Restore tty modes and let the OS cleanup the rest! */
6851         if (cursed)
6852                 endwin();
6853         exit(0);
6856 static void __NORETURN
6857 die(const char *err, ...)
6859         va_list args;
6861         endwin();
6863         va_start(args, err);
6864         fputs("tig: ", stderr);
6865         vfprintf(stderr, err, args);
6866         fputs("\n", stderr);
6867         va_end(args);
6869         exit(1);
6872 static void
6873 warn(const char *msg, ...)
6875         va_list args;
6877         va_start(args, msg);
6878         fputs("tig warning: ", stderr);
6879         vfprintf(stderr, msg, args);
6880         fputs("\n", stderr);
6881         va_end(args);
6884 static enum request
6885 parse_options(int argc, const char *argv[])
6887         enum request request = REQ_VIEW_MAIN;
6888         const char *subcommand;
6889         bool seen_dashdash = FALSE;
6890         /* XXX: This is vulnerable to the user overriding options
6891          * required for the main view parser. */
6892         const char *custom_argv[SIZEOF_ARG] = {
6893                 "git", "log", "--no-color", "--pretty=raw", "--parents",
6894                         "--topo-order", NULL
6895         };
6896         int i, j = 6;
6898         if (!isatty(STDIN_FILENO)) {
6899                 io_open(&VIEW(REQ_VIEW_PAGER)->io, "");
6900                 return REQ_VIEW_PAGER;
6901         }
6903         if (argc <= 1)
6904                 return REQ_NONE;
6906         subcommand = argv[1];
6907         if (!strcmp(subcommand, "status")) {
6908                 if (argc > 2)
6909                         warn("ignoring arguments after `%s'", subcommand);
6910                 return REQ_VIEW_STATUS;
6912         } else if (!strcmp(subcommand, "blame")) {
6913                 if (argc <= 2 || argc > 4)
6914                         die("invalid number of options to blame\n\n%s", usage);
6916                 i = 2;
6917                 if (argc == 4) {
6918                         string_ncopy(opt_ref, argv[i], strlen(argv[i]));
6919                         i++;
6920                 }
6922                 string_ncopy(opt_file, argv[i], strlen(argv[i]));
6923                 return REQ_VIEW_BLAME;
6925         } else if (!strcmp(subcommand, "show")) {
6926                 request = REQ_VIEW_DIFF;
6928         } else {
6929                 subcommand = NULL;
6930         }
6932         if (subcommand) {
6933                 custom_argv[1] = subcommand;
6934                 j = 2;
6935         }
6937         for (i = 1 + !!subcommand; i < argc; i++) {
6938                 const char *opt = argv[i];
6940                 if (seen_dashdash || !strcmp(opt, "--")) {
6941                         seen_dashdash = TRUE;
6943                 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
6944                         printf("tig version %s\n", TIG_VERSION);
6945                         quit(0);
6947                 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
6948                         printf("%s\n", usage);
6949                         quit(0);
6950                 }
6952                 custom_argv[j++] = opt;
6953                 if (j >= ARRAY_SIZE(custom_argv))
6954                         die("command too long");
6955         }
6957         if (!prepare_update(VIEW(request), custom_argv, NULL, FORMAT_NONE))                                                                        
6958                 die("Failed to format arguments"); 
6960         return request;
6963 int
6964 main(int argc, const char *argv[])
6966         enum request request = parse_options(argc, argv);
6967         struct view *view;
6968         size_t i;
6970         signal(SIGINT, quit);
6971         signal(SIGPIPE, SIG_IGN);
6973         if (setlocale(LC_ALL, "")) {
6974                 char *codeset = nl_langinfo(CODESET);
6976                 string_ncopy(opt_codeset, codeset, strlen(codeset));
6977         }
6979         if (load_repo_info() == ERR)
6980                 die("Failed to load repo info.");
6982         if (load_options() == ERR)
6983                 die("Failed to load user config.");
6985         if (load_git_config() == ERR)
6986                 die("Failed to load repo config.");
6988         /* Require a git repository unless when running in pager mode. */
6989         if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
6990                 die("Not a git repository");
6992         if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
6993                 opt_utf8 = FALSE;
6995         if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
6996                 opt_iconv = iconv_open(opt_codeset, opt_encoding);
6997                 if (opt_iconv == ICONV_NONE)
6998                         die("Failed to initialize character set conversion");
6999         }
7001         if (load_refs() == ERR)
7002                 die("Failed to load refs.");
7004         foreach_view (view, i)
7005                 argv_from_env(view->ops->argv, view->cmd_env);
7007         init_display();
7009         if (request != REQ_NONE)
7010                 open_view(NULL, request, OPEN_PREPARED);
7011         request = request == REQ_NONE ? REQ_VIEW_MAIN : REQ_NONE;
7013         while (view_driver(display[current_view], request)) {
7014                 int key = get_input(0);
7016                 view = display[current_view];
7017                 request = get_keybinding(view->keymap, key);
7019                 /* Some low-level request handling. This keeps access to
7020                  * status_win restricted. */
7021                 switch (request) {
7022                 case REQ_PROMPT:
7023                 {
7024                         char *cmd = read_prompt(":");
7026                         if (cmd && isdigit(*cmd)) {
7027                                 int lineno = view->lineno + 1;
7029                                 if (parse_int(&lineno, cmd, 1, view->lines + 1) == OK) {
7030                                         select_view_line(view, lineno - 1);
7031                                         report("");
7032                                 } else {
7033                                         report("Unable to parse '%s' as a line number", cmd);
7034                                 }
7036                         } else if (cmd) {
7037                                 struct view *next = VIEW(REQ_VIEW_PAGER);
7038                                 const char *argv[SIZEOF_ARG] = { "git" };
7039                                 int argc = 1;
7041                                 /* When running random commands, initially show the
7042                                  * command in the title. However, it maybe later be
7043                                  * overwritten if a commit line is selected. */
7044                                 string_ncopy(next->ref, cmd, strlen(cmd));
7046                                 if (!argv_from_string(argv, &argc, cmd)) {
7047                                         report("Too many arguments");
7048                                 } else if (!prepare_update(next, argv, NULL, FORMAT_DASH)) {
7049                                         report("Failed to format command");
7050                                 } else {
7051                                         open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
7052                                 }
7053                         }
7055                         request = REQ_NONE;
7056                         break;
7057                 }
7058                 case REQ_SEARCH:
7059                 case REQ_SEARCH_BACK:
7060                 {
7061                         const char *prompt = request == REQ_SEARCH ? "/" : "?";
7062                         char *search = read_prompt(prompt);
7064                         if (search)
7065                                 string_ncopy(opt_search, search, strlen(search));
7066                         else if (*opt_search)
7067                                 request = request == REQ_SEARCH ?
7068                                         REQ_FIND_NEXT :
7069                                         REQ_FIND_PREV;
7070                         else
7071                                 request = REQ_NONE;
7072                         break;
7073                 }
7074                 default:
7075                         break;
7076                 }
7077         }
7079         quit(0);
7081         return 0;