Code

Add missing NULL in blame_grep
[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                 NULL
4594         };
4596         return grep_text(view, text);
4599 static void
4600 blame_select(struct view *view, struct line *line)
4602         struct blame *blame = line->data;
4603         struct blame_commit *commit = blame->commit;
4605         if (!commit)
4606                 return;
4608         if (!strcmp(commit->id, NULL_ID))
4609                 string_ncopy(ref_commit, "HEAD", 4);
4610         else
4611                 string_copy_rev(ref_commit, commit->id);
4614 static struct view_ops blame_ops = {
4615         "line",
4616         NULL,
4617         blame_open,
4618         blame_read,
4619         blame_draw,
4620         blame_request,
4621         blame_grep,
4622         blame_select,
4623 };
4625 /*
4626  * Status backend
4627  */
4629 struct status {
4630         char status;
4631         struct {
4632                 mode_t mode;
4633                 char rev[SIZEOF_REV];
4634                 char name[SIZEOF_STR];
4635         } old;
4636         struct {
4637                 mode_t mode;
4638                 char rev[SIZEOF_REV];
4639                 char name[SIZEOF_STR];
4640         } new;
4641 };
4643 static char status_onbranch[SIZEOF_STR];
4644 static struct status stage_status;
4645 static enum line_type stage_line_type;
4646 static size_t stage_chunks;
4647 static int *stage_chunk;
4649 DEFINE_ALLOCATOR(realloc_ints, int, 32)
4651 /* This should work even for the "On branch" line. */
4652 static inline bool
4653 status_has_none(struct view *view, struct line *line)
4655         return line < view->line + view->lines && !line[1].data;
4658 /* Get fields from the diff line:
4659  * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
4660  */
4661 static inline bool
4662 status_get_diff(struct status *file, const char *buf, size_t bufsize)
4664         const char *old_mode = buf +  1;
4665         const char *new_mode = buf +  8;
4666         const char *old_rev  = buf + 15;
4667         const char *new_rev  = buf + 56;
4668         const char *status   = buf + 97;
4670         if (bufsize < 98 ||
4671             old_mode[-1] != ':' ||
4672             new_mode[-1] != ' ' ||
4673             old_rev[-1]  != ' ' ||
4674             new_rev[-1]  != ' ' ||
4675             status[-1]   != ' ')
4676                 return FALSE;
4678         file->status = *status;
4680         string_copy_rev(file->old.rev, old_rev);
4681         string_copy_rev(file->new.rev, new_rev);
4683         file->old.mode = strtoul(old_mode, NULL, 8);
4684         file->new.mode = strtoul(new_mode, NULL, 8);
4686         file->old.name[0] = file->new.name[0] = 0;
4688         return TRUE;
4691 static bool
4692 status_run(struct view *view, const char *argv[], char status, enum line_type type)
4694         struct status *unmerged = NULL;
4695         char *buf;
4696         struct io io = {};
4698         if (!run_io(&io, argv, NULL, IO_RD))
4699                 return FALSE;
4701         add_line_data(view, NULL, type);
4703         while ((buf = io_get(&io, 0, TRUE))) {
4704                 struct status *file = unmerged;
4706                 if (!file) {
4707                         file = calloc(1, sizeof(*file));
4708                         if (!file || !add_line_data(view, file, type))
4709                                 goto error_out;
4710                 }
4712                 /* Parse diff info part. */
4713                 if (status) {
4714                         file->status = status;
4715                         if (status == 'A')
4716                                 string_copy(file->old.rev, NULL_ID);
4718                 } else if (!file->status || file == unmerged) {
4719                         if (!status_get_diff(file, buf, strlen(buf)))
4720                                 goto error_out;
4722                         buf = io_get(&io, 0, TRUE);
4723                         if (!buf)
4724                                 break;
4726                         /* Collapse all modified entries that follow an
4727                          * associated unmerged entry. */
4728                         if (unmerged == file) {
4729                                 unmerged->status = 'U';
4730                                 unmerged = NULL;
4731                         } else if (file->status == 'U') {
4732                                 unmerged = file;
4733                         }
4734                 }
4736                 /* Grab the old name for rename/copy. */
4737                 if (!*file->old.name &&
4738                     (file->status == 'R' || file->status == 'C')) {
4739                         string_ncopy(file->old.name, buf, strlen(buf));
4741                         buf = io_get(&io, 0, TRUE);
4742                         if (!buf)
4743                                 break;
4744                 }
4746                 /* git-ls-files just delivers a NUL separated list of
4747                  * file names similar to the second half of the
4748                  * git-diff-* output. */
4749                 string_ncopy(file->new.name, buf, strlen(buf));
4750                 if (!*file->old.name)
4751                         string_copy(file->old.name, file->new.name);
4752                 file = NULL;
4753         }
4755         if (io_error(&io)) {
4756 error_out:
4757                 done_io(&io);
4758                 return FALSE;
4759         }
4761         if (!view->line[view->lines - 1].data)
4762                 add_line_data(view, NULL, LINE_STAT_NONE);
4764         done_io(&io);
4765         return TRUE;
4768 /* Don't show unmerged entries in the staged section. */
4769 static const char *status_diff_index_argv[] = {
4770         "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
4771                              "--cached", "-M", "HEAD", NULL
4772 };
4774 static const char *status_diff_files_argv[] = {
4775         "git", "diff-files", "-z", NULL
4776 };
4778 static const char *status_list_other_argv[] = {
4779         "git", "ls-files", "-z", "--others", "--exclude-standard", NULL
4780 };
4782 static const char *status_list_no_head_argv[] = {
4783         "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
4784 };
4786 static const char *update_index_argv[] = {
4787         "git", "update-index", "-q", "--unmerged", "--refresh", NULL
4788 };
4790 /* Restore the previous line number to stay in the context or select a
4791  * line with something that can be updated. */
4792 static void
4793 status_restore(struct view *view)
4795         if (view->p_lineno >= view->lines)
4796                 view->p_lineno = view->lines - 1;
4797         while (view->p_lineno < view->lines && !view->line[view->p_lineno].data)
4798                 view->p_lineno++;
4799         while (view->p_lineno > 0 && !view->line[view->p_lineno].data)
4800                 view->p_lineno--;
4802         /* If the above fails, always skip the "On branch" line. */
4803         if (view->p_lineno < view->lines)
4804                 view->lineno = view->p_lineno;
4805         else
4806                 view->lineno = 1;
4808         if (view->lineno < view->offset)
4809                 view->offset = view->lineno;
4810         else if (view->offset + view->height <= view->lineno)
4811                 view->offset = view->lineno - view->height + 1;
4813         view->p_restore = FALSE;
4816 static void
4817 status_update_onbranch(void)
4819         static const char *paths[][2] = {
4820                 { "rebase-apply/rebasing",      "Rebasing" },
4821                 { "rebase-apply/applying",      "Applying mailbox" },
4822                 { "rebase-apply/",              "Rebasing mailbox" },
4823                 { "rebase-merge/interactive",   "Interactive rebase" },
4824                 { "rebase-merge/",              "Rebase merge" },
4825                 { "MERGE_HEAD",                 "Merging" },
4826                 { "BISECT_LOG",                 "Bisecting" },
4827                 { "HEAD",                       "On branch" },
4828         };
4829         char buf[SIZEOF_STR];
4830         struct stat stat;
4831         int i;
4833         if (is_initial_commit()) {
4834                 string_copy(status_onbranch, "Initial commit");
4835                 return;
4836         }
4838         for (i = 0; i < ARRAY_SIZE(paths); i++) {
4839                 char *head = opt_head;
4841                 if (!string_format(buf, "%s/%s", opt_git_dir, paths[i][0]) ||
4842                     lstat(buf, &stat) < 0)
4843                         continue;
4845                 if (!*opt_head) {
4846                         struct io io = {};
4848                         if (string_format(buf, "%s/rebase-merge/head-name", opt_git_dir) &&
4849                             io_open(&io, buf) &&
4850                             io_read_buf(&io, buf, sizeof(buf))) {
4851                                 head = buf;
4852                                 if (!prefixcmp(head, "refs/heads/"))
4853                                         head += STRING_SIZE("refs/heads/");
4854                         }
4855                 }
4857                 if (!string_format(status_onbranch, "%s %s", paths[i][1], head))
4858                         string_copy(status_onbranch, opt_head);
4859                 return;
4860         }
4862         string_copy(status_onbranch, "Not currently on any branch");
4865 /* First parse staged info using git-diff-index(1), then parse unstaged
4866  * info using git-diff-files(1), and finally untracked files using
4867  * git-ls-files(1). */
4868 static bool
4869 status_open(struct view *view)
4871         reset_view(view);
4873         add_line_data(view, NULL, LINE_STAT_HEAD);
4874         status_update_onbranch();
4876         run_io_bg(update_index_argv);
4878         if (is_initial_commit()) {
4879                 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
4880                         return FALSE;
4881         } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
4882                 return FALSE;
4883         }
4885         if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
4886             !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
4887                 return FALSE;
4889         /* Restore the exact position or use the specialized restore
4890          * mode? */
4891         if (!view->p_restore)
4892                 status_restore(view);
4893         return TRUE;
4896 static bool
4897 status_draw(struct view *view, struct line *line, unsigned int lineno)
4899         struct status *status = line->data;
4900         enum line_type type;
4901         const char *text;
4903         if (!status) {
4904                 switch (line->type) {
4905                 case LINE_STAT_STAGED:
4906                         type = LINE_STAT_SECTION;
4907                         text = "Changes to be committed:";
4908                         break;
4910                 case LINE_STAT_UNSTAGED:
4911                         type = LINE_STAT_SECTION;
4912                         text = "Changed but not updated:";
4913                         break;
4915                 case LINE_STAT_UNTRACKED:
4916                         type = LINE_STAT_SECTION;
4917                         text = "Untracked files:";
4918                         break;
4920                 case LINE_STAT_NONE:
4921                         type = LINE_DEFAULT;
4922                         text = "  (no files)";
4923                         break;
4925                 case LINE_STAT_HEAD:
4926                         type = LINE_STAT_HEAD;
4927                         text = status_onbranch;
4928                         break;
4930                 default:
4931                         return FALSE;
4932                 }
4933         } else {
4934                 static char buf[] = { '?', ' ', ' ', ' ', 0 };
4936                 buf[0] = status->status;
4937                 if (draw_text(view, line->type, buf, TRUE))
4938                         return TRUE;
4939                 type = LINE_DEFAULT;
4940                 text = status->new.name;
4941         }
4943         draw_text(view, type, text, TRUE);
4944         return TRUE;
4947 static enum request
4948 status_load_error(struct view *view, struct view *stage, const char *path)
4950         if (displayed_views() == 2 || display[current_view] != view)
4951                 maximize_view(view);
4952         report("Failed to load '%s': %s", path, io_strerror(&stage->io));
4953         return REQ_NONE;
4956 static enum request
4957 status_enter(struct view *view, struct line *line)
4959         struct status *status = line->data;
4960         const char *oldpath = status ? status->old.name : NULL;
4961         /* Diffs for unmerged entries are empty when passing the new
4962          * path, so leave it empty. */
4963         const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
4964         const char *info;
4965         enum open_flags split;
4966         struct view *stage = VIEW(REQ_VIEW_STAGE);
4968         if (line->type == LINE_STAT_NONE ||
4969             (!status && line[1].type == LINE_STAT_NONE)) {
4970                 report("No file to diff");
4971                 return REQ_NONE;
4972         }
4974         switch (line->type) {
4975         case LINE_STAT_STAGED:
4976                 if (is_initial_commit()) {
4977                         const char *no_head_diff_argv[] = {
4978                                 "git", "diff", "--no-color", "--patch-with-stat",
4979                                         "--", "/dev/null", newpath, NULL
4980                         };
4982                         if (!prepare_update(stage, no_head_diff_argv, opt_cdup, FORMAT_DASH))
4983                                 return status_load_error(view, stage, newpath);
4984                 } else {
4985                         const char *index_show_argv[] = {
4986                                 "git", "diff-index", "--root", "--patch-with-stat",
4987                                         "-C", "-M", "--cached", "HEAD", "--",
4988                                         oldpath, newpath, NULL
4989                         };
4991                         if (!prepare_update(stage, index_show_argv, opt_cdup, FORMAT_DASH))
4992                                 return status_load_error(view, stage, newpath);
4993                 }
4995                 if (status)
4996                         info = "Staged changes to %s";
4997                 else
4998                         info = "Staged changes";
4999                 break;
5001         case LINE_STAT_UNSTAGED:
5002         {
5003                 const char *files_show_argv[] = {
5004                         "git", "diff-files", "--root", "--patch-with-stat",
5005                                 "-C", "-M", "--", oldpath, newpath, NULL
5006                 };
5008                 if (!prepare_update(stage, files_show_argv, opt_cdup, FORMAT_DASH))
5009                         return status_load_error(view, stage, newpath);
5010                 if (status)
5011                         info = "Unstaged changes to %s";
5012                 else
5013                         info = "Unstaged changes";
5014                 break;
5015         }
5016         case LINE_STAT_UNTRACKED:
5017                 if (!newpath) {
5018                         report("No file to show");
5019                         return REQ_NONE;
5020                 }
5022                 if (!suffixcmp(status->new.name, -1, "/")) {
5023                         report("Cannot display a directory");
5024                         return REQ_NONE;
5025                 }
5027                 if (!prepare_update_file(stage, newpath))
5028                         return status_load_error(view, stage, newpath);
5029                 info = "Untracked file %s";
5030                 break;
5032         case LINE_STAT_HEAD:
5033                 return REQ_NONE;
5035         default:
5036                 die("line type %d not handled in switch", line->type);
5037         }
5039         split = view_is_displayed(view) ? OPEN_SPLIT : 0;
5040         open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split);
5041         if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
5042                 if (status) {
5043                         stage_status = *status;
5044                 } else {
5045                         memset(&stage_status, 0, sizeof(stage_status));
5046                 }
5048                 stage_line_type = line->type;
5049                 stage_chunks = 0;
5050                 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
5051         }
5053         return REQ_NONE;
5056 static bool
5057 status_exists(struct status *status, enum line_type type)
5059         struct view *view = VIEW(REQ_VIEW_STATUS);
5060         unsigned long lineno;
5062         for (lineno = 0; lineno < view->lines; lineno++) {
5063                 struct line *line = &view->line[lineno];
5064                 struct status *pos = line->data;
5066                 if (line->type != type)
5067                         continue;
5068                 if (!pos && (!status || !status->status) && line[1].data) {
5069                         select_view_line(view, lineno);
5070                         return TRUE;
5071                 }
5072                 if (pos && !strcmp(status->new.name, pos->new.name)) {
5073                         select_view_line(view, lineno);
5074                         return TRUE;
5075                 }
5076         }
5078         return FALSE;
5082 static bool
5083 status_update_prepare(struct io *io, enum line_type type)
5085         const char *staged_argv[] = {
5086                 "git", "update-index", "-z", "--index-info", NULL
5087         };
5088         const char *others_argv[] = {
5089                 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
5090         };
5092         switch (type) {
5093         case LINE_STAT_STAGED:
5094                 return run_io(io, staged_argv, opt_cdup, IO_WR);
5096         case LINE_STAT_UNSTAGED:
5097                 return run_io(io, others_argv, opt_cdup, IO_WR);
5099         case LINE_STAT_UNTRACKED:
5100                 return run_io(io, others_argv, NULL, IO_WR);
5102         default:
5103                 die("line type %d not handled in switch", type);
5104                 return FALSE;
5105         }
5108 static bool
5109 status_update_write(struct io *io, struct status *status, enum line_type type)
5111         char buf[SIZEOF_STR];
5112         size_t bufsize = 0;
5114         switch (type) {
5115         case LINE_STAT_STAGED:
5116                 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
5117                                         status->old.mode,
5118                                         status->old.rev,
5119                                         status->old.name, 0))
5120                         return FALSE;
5121                 break;
5123         case LINE_STAT_UNSTAGED:
5124         case LINE_STAT_UNTRACKED:
5125                 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
5126                         return FALSE;
5127                 break;
5129         default:
5130                 die("line type %d not handled in switch", type);
5131         }
5133         return io_write(io, buf, bufsize);
5136 static bool
5137 status_update_file(struct status *status, enum line_type type)
5139         struct io io = {};
5140         bool result;
5142         if (!status_update_prepare(&io, type))
5143                 return FALSE;
5145         result = status_update_write(&io, status, type);
5146         return done_io(&io) && result;
5149 static bool
5150 status_update_files(struct view *view, struct line *line)
5152         char buf[sizeof(view->ref)];
5153         struct io io = {};
5154         bool result = TRUE;
5155         struct line *pos = view->line + view->lines;
5156         int files = 0;
5157         int file, done;
5158         int cursor_y, cursor_x;
5160         if (!status_update_prepare(&io, line->type))
5161                 return FALSE;
5163         for (pos = line; pos < view->line + view->lines && pos->data; pos++)
5164                 files++;
5166         string_copy(buf, view->ref);
5167         getsyx(cursor_y, cursor_x);
5168         for (file = 0, done = 5; result && file < files; line++, file++) {
5169                 int almost_done = file * 100 / files;
5171                 if (almost_done > done) {
5172                         done = almost_done;
5173                         string_format(view->ref, "updating file %u of %u (%d%% done)",
5174                                       file, files, done);
5175                         update_view_title(view);
5176                         setsyx(cursor_y, cursor_x);
5177                         doupdate();
5178                 }
5179                 result = status_update_write(&io, line->data, line->type);
5180         }
5181         string_copy(view->ref, buf);
5183         return done_io(&io) && result;
5186 static bool
5187 status_update(struct view *view)
5189         struct line *line = &view->line[view->lineno];
5191         assert(view->lines);
5193         if (!line->data) {
5194                 /* This should work even for the "On branch" line. */
5195                 if (line < view->line + view->lines && !line[1].data) {
5196                         report("Nothing to update");
5197                         return FALSE;
5198                 }
5200                 if (!status_update_files(view, line + 1)) {
5201                         report("Failed to update file status");
5202                         return FALSE;
5203                 }
5205         } else if (!status_update_file(line->data, line->type)) {
5206                 report("Failed to update file status");
5207                 return FALSE;
5208         }
5210         return TRUE;
5213 static bool
5214 status_revert(struct status *status, enum line_type type, bool has_none)
5216         if (!status || type != LINE_STAT_UNSTAGED) {
5217                 if (type == LINE_STAT_STAGED) {
5218                         report("Cannot revert changes to staged files");
5219                 } else if (type == LINE_STAT_UNTRACKED) {
5220                         report("Cannot revert changes to untracked files");
5221                 } else if (has_none) {
5222                         report("Nothing to revert");
5223                 } else {
5224                         report("Cannot revert changes to multiple files");
5225                 }
5226                 return FALSE;
5228         } else {
5229                 char mode[10] = "100644";
5230                 const char *reset_argv[] = {
5231                         "git", "update-index", "--cacheinfo", mode,
5232                                 status->old.rev, status->old.name, NULL
5233                 };
5234                 const char *checkout_argv[] = {
5235                         "git", "checkout", "--", status->old.name, NULL
5236                 };
5238                 if (!prompt_yesno("Are you sure you want to overwrite any changes?"))
5239                         return FALSE;
5240                 string_format(mode, "%o", status->old.mode);
5241                 return (status->status != 'U' || run_io_fg(reset_argv, opt_cdup)) &&
5242                         run_io_fg(checkout_argv, opt_cdup);
5243         }
5246 static enum request
5247 status_request(struct view *view, enum request request, struct line *line)
5249         struct status *status = line->data;
5251         switch (request) {
5252         case REQ_STATUS_UPDATE:
5253                 if (!status_update(view))
5254                         return REQ_NONE;
5255                 break;
5257         case REQ_STATUS_REVERT:
5258                 if (!status_revert(status, line->type, status_has_none(view, line)))
5259                         return REQ_NONE;
5260                 break;
5262         case REQ_STATUS_MERGE:
5263                 if (!status || status->status != 'U') {
5264                         report("Merging only possible for files with unmerged status ('U').");
5265                         return REQ_NONE;
5266                 }
5267                 open_mergetool(status->new.name);
5268                 break;
5270         case REQ_EDIT:
5271                 if (!status)
5272                         return request;
5273                 if (status->status == 'D') {
5274                         report("File has been deleted.");
5275                         return REQ_NONE;
5276                 }
5278                 open_editor(status->status != '?', status->new.name);
5279                 break;
5281         case REQ_VIEW_BLAME:
5282                 if (status) {
5283                         string_copy(opt_file, status->new.name);
5284                         opt_ref[0] = 0;
5285                 }
5286                 return request;
5288         case REQ_ENTER:
5289                 /* After returning the status view has been split to
5290                  * show the stage view. No further reloading is
5291                  * necessary. */
5292                 return status_enter(view, line);
5294         case REQ_REFRESH:
5295                 /* Simply reload the view. */
5296                 break;
5298         default:
5299                 return request;
5300         }
5302         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
5304         return REQ_NONE;
5307 static void
5308 status_select(struct view *view, struct line *line)
5310         struct status *status = line->data;
5311         char file[SIZEOF_STR] = "all files";
5312         const char *text;
5313         const char *key;
5315         if (status && !string_format(file, "'%s'", status->new.name))
5316                 return;
5318         if (!status && line[1].type == LINE_STAT_NONE)
5319                 line++;
5321         switch (line->type) {
5322         case LINE_STAT_STAGED:
5323                 text = "Press %s to unstage %s for commit";
5324                 break;
5326         case LINE_STAT_UNSTAGED:
5327                 text = "Press %s to stage %s for commit";
5328                 break;
5330         case LINE_STAT_UNTRACKED:
5331                 text = "Press %s to stage %s for addition";
5332                 break;
5334         case LINE_STAT_HEAD:
5335         case LINE_STAT_NONE:
5336                 text = "Nothing to update";
5337                 break;
5339         default:
5340                 die("line type %d not handled in switch", line->type);
5341         }
5343         if (status && status->status == 'U') {
5344                 text = "Press %s to resolve conflict in %s";
5345                 key = get_key(REQ_STATUS_MERGE);
5347         } else {
5348                 key = get_key(REQ_STATUS_UPDATE);
5349         }
5351         string_format(view->ref, text, key, file);
5354 static bool
5355 status_grep(struct view *view, struct line *line)
5357         struct status *status = line->data;
5359         if (status) {
5360                 const char buf[2] = { status->status, 0 };
5361                 const char *text[] = { status->new.name, buf, NULL };
5363                 return grep_text(view, text);
5364         }
5366         return FALSE;
5369 static struct view_ops status_ops = {
5370         "file",
5371         NULL,
5372         status_open,
5373         NULL,
5374         status_draw,
5375         status_request,
5376         status_grep,
5377         status_select,
5378 };
5381 static bool
5382 stage_diff_write(struct io *io, struct line *line, struct line *end)
5384         while (line < end) {
5385                 if (!io_write(io, line->data, strlen(line->data)) ||
5386                     !io_write(io, "\n", 1))
5387                         return FALSE;
5388                 line++;
5389                 if (line->type == LINE_DIFF_CHUNK ||
5390                     line->type == LINE_DIFF_HEADER)
5391                         break;
5392         }
5394         return TRUE;
5397 static struct line *
5398 stage_diff_find(struct view *view, struct line *line, enum line_type type)
5400         for (; view->line < line; line--)
5401                 if (line->type == type)
5402                         return line;
5404         return NULL;
5407 static bool
5408 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
5410         const char *apply_argv[SIZEOF_ARG] = {
5411                 "git", "apply", "--whitespace=nowarn", NULL
5412         };
5413         struct line *diff_hdr;
5414         struct io io = {};
5415         int argc = 3;
5417         diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
5418         if (!diff_hdr)
5419                 return FALSE;
5421         if (!revert)
5422                 apply_argv[argc++] = "--cached";
5423         if (revert || stage_line_type == LINE_STAT_STAGED)
5424                 apply_argv[argc++] = "-R";
5425         apply_argv[argc++] = "-";
5426         apply_argv[argc++] = NULL;
5427         if (!run_io(&io, apply_argv, opt_cdup, IO_WR))
5428                 return FALSE;
5430         if (!stage_diff_write(&io, diff_hdr, chunk) ||
5431             !stage_diff_write(&io, chunk, view->line + view->lines))
5432                 chunk = NULL;
5434         done_io(&io);
5435         run_io_bg(update_index_argv);
5437         return chunk ? TRUE : FALSE;
5440 static bool
5441 stage_update(struct view *view, struct line *line)
5443         struct line *chunk = NULL;
5445         if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
5446                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
5448         if (chunk) {
5449                 if (!stage_apply_chunk(view, chunk, FALSE)) {
5450                         report("Failed to apply chunk");
5451                         return FALSE;
5452                 }
5454         } else if (!stage_status.status) {
5455                 view = VIEW(REQ_VIEW_STATUS);
5457                 for (line = view->line; line < view->line + view->lines; line++)
5458                         if (line->type == stage_line_type)
5459                                 break;
5461                 if (!status_update_files(view, line + 1)) {
5462                         report("Failed to update files");
5463                         return FALSE;
5464                 }
5466         } else if (!status_update_file(&stage_status, stage_line_type)) {
5467                 report("Failed to update file");
5468                 return FALSE;
5469         }
5471         return TRUE;
5474 static bool
5475 stage_revert(struct view *view, struct line *line)
5477         struct line *chunk = NULL;
5479         if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
5480                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
5482         if (chunk) {
5483                 if (!prompt_yesno("Are you sure you want to revert changes?"))
5484                         return FALSE;
5486                 if (!stage_apply_chunk(view, chunk, TRUE)) {
5487                         report("Failed to revert chunk");
5488                         return FALSE;
5489                 }
5490                 return TRUE;
5492         } else {
5493                 return status_revert(stage_status.status ? &stage_status : NULL,
5494                                      stage_line_type, FALSE);
5495         }
5499 static void
5500 stage_next(struct view *view, struct line *line)
5502         int i;
5504         if (!stage_chunks) {
5505                 for (line = view->line; line < view->line + view->lines; line++) {
5506                         if (line->type != LINE_DIFF_CHUNK)
5507                                 continue;
5509                         if (!realloc_ints(&stage_chunk, stage_chunks, 1)) {
5510                                 report("Allocation failure");
5511                                 return;
5512                         }
5514                         stage_chunk[stage_chunks++] = line - view->line;
5515                 }
5516         }
5518         for (i = 0; i < stage_chunks; i++) {
5519                 if (stage_chunk[i] > view->lineno) {
5520                         do_scroll_view(view, stage_chunk[i] - view->lineno);
5521                         report("Chunk %d of %d", i + 1, stage_chunks);
5522                         return;
5523                 }
5524         }
5526         report("No next chunk found");
5529 static enum request
5530 stage_request(struct view *view, enum request request, struct line *line)
5532         switch (request) {
5533         case REQ_STATUS_UPDATE:
5534                 if (!stage_update(view, line))
5535                         return REQ_NONE;
5536                 break;
5538         case REQ_STATUS_REVERT:
5539                 if (!stage_revert(view, line))
5540                         return REQ_NONE;
5541                 break;
5543         case REQ_STAGE_NEXT:
5544                 if (stage_line_type == LINE_STAT_UNTRACKED) {
5545                         report("File is untracked; press %s to add",
5546                                get_key(REQ_STATUS_UPDATE));
5547                         return REQ_NONE;
5548                 }
5549                 stage_next(view, line);
5550                 return REQ_NONE;
5552         case REQ_EDIT:
5553                 if (!stage_status.new.name[0])
5554                         return request;
5555                 if (stage_status.status == 'D') {
5556                         report("File has been deleted.");
5557                         return REQ_NONE;
5558                 }
5560                 open_editor(stage_status.status != '?', stage_status.new.name);
5561                 break;
5563         case REQ_REFRESH:
5564                 /* Reload everything ... */
5565                 break;
5567         case REQ_VIEW_BLAME:
5568                 if (stage_status.new.name[0]) {
5569                         string_copy(opt_file, stage_status.new.name);
5570                         opt_ref[0] = 0;
5571                 }
5572                 return request;
5574         case REQ_ENTER:
5575                 return pager_request(view, request, line);
5577         default:
5578                 return request;
5579         }
5581         VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
5582         open_view(view, REQ_VIEW_STATUS, OPEN_REFRESH);
5584         /* Check whether the staged entry still exists, and close the
5585          * stage view if it doesn't. */
5586         if (!status_exists(&stage_status, stage_line_type)) {
5587                 status_restore(VIEW(REQ_VIEW_STATUS));
5588                 return REQ_VIEW_CLOSE;
5589         }
5591         if (stage_line_type == LINE_STAT_UNTRACKED) {
5592                 if (!suffixcmp(stage_status.new.name, -1, "/")) {
5593                         report("Cannot display a directory");
5594                         return REQ_NONE;
5595                 }
5597                 if (!prepare_update_file(view, stage_status.new.name)) {
5598                         report("Failed to open file: %s", strerror(errno));
5599                         return REQ_NONE;
5600                 }
5601         }
5602         open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
5604         return REQ_NONE;
5607 static struct view_ops stage_ops = {
5608         "line",
5609         NULL,
5610         NULL,
5611         pager_read,
5612         pager_draw,
5613         stage_request,
5614         pager_grep,
5615         pager_select,
5616 };
5619 /*
5620  * Revision graph
5621  */
5623 struct commit {
5624         char id[SIZEOF_REV];            /* SHA1 ID. */
5625         char title[128];                /* First line of the commit message. */
5626         const char *author;             /* Author of the commit. */
5627         time_t time;                    /* Date from the author ident. */
5628         struct ref **refs;              /* Repository references. */
5629         chtype graph[SIZEOF_REVGRAPH];  /* Ancestry chain graphics. */
5630         size_t graph_size;              /* The width of the graph array. */
5631         bool has_parents;               /* Rewritten --parents seen. */
5632 };
5634 /* Size of rev graph with no  "padding" columns */
5635 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
5637 struct rev_graph {
5638         struct rev_graph *prev, *next, *parents;
5639         char rev[SIZEOF_REVITEMS][SIZEOF_REV];
5640         size_t size;
5641         struct commit *commit;
5642         size_t pos;
5643         unsigned int boundary:1;
5644 };
5646 /* Parents of the commit being visualized. */
5647 static struct rev_graph graph_parents[4];
5649 /* The current stack of revisions on the graph. */
5650 static struct rev_graph graph_stacks[4] = {
5651         { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
5652         { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
5653         { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
5654         { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
5655 };
5657 static inline bool
5658 graph_parent_is_merge(struct rev_graph *graph)
5660         return graph->parents->size > 1;
5663 static inline void
5664 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
5666         struct commit *commit = graph->commit;
5668         if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
5669                 commit->graph[commit->graph_size++] = symbol;
5672 static void
5673 clear_rev_graph(struct rev_graph *graph)
5675         graph->boundary = 0;
5676         graph->size = graph->pos = 0;
5677         graph->commit = NULL;
5678         memset(graph->parents, 0, sizeof(*graph->parents));
5681 static void
5682 done_rev_graph(struct rev_graph *graph)
5684         if (graph_parent_is_merge(graph) &&
5685             graph->pos < graph->size - 1 &&
5686             graph->next->size == graph->size + graph->parents->size - 1) {
5687                 size_t i = graph->pos + graph->parents->size - 1;
5689                 graph->commit->graph_size = i * 2;
5690                 while (i < graph->next->size - 1) {
5691                         append_to_rev_graph(graph, ' ');
5692                         append_to_rev_graph(graph, '\\');
5693                         i++;
5694                 }
5695         }
5697         clear_rev_graph(graph);
5700 static void
5701 push_rev_graph(struct rev_graph *graph, const char *parent)
5703         int i;
5705         /* "Collapse" duplicate parents lines.
5706          *
5707          * FIXME: This needs to also update update the drawn graph but
5708          * for now it just serves as a method for pruning graph lines. */
5709         for (i = 0; i < graph->size; i++)
5710                 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
5711                         return;
5713         if (graph->size < SIZEOF_REVITEMS) {
5714                 string_copy_rev(graph->rev[graph->size++], parent);
5715         }
5718 static chtype
5719 get_rev_graph_symbol(struct rev_graph *graph)
5721         chtype symbol;
5723         if (graph->boundary)
5724                 symbol = REVGRAPH_BOUND;
5725         else if (graph->parents->size == 0)
5726                 symbol = REVGRAPH_INIT;
5727         else if (graph_parent_is_merge(graph))
5728                 symbol = REVGRAPH_MERGE;
5729         else if (graph->pos >= graph->size)
5730                 symbol = REVGRAPH_BRANCH;
5731         else
5732                 symbol = REVGRAPH_COMMIT;
5734         return symbol;
5737 static void
5738 draw_rev_graph(struct rev_graph *graph)
5740         struct rev_filler {
5741                 chtype separator, line;
5742         };
5743         enum { DEFAULT, RSHARP, RDIAG, LDIAG };
5744         static struct rev_filler fillers[] = {
5745                 { ' ',  '|' },
5746                 { '`',  '.' },
5747                 { '\'', ' ' },
5748                 { '/',  ' ' },
5749         };
5750         chtype symbol = get_rev_graph_symbol(graph);
5751         struct rev_filler *filler;
5752         size_t i;
5754         if (opt_line_graphics)
5755                 fillers[DEFAULT].line = line_graphics[LINE_GRAPHIC_VLINE];
5757         filler = &fillers[DEFAULT];
5759         for (i = 0; i < graph->pos; i++) {
5760                 append_to_rev_graph(graph, filler->line);
5761                 if (graph_parent_is_merge(graph->prev) &&
5762                     graph->prev->pos == i)
5763                         filler = &fillers[RSHARP];
5765                 append_to_rev_graph(graph, filler->separator);
5766         }
5768         /* Place the symbol for this revision. */
5769         append_to_rev_graph(graph, symbol);
5771         if (graph->prev->size > graph->size)
5772                 filler = &fillers[RDIAG];
5773         else
5774                 filler = &fillers[DEFAULT];
5776         i++;
5778         for (; i < graph->size; i++) {
5779                 append_to_rev_graph(graph, filler->separator);
5780                 append_to_rev_graph(graph, filler->line);
5781                 if (graph_parent_is_merge(graph->prev) &&
5782                     i < graph->prev->pos + graph->parents->size)
5783                         filler = &fillers[RSHARP];
5784                 if (graph->prev->size > graph->size)
5785                         filler = &fillers[LDIAG];
5786         }
5788         if (graph->prev->size > graph->size) {
5789                 append_to_rev_graph(graph, filler->separator);
5790                 if (filler->line != ' ')
5791                         append_to_rev_graph(graph, filler->line);
5792         }
5795 /* Prepare the next rev graph */
5796 static void
5797 prepare_rev_graph(struct rev_graph *graph)
5799         size_t i;
5801         /* First, traverse all lines of revisions up to the active one. */
5802         for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
5803                 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
5804                         break;
5806                 push_rev_graph(graph->next, graph->rev[graph->pos]);
5807         }
5809         /* Interleave the new revision parent(s). */
5810         for (i = 0; !graph->boundary && i < graph->parents->size; i++)
5811                 push_rev_graph(graph->next, graph->parents->rev[i]);
5813         /* Lastly, put any remaining revisions. */
5814         for (i = graph->pos + 1; i < graph->size; i++)
5815                 push_rev_graph(graph->next, graph->rev[i]);
5818 static void
5819 update_rev_graph(struct view *view, struct rev_graph *graph)
5821         /* If this is the finalizing update ... */
5822         if (graph->commit)
5823                 prepare_rev_graph(graph);
5825         /* Graph visualization needs a one rev look-ahead,
5826          * so the first update doesn't visualize anything. */
5827         if (!graph->prev->commit)
5828                 return;
5830         if (view->lines > 2)
5831                 view->line[view->lines - 3].dirty = 1;
5832         if (view->lines > 1)
5833                 view->line[view->lines - 2].dirty = 1;
5834         draw_rev_graph(graph->prev);
5835         done_rev_graph(graph->prev->prev);
5839 /*
5840  * Main view backend
5841  */
5843 static const char *main_argv[SIZEOF_ARG] = {
5844         "git", "log", "--no-color", "--pretty=raw", "--parents",
5845                       "--topo-order", "%(head)", NULL
5846 };
5848 static bool
5849 main_draw(struct view *view, struct line *line, unsigned int lineno)
5851         struct commit *commit = line->data;
5853         if (!commit->author)
5854                 return FALSE;
5856         if (opt_date && draw_date(view, &commit->time))
5857                 return TRUE;
5859         if (opt_author && draw_author(view, commit->author))
5860                 return TRUE;
5862         if (opt_rev_graph && commit->graph_size &&
5863             draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
5864                 return TRUE;
5866         if (opt_show_refs && commit->refs) {
5867                 size_t i = 0;
5869                 do {
5870                         enum line_type type;
5872                         if (commit->refs[i]->head)
5873                                 type = LINE_MAIN_HEAD;
5874                         else if (commit->refs[i]->ltag)
5875                                 type = LINE_MAIN_LOCAL_TAG;
5876                         else if (commit->refs[i]->tag)
5877                                 type = LINE_MAIN_TAG;
5878                         else if (commit->refs[i]->tracked)
5879                                 type = LINE_MAIN_TRACKED;
5880                         else if (commit->refs[i]->remote)
5881                                 type = LINE_MAIN_REMOTE;
5882                         else
5883                                 type = LINE_MAIN_REF;
5885                         if (draw_text(view, type, "[", TRUE) ||
5886                             draw_text(view, type, commit->refs[i]->name, TRUE) ||
5887                             draw_text(view, type, "]", TRUE))
5888                                 return TRUE;
5890                         if (draw_text(view, LINE_DEFAULT, " ", TRUE))
5891                                 return TRUE;
5892                 } while (commit->refs[i++]->next);
5893         }
5895         draw_text(view, LINE_DEFAULT, commit->title, TRUE);
5896         return TRUE;
5899 /* Reads git log --pretty=raw output and parses it into the commit struct. */
5900 static bool
5901 main_read(struct view *view, char *line)
5903         static struct rev_graph *graph = graph_stacks;
5904         enum line_type type;
5905         struct commit *commit;
5907         if (!line) {
5908                 int i;
5910                 if (!view->lines && !view->parent)
5911                         die("No revisions match the given arguments.");
5912                 if (view->lines > 0) {
5913                         commit = view->line[view->lines - 1].data;
5914                         view->line[view->lines - 1].dirty = 1;
5915                         if (!commit->author) {
5916                                 view->lines--;
5917                                 free(commit);
5918                                 graph->commit = NULL;
5919                         }
5920                 }
5921                 update_rev_graph(view, graph);
5923                 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
5924                         clear_rev_graph(&graph_stacks[i]);
5925                 return TRUE;
5926         }
5928         type = get_line_type(line);
5929         if (type == LINE_COMMIT) {
5930                 commit = calloc(1, sizeof(struct commit));
5931                 if (!commit)
5932                         return FALSE;
5934                 line += STRING_SIZE("commit ");
5935                 if (*line == '-') {
5936                         graph->boundary = 1;
5937                         line++;
5938                 }
5940                 string_copy_rev(commit->id, line);
5941                 commit->refs = get_refs(commit->id);
5942                 graph->commit = commit;
5943                 add_line_data(view, commit, LINE_MAIN_COMMIT);
5945                 while ((line = strchr(line, ' '))) {
5946                         line++;
5947                         push_rev_graph(graph->parents, line);
5948                         commit->has_parents = TRUE;
5949                 }
5950                 return TRUE;
5951         }
5953         if (!view->lines)
5954                 return TRUE;
5955         commit = view->line[view->lines - 1].data;
5957         switch (type) {
5958         case LINE_PARENT:
5959                 if (commit->has_parents)
5960                         break;
5961                 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
5962                 break;
5964         case LINE_AUTHOR:
5965                 parse_author_line(line + STRING_SIZE("author "),
5966                                   &commit->author, &commit->time);
5967                 update_rev_graph(view, graph);
5968                 graph = graph->next;
5969                 break;
5971         default:
5972                 /* Fill in the commit title if it has not already been set. */
5973                 if (commit->title[0])
5974                         break;
5976                 /* Require titles to start with a non-space character at the
5977                  * offset used by git log. */
5978                 if (strncmp(line, "    ", 4))
5979                         break;
5980                 line += 4;
5981                 /* Well, if the title starts with a whitespace character,
5982                  * try to be forgiving.  Otherwise we end up with no title. */
5983                 while (isspace(*line))
5984                         line++;
5985                 if (*line == '\0')
5986                         break;
5987                 /* FIXME: More graceful handling of titles; append "..." to
5988                  * shortened titles, etc. */
5990                 string_expand(commit->title, sizeof(commit->title), line, 1);
5991                 view->line[view->lines - 1].dirty = 1;
5992         }
5994         return TRUE;
5997 static enum request
5998 main_request(struct view *view, enum request request, struct line *line)
6000         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
6002         switch (request) {
6003         case REQ_ENTER:
6004                 open_view(view, REQ_VIEW_DIFF, flags);
6005                 break;
6006         case REQ_REFRESH:
6007                 load_refs();
6008                 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
6009                 break;
6010         default:
6011                 return request;
6012         }
6014         return REQ_NONE;
6017 static bool
6018 grep_refs(struct ref **refs, regex_t *regex)
6020         regmatch_t pmatch;
6021         size_t i = 0;
6023         if (!opt_show_refs || !refs)
6024                 return FALSE;
6025         do {
6026                 if (regexec(regex, refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
6027                         return TRUE;
6028         } while (refs[i++]->next);
6030         return FALSE;
6033 static bool
6034 main_grep(struct view *view, struct line *line)
6036         struct commit *commit = line->data;
6037         const char *text[] = {
6038                 commit->title,
6039                 opt_author ? commit->author : "",
6040                 opt_date ? mkdate(&commit->time) : "",
6041                 NULL
6042         };
6044         return grep_text(view, text) || grep_refs(commit->refs, view->regex);
6047 static void
6048 main_select(struct view *view, struct line *line)
6050         struct commit *commit = line->data;
6052         string_copy_rev(view->ref, commit->id);
6053         string_copy_rev(ref_commit, view->ref);
6056 static struct view_ops main_ops = {
6057         "commit",
6058         main_argv,
6059         NULL,
6060         main_read,
6061         main_draw,
6062         main_request,
6063         main_grep,
6064         main_select,
6065 };
6068 /*
6069  * Unicode / UTF-8 handling
6070  *
6071  * NOTE: Much of the following code for dealing with Unicode is derived from
6072  * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
6073  * src/intl/charset.c from the UTF-8 branch commit elinks-0.11.0-g31f2c28.
6074  */
6076 static inline int
6077 unicode_width(unsigned long c)
6079         if (c >= 0x1100 &&
6080            (c <= 0x115f                         /* Hangul Jamo */
6081             || c == 0x2329
6082             || c == 0x232a
6083             || (c >= 0x2e80  && c <= 0xa4cf && c != 0x303f)
6084                                                 /* CJK ... Yi */
6085             || (c >= 0xac00  && c <= 0xd7a3)    /* Hangul Syllables */
6086             || (c >= 0xf900  && c <= 0xfaff)    /* CJK Compatibility Ideographs */
6087             || (c >= 0xfe30  && c <= 0xfe6f)    /* CJK Compatibility Forms */
6088             || (c >= 0xff00  && c <= 0xff60)    /* Fullwidth Forms */
6089             || (c >= 0xffe0  && c <= 0xffe6)
6090             || (c >= 0x20000 && c <= 0x2fffd)
6091             || (c >= 0x30000 && c <= 0x3fffd)))
6092                 return 2;
6094         if (c == '\t')
6095                 return opt_tab_size;
6097         return 1;
6100 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
6101  * Illegal bytes are set one. */
6102 static const unsigned char utf8_bytes[256] = {
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         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,
6109         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,
6110         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,
6111 };
6113 /* Decode UTF-8 multi-byte representation into a Unicode character. */
6114 static inline unsigned long
6115 utf8_to_unicode(const char *string, size_t length)
6117         unsigned long unicode;
6119         switch (length) {
6120         case 1:
6121                 unicode  =   string[0];
6122                 break;
6123         case 2:
6124                 unicode  =  (string[0] & 0x1f) << 6;
6125                 unicode +=  (string[1] & 0x3f);
6126                 break;
6127         case 3:
6128                 unicode  =  (string[0] & 0x0f) << 12;
6129                 unicode += ((string[1] & 0x3f) << 6);
6130                 unicode +=  (string[2] & 0x3f);
6131                 break;
6132         case 4:
6133                 unicode  =  (string[0] & 0x0f) << 18;
6134                 unicode += ((string[1] & 0x3f) << 12);
6135                 unicode += ((string[2] & 0x3f) << 6);
6136                 unicode +=  (string[3] & 0x3f);
6137                 break;
6138         case 5:
6139                 unicode  =  (string[0] & 0x0f) << 24;
6140                 unicode += ((string[1] & 0x3f) << 18);
6141                 unicode += ((string[2] & 0x3f) << 12);
6142                 unicode += ((string[3] & 0x3f) << 6);
6143                 unicode +=  (string[4] & 0x3f);
6144                 break;
6145         case 6:
6146                 unicode  =  (string[0] & 0x01) << 30;
6147                 unicode += ((string[1] & 0x3f) << 24);
6148                 unicode += ((string[2] & 0x3f) << 18);
6149                 unicode += ((string[3] & 0x3f) << 12);
6150                 unicode += ((string[4] & 0x3f) << 6);
6151                 unicode +=  (string[5] & 0x3f);
6152                 break;
6153         default:
6154                 die("Invalid Unicode length");
6155         }
6157         /* Invalid characters could return the special 0xfffd value but NUL
6158          * should be just as good. */
6159         return unicode > 0xffff ? 0 : unicode;
6162 /* Calculates how much of string can be shown within the given maximum width
6163  * and sets trimmed parameter to non-zero value if all of string could not be
6164  * shown. If the reserve flag is TRUE, it will reserve at least one
6165  * trailing character, which can be useful when drawing a delimiter.
6166  *
6167  * Returns the number of bytes to output from string to satisfy max_width. */
6168 static size_t
6169 utf8_length(const char **start, size_t skip, int *width, size_t max_width, int *trimmed, bool reserve)
6171         const char *string = *start;
6172         const char *end = strchr(string, '\0');
6173         unsigned char last_bytes = 0;
6174         size_t last_ucwidth = 0;
6176         *width = 0;
6177         *trimmed = 0;
6179         while (string < end) {
6180                 int c = *(unsigned char *) string;
6181                 unsigned char bytes = utf8_bytes[c];
6182                 size_t ucwidth;
6183                 unsigned long unicode;
6185                 if (string + bytes > end)
6186                         break;
6188                 /* Change representation to figure out whether
6189                  * it is a single- or double-width character. */
6191                 unicode = utf8_to_unicode(string, bytes);
6192                 /* FIXME: Graceful handling of invalid Unicode character. */
6193                 if (!unicode)
6194                         break;
6196                 ucwidth = unicode_width(unicode);
6197                 if (skip > 0) {
6198                         skip -= ucwidth <= skip ? ucwidth : skip;
6199                         *start += bytes;
6200                 }
6201                 *width  += ucwidth;
6202                 if (*width > max_width) {
6203                         *trimmed = 1;
6204                         *width -= ucwidth;
6205                         if (reserve && *width == max_width) {
6206                                 string -= last_bytes;
6207                                 *width -= last_ucwidth;
6208                         }
6209                         break;
6210                 }
6212                 string  += bytes;
6213                 last_bytes = ucwidth ? bytes : 0;
6214                 last_ucwidth = ucwidth;
6215         }
6217         return string - *start;
6221 /*
6222  * Status management
6223  */
6225 /* Whether or not the curses interface has been initialized. */
6226 static bool cursed = FALSE;
6228 /* Terminal hacks and workarounds. */
6229 static bool use_scroll_redrawwin;
6230 static bool use_scroll_status_wclear;
6232 /* The status window is used for polling keystrokes. */
6233 static WINDOW *status_win;
6235 /* Reading from the prompt? */
6236 static bool input_mode = FALSE;
6238 static bool status_empty = FALSE;
6240 /* Update status and title window. */
6241 static void
6242 report(const char *msg, ...)
6244         struct view *view = display[current_view];
6246         if (input_mode)
6247                 return;
6249         if (!view) {
6250                 char buf[SIZEOF_STR];
6251                 va_list args;
6253                 va_start(args, msg);
6254                 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
6255                         buf[sizeof(buf) - 1] = 0;
6256                         buf[sizeof(buf) - 2] = '.';
6257                         buf[sizeof(buf) - 3] = '.';
6258                         buf[sizeof(buf) - 4] = '.';
6259                 }
6260                 va_end(args);
6261                 die("%s", buf);
6262         }
6264         if (!status_empty || *msg) {
6265                 va_list args;
6267                 va_start(args, msg);
6269                 wmove(status_win, 0, 0);
6270                 if (view->has_scrolled && use_scroll_status_wclear)
6271                         wclear(status_win);
6272                 if (*msg) {
6273                         vwprintw(status_win, msg, args);
6274                         status_empty = FALSE;
6275                 } else {
6276                         status_empty = TRUE;
6277                 }
6278                 wclrtoeol(status_win);
6279                 wnoutrefresh(status_win);
6281                 va_end(args);
6282         }
6284         update_view_title(view);
6287 /* Controls when nodelay should be in effect when polling user input. */
6288 static void
6289 set_nonblocking_input(bool loading)
6291         static unsigned int loading_views;
6293         if ((loading == FALSE && loading_views-- == 1) ||
6294             (loading == TRUE  && loading_views++ == 0))
6295                 nodelay(status_win, loading);
6298 static void
6299 init_display(void)
6301         const char *term;
6302         int x, y;
6304         /* Initialize the curses library */
6305         if (isatty(STDIN_FILENO)) {
6306                 cursed = !!initscr();
6307                 opt_tty = stdin;
6308         } else {
6309                 /* Leave stdin and stdout alone when acting as a pager. */
6310                 opt_tty = fopen("/dev/tty", "r+");
6311                 if (!opt_tty)
6312                         die("Failed to open /dev/tty");
6313                 cursed = !!newterm(NULL, opt_tty, opt_tty);
6314         }
6316         if (!cursed)
6317                 die("Failed to initialize curses");
6319         nonl();         /* Disable conversion and detect newlines from input. */
6320         cbreak();       /* Take input chars one at a time, no wait for \n */
6321         noecho();       /* Don't echo input */
6322         leaveok(stdscr, FALSE);
6324         if (has_colors())
6325                 init_colors();
6327         getmaxyx(stdscr, y, x);
6328         status_win = newwin(1, 0, y - 1, 0);
6329         if (!status_win)
6330                 die("Failed to create status window");
6332         /* Enable keyboard mapping */
6333         keypad(status_win, TRUE);
6334         wbkgdset(status_win, get_line_attr(LINE_STATUS));
6336         TABSIZE = opt_tab_size;
6337         if (opt_line_graphics) {
6338                 line_graphics[LINE_GRAPHIC_VLINE] = ACS_VLINE;
6339         }
6341         term = getenv("XTERM_VERSION") ? NULL : getenv("COLORTERM");
6342         if (term && !strcmp(term, "gnome-terminal")) {
6343                 /* In the gnome-terminal-emulator, the message from
6344                  * scrolling up one line when impossible followed by
6345                  * scrolling down one line causes corruption of the
6346                  * status line. This is fixed by calling wclear. */
6347                 use_scroll_status_wclear = TRUE;
6348                 use_scroll_redrawwin = FALSE;
6350         } else if (term && !strcmp(term, "xrvt-xpm")) {
6351                 /* No problems with full optimizations in xrvt-(unicode)
6352                  * and aterm. */
6353                 use_scroll_status_wclear = use_scroll_redrawwin = FALSE;
6355         } else {
6356                 /* When scrolling in (u)xterm the last line in the
6357                  * scrolling direction will update slowly. */
6358                 use_scroll_redrawwin = TRUE;
6359                 use_scroll_status_wclear = FALSE;
6360         }
6363 static int
6364 get_input(int prompt_position)
6366         struct view *view;
6367         int i, key, cursor_y, cursor_x;
6369         if (prompt_position)
6370                 input_mode = TRUE;
6372         while (TRUE) {
6373                 foreach_view (view, i) {
6374                         update_view(view);
6375                         if (view_is_displayed(view) && view->has_scrolled &&
6376                             use_scroll_redrawwin)
6377                                 redrawwin(view->win);
6378                         view->has_scrolled = FALSE;
6379                 }
6381                 /* Update the cursor position. */
6382                 if (prompt_position) {
6383                         getbegyx(status_win, cursor_y, cursor_x);
6384                         cursor_x = prompt_position;
6385                 } else {
6386                         view = display[current_view];
6387                         getbegyx(view->win, cursor_y, cursor_x);
6388                         cursor_x = view->width - 1;
6389                         cursor_y += view->lineno - view->offset;
6390                 }
6391                 setsyx(cursor_y, cursor_x);
6393                 /* Refresh, accept single keystroke of input */
6394                 doupdate();
6395                 key = wgetch(status_win);
6397                 /* wgetch() with nodelay() enabled returns ERR when
6398                  * there's no input. */
6399                 if (key == ERR) {
6401                 } else if (key == KEY_RESIZE) {
6402                         int height, width;
6404                         getmaxyx(stdscr, height, width);
6406                         wresize(status_win, 1, width);
6407                         mvwin(status_win, height - 1, 0);
6408                         wnoutrefresh(status_win);
6409                         resize_display();
6410                         redraw_display(TRUE);
6412                 } else {
6413                         input_mode = FALSE;
6414                         return key;
6415                 }
6416         }
6419 static char *
6420 prompt_input(const char *prompt, input_handler handler, void *data)
6422         enum input_status status = INPUT_OK;
6423         static char buf[SIZEOF_STR];
6424         size_t pos = 0;
6426         buf[pos] = 0;
6428         while (status == INPUT_OK || status == INPUT_SKIP) {
6429                 int key;
6431                 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
6432                 wclrtoeol(status_win);
6434                 key = get_input(pos + 1);
6435                 switch (key) {
6436                 case KEY_RETURN:
6437                 case KEY_ENTER:
6438                 case '\n':
6439                         status = pos ? INPUT_STOP : INPUT_CANCEL;
6440                         break;
6442                 case KEY_BACKSPACE:
6443                         if (pos > 0)
6444                                 buf[--pos] = 0;
6445                         else
6446                                 status = INPUT_CANCEL;
6447                         break;
6449                 case KEY_ESC:
6450                         status = INPUT_CANCEL;
6451                         break;
6453                 default:
6454                         if (pos >= sizeof(buf)) {
6455                                 report("Input string too long");
6456                                 return NULL;
6457                         }
6459                         status = handler(data, buf, key);
6460                         if (status == INPUT_OK)
6461                                 buf[pos++] = (char) key;
6462                 }
6463         }
6465         /* Clear the status window */
6466         status_empty = FALSE;
6467         report("");
6469         if (status == INPUT_CANCEL)
6470                 return NULL;
6472         buf[pos++] = 0;
6474         return buf;
6477 static enum input_status
6478 prompt_yesno_handler(void *data, char *buf, int c)
6480         if (c == 'y' || c == 'Y')
6481                 return INPUT_STOP;
6482         if (c == 'n' || c == 'N')
6483                 return INPUT_CANCEL;
6484         return INPUT_SKIP;
6487 static bool
6488 prompt_yesno(const char *prompt)
6490         char prompt2[SIZEOF_STR];
6492         if (!string_format(prompt2, "%s [Yy/Nn]", prompt))
6493                 return FALSE;
6495         return !!prompt_input(prompt2, prompt_yesno_handler, NULL);
6498 static enum input_status
6499 read_prompt_handler(void *data, char *buf, int c)
6501         return isprint(c) ? INPUT_OK : INPUT_SKIP;
6504 static char *
6505 read_prompt(const char *prompt)
6507         return prompt_input(prompt, read_prompt_handler, NULL);
6510 /*
6511  * Repository properties
6512  */
6514 static struct ref *refs = NULL;
6515 static size_t refs_size = 0;
6517 /* Id <-> ref store */
6518 static struct ref ***id_refs = NULL;
6519 static size_t id_refs_size = 0;
6521 DEFINE_ALLOCATOR(realloc_refs, struct ref, 256)
6522 DEFINE_ALLOCATOR(realloc_refs_list, struct ref *, 8)
6523 DEFINE_ALLOCATOR(realloc_refs_lists, struct ref **, 8)
6525 static int
6526 compare_refs(const void *ref1_, const void *ref2_)
6528         const struct ref *ref1 = *(const struct ref **)ref1_;
6529         const struct ref *ref2 = *(const struct ref **)ref2_;
6531         if (ref1->tag != ref2->tag)
6532                 return ref2->tag - ref1->tag;
6533         if (ref1->ltag != ref2->ltag)
6534                 return ref2->ltag - ref2->ltag;
6535         if (ref1->head != ref2->head)
6536                 return ref2->head - ref1->head;
6537         if (ref1->tracked != ref2->tracked)
6538                 return ref2->tracked - ref1->tracked;
6539         if (ref1->remote != ref2->remote)
6540                 return ref2->remote - ref1->remote;
6541         return strcmp(ref1->name, ref2->name);
6544 static struct ref **
6545 get_refs(const char *id)
6547         struct ref **ref_list = NULL;
6548         size_t ref_list_size = 0;
6549         size_t i;
6551         for (i = 0; i < id_refs_size; i++)
6552                 if (!strcmp(id, id_refs[i][0]->id))
6553                         return id_refs[i];
6555         if (!realloc_refs_lists(&id_refs, id_refs_size, 1))
6556                 return NULL;
6558         for (i = 0; i < refs_size; i++) {
6559                 if (strcmp(id, refs[i].id))
6560                         continue;
6562                 if (!realloc_refs_list(&ref_list, ref_list_size, 1))
6563                         return ref_list;
6565                 ref_list[ref_list_size] = &refs[i];
6566                 /* XXX: The properties of the commit chains ensures that we can
6567                  * safely modify the shared ref. The repo references will
6568                  * always be similar for the same id. */
6569                 ref_list[ref_list_size]->next = 1;
6570                 ref_list_size++;
6571         }
6573         if (ref_list) {
6574                 qsort(ref_list, ref_list_size, sizeof(*ref_list), compare_refs);
6575                 ref_list[ref_list_size - 1]->next = 0;
6576                 id_refs[id_refs_size++] = ref_list;
6577         }
6579         return ref_list;
6582 static int
6583 read_ref(char *id, size_t idlen, char *name, size_t namelen)
6585         struct ref *ref;
6586         bool tag = FALSE;
6587         bool ltag = FALSE;
6588         bool remote = FALSE;
6589         bool tracked = FALSE;
6590         bool check_replace = FALSE;
6591         bool head = FALSE;
6593         if (!prefixcmp(name, "refs/tags/")) {
6594                 if (!suffixcmp(name, namelen, "^{}")) {
6595                         namelen -= 3;
6596                         name[namelen] = 0;
6597                         if (refs_size > 0 && refs[refs_size - 1].ltag == TRUE)
6598                                 check_replace = TRUE;
6599                 } else {
6600                         ltag = TRUE;
6601                 }
6603                 tag = TRUE;
6604                 namelen -= STRING_SIZE("refs/tags/");
6605                 name    += STRING_SIZE("refs/tags/");
6607         } else if (!prefixcmp(name, "refs/remotes/")) {
6608                 remote = TRUE;
6609                 namelen -= STRING_SIZE("refs/remotes/");
6610                 name    += STRING_SIZE("refs/remotes/");
6611                 tracked  = !strcmp(opt_remote, name);
6613         } else if (!prefixcmp(name, "refs/heads/")) {
6614                 namelen -= STRING_SIZE("refs/heads/");
6615                 name    += STRING_SIZE("refs/heads/");
6616                 head     = !strncmp(opt_head, name, namelen);
6618         } else if (!strcmp(name, "HEAD")) {
6619                 string_ncopy(opt_head_rev, id, idlen);
6620                 return OK;
6621         }
6623         if (check_replace && !strcmp(name, refs[refs_size - 1].name)) {
6624                 /* it's an annotated tag, replace the previous SHA1 with the
6625                  * resolved commit id; relies on the fact git-ls-remote lists
6626                  * the commit id of an annotated tag right before the commit id
6627                  * it points to. */
6628                 refs[refs_size - 1].ltag = ltag;
6629                 string_copy_rev(refs[refs_size - 1].id, id);
6631                 return OK;
6632         }
6634         if (!realloc_refs(&refs, refs_size, 1))
6635                 return ERR;
6637         ref = &refs[refs_size++];
6638         ref->name = malloc(namelen + 1);
6639         if (!ref->name)
6640                 return ERR;
6642         strncpy(ref->name, name, namelen);
6643         ref->name[namelen] = 0;
6644         ref->head = head;
6645         ref->tag = tag;
6646         ref->ltag = ltag;
6647         ref->remote = remote;
6648         ref->tracked = tracked;
6649         string_copy_rev(ref->id, id);
6651         return OK;
6654 static int
6655 load_refs(void)
6657         const char *head_argv[] = {
6658                 "git", "symbolic-ref", "HEAD", NULL
6659         };
6660         static const char *ls_remote_argv[SIZEOF_ARG] = {
6661                 "git", "ls-remote", opt_git_dir, NULL
6662         };
6663         static bool init = FALSE;
6665         if (!init) {
6666                 argv_from_env(ls_remote_argv, "TIG_LS_REMOTE");
6667                 init = TRUE;
6668         }
6670         if (!*opt_git_dir)
6671                 return OK;
6673         if (run_io_buf(head_argv, opt_head, sizeof(opt_head)) &&
6674             !prefixcmp(opt_head, "refs/heads/")) {
6675                 char *offset = opt_head + STRING_SIZE("refs/heads/");
6677                 memmove(opt_head, offset, strlen(offset) + 1);
6678         }
6680         while (refs_size > 0)
6681                 free(refs[--refs_size].name);
6682         while (id_refs_size > 0)
6683                 free(id_refs[--id_refs_size]);
6685         return run_io_load(ls_remote_argv, "\t", read_ref);
6688 static void
6689 set_remote_branch(const char *name, const char *value, size_t valuelen)
6691         if (!strcmp(name, ".remote")) {
6692                 string_ncopy(opt_remote, value, valuelen);
6694         } else if (*opt_remote && !strcmp(name, ".merge")) {
6695                 size_t from = strlen(opt_remote);
6697                 if (!prefixcmp(value, "refs/heads/"))
6698                         value += STRING_SIZE("refs/heads/");
6700                 if (!string_format_from(opt_remote, &from, "/%s", value))
6701                         opt_remote[0] = 0;
6702         }
6705 static void
6706 set_repo_config_option(char *name, char *value, int (*cmd)(int, const char **))
6708         const char *argv[SIZEOF_ARG] = { name, "=" };
6709         int argc = 1 + (cmd == option_set_command);
6710         int error = ERR;
6712         if (!argv_from_string(argv, &argc, value))
6713                 config_msg = "Too many option arguments";
6714         else
6715                 error = cmd(argc, argv);
6717         if (error == ERR)
6718                 warn("Option 'tig.%s': %s", name, config_msg);
6721 static bool
6722 set_environment_variable(const char *name, const char *value)
6724         size_t len = strlen(name) + 1 + strlen(value) + 1;
6725         char *env = malloc(len);
6727         if (env &&
6728             string_nformat(env, len, NULL, "%s=%s", name, value) &&
6729             putenv(env) == 0)
6730                 return TRUE;
6731         free(env);
6732         return FALSE;
6735 static void
6736 set_work_tree(const char *value)
6738         char cwd[SIZEOF_STR];
6740         if (!getcwd(cwd, sizeof(cwd)))
6741                 die("Failed to get cwd path: %s", strerror(errno));
6742         if (chdir(opt_git_dir) < 0)
6743                 die("Failed to chdir(%s): %s", strerror(errno));
6744         if (!getcwd(opt_git_dir, sizeof(opt_git_dir)))
6745                 die("Failed to get git path: %s", strerror(errno));
6746         if (chdir(cwd) < 0)
6747                 die("Failed to chdir(%s): %s", cwd, strerror(errno));
6748         if (chdir(value) < 0)
6749                 die("Failed to chdir(%s): %s", value, strerror(errno));
6750         if (!getcwd(cwd, sizeof(cwd)))
6751                 die("Failed to get cwd path: %s", strerror(errno));
6752         if (!set_environment_variable("GIT_WORK_TREE", cwd))
6753                 die("Failed to set GIT_WORK_TREE to '%s'", cwd);
6754         if (!set_environment_variable("GIT_DIR", opt_git_dir))
6755                 die("Failed to set GIT_DIR to '%s'", opt_git_dir);
6756         opt_is_inside_work_tree = TRUE;
6759 static int
6760 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
6762         if (!strcmp(name, "i18n.commitencoding"))
6763                 string_ncopy(opt_encoding, value, valuelen);
6765         else if (!strcmp(name, "core.editor"))
6766                 string_ncopy(opt_editor, value, valuelen);
6768         else if (!strcmp(name, "core.worktree"))
6769                 set_work_tree(value);
6771         else if (!prefixcmp(name, "tig.color."))
6772                 set_repo_config_option(name + 10, value, option_color_command);
6774         else if (!prefixcmp(name, "tig.bind."))
6775                 set_repo_config_option(name + 9, value, option_bind_command);
6777         else if (!prefixcmp(name, "tig."))
6778                 set_repo_config_option(name + 4, value, option_set_command);
6780         else if (*opt_head && !prefixcmp(name, "branch.") &&
6781                  !strncmp(name + 7, opt_head, strlen(opt_head)))
6782                 set_remote_branch(name + 7 + strlen(opt_head), value, valuelen);
6784         return OK;
6787 static int
6788 load_git_config(void)
6790         const char *config_list_argv[] = { "git", GIT_CONFIG, "--list", NULL };
6792         return run_io_load(config_list_argv, "=", read_repo_config_option);
6795 static int
6796 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
6798         if (!opt_git_dir[0]) {
6799                 string_ncopy(opt_git_dir, name, namelen);
6801         } else if (opt_is_inside_work_tree == -1) {
6802                 /* This can be 3 different values depending on the
6803                  * version of git being used. If git-rev-parse does not
6804                  * understand --is-inside-work-tree it will simply echo
6805                  * the option else either "true" or "false" is printed.
6806                  * Default to true for the unknown case. */
6807                 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
6809         } else if (*name == '.') {
6810                 string_ncopy(opt_cdup, name, namelen);
6812         } else {
6813                 string_ncopy(opt_prefix, name, namelen);
6814         }
6816         return OK;
6819 static int
6820 load_repo_info(void)
6822         const char *rev_parse_argv[] = {
6823                 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
6824                         "--show-cdup", "--show-prefix", NULL
6825         };
6827         return run_io_load(rev_parse_argv, "=", read_repo_info);
6831 /*
6832  * Main
6833  */
6835 static const char usage[] =
6836 "tig " TIG_VERSION " (" __DATE__ ")\n"
6837 "\n"
6838 "Usage: tig        [options] [revs] [--] [paths]\n"
6839 "   or: tig show   [options] [revs] [--] [paths]\n"
6840 "   or: tig blame  [rev] path\n"
6841 "   or: tig status\n"
6842 "   or: tig <      [git command output]\n"
6843 "\n"
6844 "Options:\n"
6845 "  -v, --version   Show version and exit\n"
6846 "  -h, --help      Show help message and exit";
6848 static void __NORETURN
6849 quit(int sig)
6851         /* XXX: Restore tty modes and let the OS cleanup the rest! */
6852         if (cursed)
6853                 endwin();
6854         exit(0);
6857 static void __NORETURN
6858 die(const char *err, ...)
6860         va_list args;
6862         endwin();
6864         va_start(args, err);
6865         fputs("tig: ", stderr);
6866         vfprintf(stderr, err, args);
6867         fputs("\n", stderr);
6868         va_end(args);
6870         exit(1);
6873 static void
6874 warn(const char *msg, ...)
6876         va_list args;
6878         va_start(args, msg);
6879         fputs("tig warning: ", stderr);
6880         vfprintf(stderr, msg, args);
6881         fputs("\n", stderr);
6882         va_end(args);
6885 static enum request
6886 parse_options(int argc, const char *argv[])
6888         enum request request = REQ_VIEW_MAIN;
6889         const char *subcommand;
6890         bool seen_dashdash = FALSE;
6891         /* XXX: This is vulnerable to the user overriding options
6892          * required for the main view parser. */
6893         const char *custom_argv[SIZEOF_ARG] = {
6894                 "git", "log", "--no-color", "--pretty=raw", "--parents",
6895                         "--topo-order", NULL
6896         };
6897         int i, j = 6;
6899         if (!isatty(STDIN_FILENO)) {
6900                 io_open(&VIEW(REQ_VIEW_PAGER)->io, "");
6901                 return REQ_VIEW_PAGER;
6902         }
6904         if (argc <= 1)
6905                 return REQ_NONE;
6907         subcommand = argv[1];
6908         if (!strcmp(subcommand, "status")) {
6909                 if (argc > 2)
6910                         warn("ignoring arguments after `%s'", subcommand);
6911                 return REQ_VIEW_STATUS;
6913         } else if (!strcmp(subcommand, "blame")) {
6914                 if (argc <= 2 || argc > 4)
6915                         die("invalid number of options to blame\n\n%s", usage);
6917                 i = 2;
6918                 if (argc == 4) {
6919                         string_ncopy(opt_ref, argv[i], strlen(argv[i]));
6920                         i++;
6921                 }
6923                 string_ncopy(opt_file, argv[i], strlen(argv[i]));
6924                 return REQ_VIEW_BLAME;
6926         } else if (!strcmp(subcommand, "show")) {
6927                 request = REQ_VIEW_DIFF;
6929         } else {
6930                 subcommand = NULL;
6931         }
6933         if (subcommand) {
6934                 custom_argv[1] = subcommand;
6935                 j = 2;
6936         }
6938         for (i = 1 + !!subcommand; i < argc; i++) {
6939                 const char *opt = argv[i];
6941                 if (seen_dashdash || !strcmp(opt, "--")) {
6942                         seen_dashdash = TRUE;
6944                 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
6945                         printf("tig version %s\n", TIG_VERSION);
6946                         quit(0);
6948                 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
6949                         printf("%s\n", usage);
6950                         quit(0);
6951                 }
6953                 custom_argv[j++] = opt;
6954                 if (j >= ARRAY_SIZE(custom_argv))
6955                         die("command too long");
6956         }
6958         if (!prepare_update(VIEW(request), custom_argv, NULL, FORMAT_NONE))                                                                        
6959                 die("Failed to format arguments"); 
6961         return request;
6964 int
6965 main(int argc, const char *argv[])
6967         enum request request = parse_options(argc, argv);
6968         struct view *view;
6969         size_t i;
6971         signal(SIGINT, quit);
6972         signal(SIGPIPE, SIG_IGN);
6974         if (setlocale(LC_ALL, "")) {
6975                 char *codeset = nl_langinfo(CODESET);
6977                 string_ncopy(opt_codeset, codeset, strlen(codeset));
6978         }
6980         if (load_repo_info() == ERR)
6981                 die("Failed to load repo info.");
6983         if (load_options() == ERR)
6984                 die("Failed to load user config.");
6986         if (load_git_config() == ERR)
6987                 die("Failed to load repo config.");
6989         /* Require a git repository unless when running in pager mode. */
6990         if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
6991                 die("Not a git repository");
6993         if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
6994                 opt_utf8 = FALSE;
6996         if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
6997                 opt_iconv = iconv_open(opt_codeset, opt_encoding);
6998                 if (opt_iconv == ICONV_NONE)
6999                         die("Failed to initialize character set conversion");
7000         }
7002         if (load_refs() == ERR)
7003                 die("Failed to load refs.");
7005         foreach_view (view, i)
7006                 argv_from_env(view->ops->argv, view->cmd_env);
7008         init_display();
7010         if (request != REQ_NONE)
7011                 open_view(NULL, request, OPEN_PREPARED);
7012         request = request == REQ_NONE ? REQ_VIEW_MAIN : REQ_NONE;
7014         while (view_driver(display[current_view], request)) {
7015                 int key = get_input(0);
7017                 view = display[current_view];
7018                 request = get_keybinding(view->keymap, key);
7020                 /* Some low-level request handling. This keeps access to
7021                  * status_win restricted. */
7022                 switch (request) {
7023                 case REQ_PROMPT:
7024                 {
7025                         char *cmd = read_prompt(":");
7027                         if (cmd && isdigit(*cmd)) {
7028                                 int lineno = view->lineno + 1;
7030                                 if (parse_int(&lineno, cmd, 1, view->lines + 1) == OK) {
7031                                         select_view_line(view, lineno - 1);
7032                                         report("");
7033                                 } else {
7034                                         report("Unable to parse '%s' as a line number", cmd);
7035                                 }
7037                         } else if (cmd) {
7038                                 struct view *next = VIEW(REQ_VIEW_PAGER);
7039                                 const char *argv[SIZEOF_ARG] = { "git" };
7040                                 int argc = 1;
7042                                 /* When running random commands, initially show the
7043                                  * command in the title. However, it maybe later be
7044                                  * overwritten if a commit line is selected. */
7045                                 string_ncopy(next->ref, cmd, strlen(cmd));
7047                                 if (!argv_from_string(argv, &argc, cmd)) {
7048                                         report("Too many arguments");
7049                                 } else if (!prepare_update(next, argv, NULL, FORMAT_DASH)) {
7050                                         report("Failed to format command");
7051                                 } else {
7052                                         open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
7053                                 }
7054                         }
7056                         request = REQ_NONE;
7057                         break;
7058                 }
7059                 case REQ_SEARCH:
7060                 case REQ_SEARCH_BACK:
7061                 {
7062                         const char *prompt = request == REQ_SEARCH ? "/" : "?";
7063                         char *search = read_prompt(prompt);
7065                         if (search)
7066                                 string_ncopy(opt_search, search, strlen(search));
7067                         else if (*opt_search)
7068                                 request = request == REQ_SEARCH ?
7069                                         REQ_FIND_NEXT :
7070                                         REQ_FIND_PREV;
7071                         else
7072                                 request = REQ_NONE;
7073                         break;
7074                 }
7075                 default:
7076                         break;
7077                 }
7078         }
7080         quit(0);
7082         return 0;